ACM 各種排序算法

演示動畫來源於:https://www.cnblogs.com/onepixel/p/7674659.html

算法分類

插入類
  • 插入排序
  • 希爾排序
交換類
  • 冒泡排序
  • 快速排序
選擇類
  • 選擇排序
  • 堆排序
歸併類
  • 歸併排序

排序算法的性能指標:

  • 時間複雜度
  • 空間複雜度
  • 穩定性1

具體原理以及實現

插入排序

  1. 算法描述:
    從數組的第一個元素開始,認定該元素已經被排序,然後取下一個元素,對於已經排序的元素序列中從後往前掃描,如果已經排序的某一個元素大於新元素,則將這個元素往後移一個位置,重複此操作,直至找到已排序的某元素小於等於這個新元素,然後將這個新元素插入到該元素的後面。然後遍歷餘下的數組;

     - 從第一個元素開始,認定該元素已被排序
     - 取下一個元素,對於已經排序的元素序列中從後往前掃描
     - 如果已經排序的某一個元素大於新元素,則將這個元素往後移一個位置
     - 重複上一操作,直至找到已排序的某元素小於等於這個新元素
     - 將這個新元素插入到該元素的後面
     - 繼續遍歷餘下的數組
    在這裏插入圖片描述
  2. 性能:
    時間複雜度:O(n2n^2)
    空間複雜度:O(1)
    穩定性:穩定
  3. 代碼實現:
//插入排序
    //傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
    int* InsertSort(int *a,int lena)
    {
        //preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
        int preindex,tmp;
        for (int i = 1; i < lena; i++)
        {
            //tmp初始化
            tmp=a[i];
            //初始化爲i-1
            preindex = i-1;
            //插入前的比較
            while(preindex>=0 &&a[preindex]>tmp)
            {
                a[preindex+1]=a[preindex];
                preindex--;
            }
            //preindex現在就是那個小於等於新元素的位置,插入其後
            a[preindex+1]=tmp;
        }
        return a;
    }

希爾排序

  1. 算法描述:
    又稱爲“縮小增量排序”,屬於直接插入排序算法的一個升級版本,實現希爾排序需要手動或者動態的設置一個遞減的增量數組,這個數組最後一個數必須是1,a1<n,a2,...,ak=1a_1<n,a_2,...,a_k=1,有了這個增量數組之後,就要對需要排序的數組b進行k次排序,每一次排序都要順序對應用到一個增量;這個增量aia_i的作用通俗一點來說就是對索引下標進行操作,如0,0+1ai,...,0+d0ai0,0+1*a_i,...,0+d_0*a_i進行插入排序,1,1+1ai,...,1+d1ai1,1+1*a_i,...,1+d_1*a_i進行比較…依次類推,直至無法再進行比較;然後再換下一個增量,直至結束;

     -選擇或者動態一個遞減的增量序列
     -進行k次排序
     -每一次排序,對應一個a,分爲若干排序小組,對於這若干個排序小組進行插入排序;
     -遍歷直至增量爲1

    增量數組爲 5,2,1    注:此動圖旨在幫助更好的理解這個算法,實際過程中是多組交替執行的
    在這裏插入圖片描述
  2. 性能:
    時間複雜度:O(n32n^{\frac{3}{2}})
    空間複雜度:O(1)
    穩定性:不穩定
  3. 代碼實現:
    //希爾排序
    //傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
    int * ShellSort(int *a,int lena)
    {
        //對於增量序列,採用動態,不再手動設置
        //k就是動態的增量
        for (int k = lena/2; k >0; k=k/2)
        {
            //preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
            int preindex,tmp;
           for (int i = k; i < lena; i++)
           {
               //初始化tmp
               tmp=a[i];
               //初始化爲i-k
               preindex=i-k;
               //插入前的比較
               while(preindex>=0&&a[preindex]>tmp)
               {
                   a[preindex+k]=a[preindex];
                    preindex-=k;
               }
               //preindex現在就是那個小於等於新元素的位置,插入其後
               a[preindex+k]=tmp;
           }
        }
        return a;
    }

冒泡排序

  1. 算法描述:
    這是一個比較笨但是又特別明瞭的一個算法,核心就是重複走n次,每一次遍歷,都要比較相鄰的兩個元素,如果爲逆序,那麼則把元素交換位置,否則繼續下一個比較,直至n躺走完
     - 比較相鄰的兩個元素,如果爲逆序,則交換
     - 然後接着遍歷,直至結束
     - 重複以上步驟n次

在這裏插入圖片描述
2. 性能:
時間複雜度:O(n2n^2)
空間複雜度:O(1)
穩定性:穩定排序
3. 代碼演示:

	//冒泡排序
    //傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
    int* BubbleSort(int *a,int lena)
    {
        int tmp;
        for (int i = 0; i < lena-1; i++)
        {
            for (int j = 0; j < lena-1-i; j++)
            {
                if(a[j]>a[j+1])
                {
                    tmp=a[j+1];
                    a[j+1]=a[j];
                    a[j]=tmp;
                }
            }
        }
        return a;
    }

快速排序

  1. 算法描述:
    快速排序是一個收益比較好的一個排序,其核心思想是對於每一次排序,均選擇一個樞軸(pivot),然後小於樞軸的數放在左邊,大於的數放在右邊,然後進行對左邊右邊的數進行遞歸快速排序;
     - 從數組中跳出一個元素作爲樞軸
     - 所有比樞軸小的元素放在樞軸左邊,大的元素則放在樞軸右邊
     - 對左右均進行遞歸的快排
    在這裏插入圖片描述
  2. 性能:
    時間複雜度:O(nlog2nnlog_2n)
    空間複雜度:O(nlog2nnlog_2n)
    穩定性:不穩定排序
  3. 代碼演示:
	//快速排序
    //傳進來的是數組的首地址,以及開始結束的索引
    void QuickSort(int *a, int left, int right)
    {
        //初始化,pivot是樞軸
        int i = left, j = right, pivot;
        //快速排序結束標誌
        if (i >= j)
            return;

        //每一次排序開始便初始化樞軸
        pivot = a[i];
        while (i < j)
        {
            while (i < j && a[j] > pivot)
                j--;
            a[i] = a[j];
            while (i < j && a[i] <= pivot)
                i++;
            a[j] = a[i];
        }
        a[i] = pivot;
        QuickSort(a, left, i);
        QuickSort(a, i + 1, right-1);
    }

選擇排序

  1. 算法描述:
    這是一個簡單直白的排序方式,很切合一般人的思維,核心思想就是進行n-1次排序,每一次排序都選擇一個最小值,然後將選好的最小值放入有序區(從索引0開始),然後開始對無序區接着排序,有序無序區均用同一個數組。
     -初始化,有序區爲空,無序區爲a[0]…a[n-1]
     -第i次排序,從a[i-1]開始一直到末尾,選出最小的一個,然後與a[i-1]互換
     -重複n-1次
    在這裏插入圖片描述
  2. 性能
    時間複雜度:O(n2n^2)
    空間複雜度:O(1)
    穩定性:不穩定排序
  3. 代碼演示:
	//選擇排序
    //傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
    int* SelectSort(int *a,int lena)
    {
        int minindex,tmp;
        for (int i = 0; i < lena-1; i++)
        {
            minindex=i;
            for (int j = i+1; j < lena; j++)
            {
                if(a[j]<a[minindex])
                minindex=j;
            }
            tmp=a[i];
            a[i]=a[minindex];
            a[minindex]=tmp;
        }
        return a;
    }

堆排序

  1. 算法描述
    堆排序是利用了堆這種數據結構,因爲堆本身就要滿足堆積的性質:孩子結點的鍵值小於其父節點(本博客中使用這個性質)。那麼就先初始化一個這樣的大頂堆,,將次堆分爲兩部分,一部分是無序的大頂堆,另一部分就是有序的排列數組,然後將堆頂元素與無序的大頂堆最後一個元素交換,交換後的無序堆的最後一個歸爲有序數組;然後重複此操作;
     -初始化大頂堆
     -將堆頂的元素與無序區的最後一個元素交換
     -交換後的無序區的最後一個元素,歸爲有序區
     -重複n次
    在這裏插入圖片描述
  2. 性質:
    時間複雜度:O(nlog2nnlog_2n)
    空間複雜度:O(n)
    穩定性:不穩定排序
  3. 代碼演示:
    //堆排序,代碼來源於https://www.cnblogs.com/skywang12345/p/3602162.html#a42
    //輔助函數,(最大)堆的向下調整算法
    void maxHeapDown(int *a, int start, int end)
    {
        int c = start;     // 當前(current)節點的位置
        int l = 2 * c + 1; // 左(left)孩子的位置
        int tmp = a[c];    // 當前(current)節點的大小
        for (; l <= end; c = l, l = 2 * l + 1)
        {
            // "l"是左孩子,"l+1"是右孩子
            if (l < end && a[l] < a[l + 1])
                l++; // 左右兩孩子中選擇較大者,即m_heap[l+1]
            if (tmp >= a[l])
                break; // 調整結束
            else       // 交換值
            {
                a[c] = a[l];
                a[l] = tmp;
            }
        }
    }

    //傳進來的是數組的首地址,以及數組的長度
    void HeapSort(int *a, int n)
    {
        int i, tmp;

        // 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
        for (i = n / 2 - 1; i >= 0; i--)
            maxHeapDown(a, i, n - 1);

        // 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
        for (i = n - 1; i > 0; i--)
        {
            // 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最大的。
            tmp = a[0];
            a[0] = a[i];
            a[i] = tmp;
            // 調整a[0...i-1],使得a[0...i-1]仍然是一個最大堆。
            // 即,保證a[i-1]是a[0...i-1]中的最大值。
            maxHeapDown(a, 0, i - 1);
        }
    }

歸併排序

  1. 算法描述:
    歸併排序是建立在歸併操作上的一種有效的排序方法;其總共有兩個步驟,一是分割,二是集成;對於一個無序的序列,要遞歸的把當前序列平均分割爲兩半,然後對這個子序列在保持元素順序的同時進行集成(歸併)操作,最後將兩個排序好的子序列合併爲一個最終的排序的序列;
     -把一個無序的序列平均分爲兩部分
     -對於兩個子序列分別採用歸併排序(實質上就是遞歸)
     -將兩個排序的子序列合併爲一個最終的排序的序列;在這裏插入圖片描述
  2. 性能:
    時間複雜度:O(nlog2nnlog_2n)
    空間複雜度:O(n)
    穩定性:不穩定排序
  3. 代碼演示:
    //歸併排序,代碼來源於:https://blog.csdn.net/zpznba/article/details/83745205
    //歸併排序輔助函數
    void Merge(int* arr, int l, int q, int r)
    {
        int n = r - l + 1; //臨時數組存合併後的有序序列
        int *tmp = new int[n];
        int i = 0;
        int left = l;
        int right = q + 1;
        while (left <= q && right <= r)
            tmp[i++] = arr[left] <= arr[right] ? arr[left++] : arr[right++];
        while (left <= q)
            tmp[i++] = arr[left++];
        while (right <= r)
            tmp[i++] = arr[right++];
        for (int j = 0; j < n; ++j)
            arr[l + j] = tmp[j];
        delete[] tmp; //刪掉堆區的內存
    }
    //傳進來的是數組的首地址,以及l是左索引,r是右索引
    void MergeSort(int* arr, int l, int r)
    {
        if (l == r)
            return; //遞歸基是讓數組中的每個數單獨成爲長度爲1的區間
        int q = (l + r) / 2;
        MergeSort(arr, l, q);
        MergeSort(arr, q + 1, r);
        Merge(arr, l, q, r);
    }

總和代碼


#include <iostream>
using namespace std;

struct SortFunction
{

    //插入排序
    //傳進來的是數組的首地址,以及數組的長度
    void InsertSort(int *a, int lena)
    {
        //preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
        int preindex, tmp;
        for (int i = 1; i < lena; i++)
        {
            //tmp初始化
            tmp = a[i];
            //初始化爲i-1
            preindex = i - 1;
            //插入前的比較
            while (preindex >= 0 && a[preindex] > tmp)
            {
                a[preindex + 1] = a[preindex];
                preindex--;
            }
            //preindex現在就是那個小於等於新元素的位置,插入其後
            a[preindex + 1] = tmp;
        }
    }

    //希爾排序
    //傳進來的是數組的首地址,以及數組的長度
    void ShellSort(int *a, int lena)
    {
        //對於增量序列,採用動態,不再手動設置
        //k就是動態的增量
        for (int k = lena / 2; k > 0; k = k / 2)
        {
            //preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
            int preindex, tmp;
            for (int i = k; i < lena; i++)
            {
                //初始化tmp
                tmp = a[i];
                //初始化爲i-k
                preindex = i - k;
                //插入前的比較
                while (preindex >= 0 && a[preindex] > tmp)
                {
                    a[preindex + k] = a[preindex];
                    preindex -= k;
                }
                //preindex現在就是那個小於等於新元素的位置,插入其後
                a[preindex + k] = tmp;
            }
        }
    }

    //冒泡排序
    //傳進來的是數組的首地址,以及數組的長度
    void BubbleSort(int *a, int lena)
    {
        int tmp;
        for (int i = 0; i < lena - 1; i++)
        {
            for (int j = 0; j < lena - 1 - i; j++)
            {
                if (a[j] > a[j + 1])
                {
                    tmp = a[j + 1];
                    a[j + 1] = a[j];
                    a[j] = tmp;
                }
            }
        }
    }

    //快速排序
    //傳進來的是數組的首地址,以及開始結束的索引
    void QuickSort(int *a, int left, int right)
    {
        //初始化,pivot是樞軸
        int i = left, j = right, pivot;
        //快速排序結束標誌
        if (i >= j)
            return;

        //每一次排序開始便初始化樞軸
        pivot = a[i];
        while (i < j)
        {
            while (i < j && a[j] > pivot)
                j--;
            a[i] = a[j];
            while (i < j && a[i] <= pivot)
                i++;
            a[j] = a[i];
        }
        a[i] = pivot;
        QuickSort(a, left, i);
        QuickSort(a, i + 1, right - 1);
    }

    //選擇排序
    //傳進來的是數組的首地址,以及數組的長度
    void SelectSort(int *a, int lena)
    {
        int minindex, tmp;
        for (int i = 0; i < lena - 1; i++)
        {
            minindex = i;
            for (int j = i + 1; j < lena; j++)
            {
                if (a[j] < a[minindex])
                    minindex = j;
            }
            tmp = a[i];
            a[i] = a[minindex];
            a[minindex] = tmp;
        }
    }

    //堆排序,代碼來源於https://www.cnblogs.com/skywang12345/p/3602162.html#a42
    //輔助函數,(最大)堆的向下調整算法
    void maxHeapDown(int *a, int start, int end)
    {
        int c = start;     // 當前(current)節點的位置
        int l = 2 * c + 1; // 左(left)孩子的位置
        int tmp = a[c];    // 當前(current)節點的大小
        for (; l <= end; c = l, l = 2 * l + 1)
        {
            // "l"是左孩子,"l+1"是右孩子
            if (l < end && a[l] < a[l + 1])
                l++; // 左右兩孩子中選擇較大者,即m_heap[l+1]
            if (tmp >= a[l])
                break; // 調整結束
            else       // 交換值
            {
                a[c] = a[l];
                a[l] = tmp;
            }
        }
    }

    //傳進來的是數組的首地址,以及數組的長度
    void HeapSort(int *a, int n)
    {
        int i, tmp;

        // 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
        for (i = n / 2 - 1; i >= 0; i--)
            maxHeapDown(a, i, n - 1);

        // 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
        for (i = n - 1; i > 0; i--)
        {
            // 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最大的。
            tmp = a[0];
            a[0] = a[i];
            a[i] = tmp;
            // 調整a[0...i-1],使得a[0...i-1]仍然是一個最大堆。
            // 即,保證a[i-1]是a[0...i-1]中的最大值。
            maxHeapDown(a, 0, i - 1);
        }
    }

    //歸併排序,代碼來源於:https://blog.csdn.net/zpznba/article/details/83745205
    //歸併排序輔助函數
    void Merge(int* arr, int l, int q, int r)
    {
        int n = r - l + 1; //臨時數組存合併後的有序序列
        int *tmp = new int[n];
        int i = 0;
        int left = l;
        int right = q + 1;
        while (left <= q && right <= r)
            tmp[i++] = arr[left] <= arr[right] ? arr[left++] : arr[right++];
        while (left <= q)
            tmp[i++] = arr[left++];
        while (right <= r)
            tmp[i++] = arr[right++];
        for (int j = 0; j < n; ++j)
            arr[l + j] = tmp[j];
        delete[] tmp; //刪掉堆區的內存
    }
    //傳進來的是數組的首地址,以及l是左索引,r是右索引
    void MergeSort(int* arr, int l, int r)
    {
        if (l == r)
            return; //遞歸基是讓數組中的每個數單獨成爲長度爲1的區間
        int q = (l + r) / 2;
        MergeSort(arr, l, q);
        MergeSort(arr, q + 1, r);
        Merge(arr, l, q, r);
    }

} Sort;

int main()
{
    int a[5] = {5, 6, 2, 6, 1};
    Sort.MergeSort(a,0,4);
    for (int i = 0; i < 5; i++)
    {
        cout << a[i] << endl;
    }
    return 0;
}

創作不易,點個贊支持一下唄!
在這裏插入圖片描述


  1. 該處的穩定性是指遇到相同內容的元素後,排序之後有關相同元素的順序是否發生改變;例如原數據元素序列爲:0,1,51,52,2,30,1,5_1,5_2,2,3,我們可以發現有兩個5,而經過某排序後,序列爲:0,1,2,3,51,520,1,2,3,5_1,5_2的爲穩定排序,否則爲不穩定排序 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章