1. 程式人生 > >11、【演算法】排序演算法總結

11、【演算法】排序演算法總結

常見排序演算法總結

一、氣泡排序
1、定義

    氣泡排序是一種比較簡單的排序演算法,它會遍歷若干次要排序的數列,每次便利時,它都會從前往後依次的比較兩個相鄰的數的大小;如果前者比後者大,則交換它們的位置。
    這樣一次遍歷之後,最大的元素就在數列的末尾了。採用相同的方法在此遍歷時,第二大的元素就被排列在最大元素之前,重複此操作,直到整個數列都有序為止。

氣泡排序的時間複雜度為 :O(N2)-穩定

2、實現(C++)
void bubbleSort(int *arr, int len)
{
    int temp;	  
    int flag;    //標誌位
//注意i和j的取值範圍是重點 for(int i = len-1;i > 0; i++) { flag = 0;//初始化標誌為為0 //將arr[0……i]中最大的資料放在末尾 for(int j = 0; j < i; j++) { if(arr[j] > arr[j+1]) { //交換兩個數 temp = arr[j]; arr[j] = arr[j+1]; arr[
j+1] = temp; //若發生交換,則將標誌為置為1 flag = 1; } } //若沒有發生交換,則說明數列已有序 if(flag == 0) break; } }
二、快速排序
1、定義

    快速排序(quick sort)使用分治法策略
    它的基本思想是:選擇一個基準數,通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都小,然後,再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個數列變成有序數列。

快速排序的流程:
    (1)從數列中選擇一個基準值
    (2)將所有比基準值小的資料放在基準值前面,所有比基準值大的資料放在基準值後面(相同的資料可以放在一邊),在這個分割槽結束後,該基準值就處於數列的中間位置了。
    (3)遞迴的把基準值前面的子數列和基準值後面的子數列進行快速排序。

快速排序的時間複雜度:最壞的情況下是O(n2),平均情況是O(nlog2n)-不穩定

2、實現(C++)
//2、快速排序
void quickSort(int *arr, int left, int right)
{
    if(left < right)
    {
        int midTemp;//基準值

        int i = left;
        int j = right;
        midTemp = arr[0];    //將arr[0]作為基準值
        while(i < j)
        {
            while(i < j && arr[j] > midTemp)
                j--;//從後往前找第一個小於midTemp的值
            if(i < j)
                a[i++] = a[j];
            while(i < j && arr[i] < midTemp)
                i++;//從前往後找第一個大於midTemp的值
            if(i < j)
                arr[j--] = arr[i];
        }
        arr[i] = midTemp;
        //遞迴呼叫
        quickSort(arr, left, i-1);
        quickSort(arr, i+1, right);
    }
}
三、直接插入排序
1、定義

    直接插入排序(Straight insertion Sort)的基本思想是:把n個待排序的元素看成一個有序表和一個無序表,開始時有序表中只包含一個元素,無序表中包含n-1個元素,排序過程中,每次從無序表中取出第一個元素,將它插入到有序表中的適當位置,使之成為一個新的有序表,重複n-1次可完成整個排序過程。
    直接插入排序的時間複雜度:O(n2)-穩定

2、實現(C++)
//3、直接插入排序
void insertSort(int *arr, int len)
{
    for(int i = 1; i < len; i++)
    {
        //為arr[i]在前面arr[0 ... i-1]有序區間中找到一個合適的位置
        for(int j = i - 1; j >= 0; j--)
        {
            if(arr[j] < arr[i])
                break;
        }

        //若找到一個合適的位置
        if(j != i -1)
        {
            //將比arr[i]大的資料向後移
            int temp = arr[i];
            for(int k = i - 1; k > j; k--)
                arr[k+1] = arr[k];
            //將arr[i]放到正確的位置上
            arr[k+1] = temp;
        }
    }
}
四、希爾排序
1、定義

    希爾排序(Shell Sort)是插入排序的一種,它是針對直接插入排序演算法的改進。該方法又稱縮小增量排序

    希爾排序實質上是一種分組插入方法。它的基本思想是:對於n個待排序的數列,取一個小於n的整數gap(gap被稱為步長)將待排序元素分成若干個組子序列,所有距離為gap的倍數的記錄放在同一個組中;然後,對各組內的元素進行直接插入排序。 這一趟排序完成之後,每一個組的元素都是有序的。然後減小gap的值,並重復執行上述的分組和排序。重複這樣的操作,當gap=1時,整個數列就是有序的。

    希爾排序時間複雜度:希爾排序的時間複雜度與增量(即,步長gap)的選取有關。例如,當增量為1時,希爾排序退化成了直接插入排序,此時的時間複雜度為O(N2),而Hibbard增量的希爾排序的時間複雜度為O(N3/2)。-不穩定

Hbbard增量序列: h = 1,3,7,,2^k-1^
hbbard增量序列的特點是:相鄰增量沒有公因子,最壞的執行時間為O(N^3/2^)
2、實現(C++)
/*
 * 希爾排序
 * 引數說明:
 *     arr -- 待排序的陣列
 *     n -- 陣列的長度
 */
void shellSort(int* arr, int n)
{
    // gap為步長,每次減為原來的一半。
    for (int gap = n / 2; gap > 0; gap /= 2)
    {
        // 共gap個組,對每一組都執行直接插入排序
        for (int i = 0 ;i < gap; i++)
        {
            for (int j = i + gap; j < n; j += gap)
            {
                // 如果a[j] < a[j-gap],則尋找a[j]位置,並將後面資料的位置都後移。
                if (arr[j] < arr[j - gap])
                {
                    int tmp = arr[j];
                    int k = j - gap;
                    while (k >= 0 && a[k] > tmp)
                    {
                        arr[k + gap] = arr[k];
                        k -= gap;
                    }
                    arr[k + gap] = tmp;
                }
            }
        }
    }
}
五、選擇排序
1、定義

    選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的基本思想是:首先在未排序的數列中找到最小(or最大)元素,然後將其存放到數列的起始位置;接著,再從剩餘未排序的元素中繼續尋找最小(or最大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

    選擇排序時間複雜度:選擇排序的時間複雜度是O(N2)。
    假設被排序的數列中有N個數。遍歷一趟的時間複雜度是O(N),需要遍歷多少次呢?N-1!因此,選擇排序的時間複雜度是O(N2)。-不穩定

2、實現(C++)
/*
 * 選擇排序
 * 引數說明:
 *     a -- 待排序的陣列
 *     n -- 陣列的長度
 */
void selectSort(int* a, int n)
{
    int min;    // 無序區中最小元素位置

    for(int i=0; i<n; i++)//有序區的末尾位置
    {
        min=i;

        // 找出"a[i+1] ... a[n]"之間的最小元素,並賦值給min。
        for(j=i+1; j<n; j++)// 無序區的起始位置
        {
            if(a[j] < a[min])
                min=j;
        }

        // 若min!=i,則交換 a[i] 和 a[min]。
        // 交換之後,保證了a[0] ... a[i] 之間的元素是有序的。
        if(min != i)
        {
            int tmp = a[i];
            a[i] = a[min];
            a[min] = tmp;
        }
    }
}
六、堆排序
1、定義

    堆排序(Heap Sort)是指利用堆這種資料結構所設計的一種排序演算法。我們知道,堆分為"最大堆"和"最小堆"。最大堆通常被用來進行"升序"排序,而最小堆通常被用來進行"降序"排序。

    鑑於最大堆和最小堆是對稱關係,理解其中一種即可。本文將對最大堆實現的升序排序進行詳細說明。

    最大堆進行升序排序的基本思想:
      ① 初始化堆:將數列a[1…n]構造成最大堆。
      ② 交換資料:將a[1]和a[n]交換,使a[n]是a[1…n]中的最大值;然後將a[1…n-1]重新調整為最大堆。 接著,將a[1]和a[n-1]交換,使a[n-1]是a[1…n-1]中的最大值;然後將a[1…n-2]重新調整為最大值。 依次類推,直到整個數列都是有序的。

陣列實現的二叉堆的性質:
    在第一個元素的索引為 0 的情形中:
    性質一:索引為i的左孩子的索引是 (2*i+1);
    性質二:索引為i的左孩子的索引是 (2*i+2);
    性質三:索引為i的父結點的索引是 floor((i-1)/2);

堆排序的時間複雜度:O(n*log2n) - 不穩定

2、實現(C++)
 /*
 * 最大堆實現升序排序
 * 引數說明:
 *     a -- 待排序的陣列
 *     start -- 被下調節點的起始位置(一般為0,表示從第1個開始)
 *     end   -- 截至範圍(一般為陣列中最後一個元素的索引)
 */
void maxheap_down(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;
        }
    }
}
七、歸併排序
1、定義

    將兩個的有序數列合併成一個有序數列,我們稱之為"歸併"。歸併排序(Merge Sort)就是利用歸併思想對數列進行排序。根據具體的實現,歸併排序包括"從上往下"和"從下往上"2種方式。
    1、從下往上的歸併排序:將待排序的數列分成若干個長度為1的子數列,然後將這些數列兩兩合併;得到若干個長度為2的有序數列,再將這些數列兩兩合併;得到若干個長度為4的有序數列,再將它們兩兩合併;直接合併成一個數列為止。這樣就得到了我們想要的排序結果。

    2、從上往下的歸併排序:它與"從下往上"在排序上是反方向的。它基本包括3步:
    ① 分解 – 將當前區間一分為二,即求分裂點 mid = (low + high)/2;
    ② 求解 – 遞迴地對兩個子區間a[low…mid] 和 a[mid+1…high]進行歸併排序。遞迴的終結條件是子區間長度為1。
    ③ 合併 – 將已排序的兩個子區間a[low…mid]和 a[mid+1…high]歸併為一個有序的區間a[low…high]。

歸併排序時間複雜度:歸併排序的時間複雜度是O(Nlog2N)。
    假設被排序的數列中有N個數。遍歷一趟的時間複雜度是O(N),需要遍歷多少次呢?
    歸併排序的形式就是一棵二叉樹,它需要遍歷的次數就是二叉樹的深度,而根據完全二叉樹的可以得出它的時間複雜度是O(N
log2N)。- 穩定

2、實現(C++)
/*
 * 將一個數組中的兩個相鄰有序區間合併成一個
 *
 * 引數說明:
 *     a -- 包含兩個有序區間的陣列
 *     start -- 第1個有序區間的起始地址。
 *     mid   -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
 *     end   -- 第2個有序區間的結束地址。
 */
void merge(int* a, int start, int mid, int end)
{
    int *tmp = new int[end-start+1];    // tmp是彙總2個有序區的臨時區域
    int i = start;            // 第1個有序區的索引
    int j = mid + 1;        // 第2個有序區的索引
    int k = 0;                // 臨時區域的索引

    while(i <= mid && j <= end)
    {
        if (a[i] <= a[j])
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    }

    while(i <= mid)
        tmp[k++] = a[i++];

    while(j <= end)
        tmp[k++] = a[j++];

    // 將排序後的元素,全部都整合到陣列a中。
    for (i = 0; i < k; i++)
        a[start + i] = tmp[i];

    delete[] tmp;
}

/*
 * 歸併排序(從上往下)
 *
 * 引數說明:
 *     a -- 待排序的陣列
 *     start -- 陣列的起始地址
 *     endi -- 陣列的結束地址
 */
void mergeSortUp2Down(int* a, int start, int end)
{
    if(a==NULL || start >= end)
        return ;

    int mid = (end + start)/2;
    mergeSortUp2Down(a, start, mid); // 遞迴排序a[start...mid]
    mergeSortUp2Down(a, mid+1, end); // 遞迴排序a[mid+1...end]

    // a[start...mid] 和 a[mid...end]是兩個有序空間,
    // 將它們排序成一個有序空間a[start...end]
    merge(a, start, mid, end);
}

/*
 * 對陣列a做若干次合併:陣列a的總長度為len,將它分為若干個長度為gap的子陣列;
 *             將"每2個相鄰的子陣列" 進行合併排序。
 *
 * 引數說明:
 *     a -- 待排序的陣列
 *     len -- 陣列的長度
 *     gap -- 子陣列的長度
 */
void mergeGroups(int* a, int len, int gap)
{
    int i;
    int twolen = 2 * gap;    // 兩個相鄰的子陣列的長度

    // 將"每2個相鄰的子陣列" 進行合併排序。
    for(i = 0; i+2*gap-1 < len; i+=(2*gap))
    {
        merge(a, i, i+gap-1, i+2*gap-1);
    }

    // 若 i+gap-1 < len-1,則剩餘一個子陣列沒有配對。
    // 將該子數組合併到已排序的陣列中。
    if ( i+gap-1 < len-1)
    {
        merge(a, i, i + gap - 1, len - 1);
    }
}

/*
 * 歸併排序(從下往上)
 *
 * 引數說明:
 *     a -- 待排序的陣列
 *     len -- 陣列的長度
 */
void mergeSortDown2Up(int* a, int len)
{
    int n;

    if (a==NULL || len<=0)
        return ;

    for(n = 1; n < len; n*=2)
        mergeGroups(a, len, n);
}
八、桶排序
1、定義

    桶排序(Bucket Sort)的原理很簡單,它是將陣列分到有限數量的桶裡

    假設待排序的陣列a中共有N個整數,並且已知陣列a中資料的範圍[0, MAX)。在桶排序時,建立容量為MAX的桶陣列r,並將桶陣列元素都初始化為0;將容量為MAX的桶陣列中的每一個單元都看作一個"桶"。
    在排序時,逐個遍歷陣列a,將陣列a的值,作為"桶陣列r"的下標。當a中資料被讀取時,就將桶的值加1。例如,讀取到陣列a[3]=5,則將r[5]的值+1。

桶排序的時間複雜度
    對於N個待排資料,M個桶,平均每個桶[N/M]個數據的桶排序平均時間複雜度為:
O(N)+O(M*(N/M)log2(N/M))=O(N+N(log2N-log2M))=O(N+Nlog2N-Nlog2M)
    當N=M時,即極限情況下每個桶只有一個數據時。桶排序的最好效率能夠達到O(N)。- 穩定

2、實現(C++)
/*
 * 桶排序
 * 引數說明:
 *     a -- 待排序陣列
 *     n -- 陣列a的長度
 *     max -- 陣列a中最大值的範圍
 */
void bucketSort(int a[], int n, int max)
{
    int i,j;
    int buckets[max];

    // 將buckets中的所有資料都初始化為0。
    memset(buckets, 0, max*sizeof(int));

    // 1. 計數
    for(i = 0; i < n; i++) 
        buckets[a[i]]++; 

    // 2. 排序
    for (i = 0, j = 0; i < max; i++) 
    {
        while( (buckets[i]--) >0 )
            a[j++] = i;
    }
}