淺析十大常見排序(含C++代碼)


首先聲明一下,本文只對十種排序算法做簡單總結,並參照一些資料給出自己的代碼實現,並沒有對某種算法理論講解,更詳細的

了解可以參考以下資料:

1、《data structure and algorithm analysis in c 》

2、《大話數據結構》

3、http://blog.csdn.net/morewindows/article/details/7961256

4、http://www.cs.usfca.edu/~galles/visualization/Algorithms.html 

5、https://www.runoob.com/w3cnote/ten-sorting-algorithm.html(導航頁,詳細分析十大排序,使用多種語言)

 

一、冒泡排序

基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換

冒泡排序時間復雜度最好的情況為O(n),最壞的情況是O(n^2) 

改進思路1:設置標志位,明顯如果有一趟沒有發生交換(flag = false),說明排序已經完成

改進思路2:記錄一輪下來標記的最后位置,下次從頭部遍歷到這個位置就Ok

 

二、直接插入排序

將一個記錄插入到已經排好序的有序表中, 從而得到一個新的,記錄數增1的有序表 

時間復雜度也為O(n^2), 比冒泡法和選擇排序的性能要更好一些

 

三、簡單選擇排序

通過n-i次關鍵字之間的比較,從n-i+1 個記錄中選擇關鍵字最小的記錄,並和第i(1<=i<=n)個記錄交換之

 盡管與冒泡排序同為O(n^2),但簡單選擇排序的性能要略優於冒泡排序

 

四、希爾排序

先將整個待排元素序列分割成若干子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然后依次縮減增量再進行排

,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序(增量為1)。其時間復雜度為O(n^3/2),要好於直接

插入排序的O(n^2)

 

五、歸並排序

假設初始序列含有n個記錄,則可以看成n個有序的子序列,每個子序列的長度為1,然后兩兩歸並,得到(不小於n/2的最小整數)個長度為2

或1的有序子序列,再兩兩歸並,...如此重復,直至得到一個長度為n的有序序列為止,這種排序方法稱為2路歸並排序。 時間復雜度為

O(nlogn),空間復雜度為O(n+logn),如果非遞歸實現歸並,則避免了遞歸時深度為logn的棧空間 空間復雜度為O(n)

 

六、堆排序

堆是具有下列性質的完全二叉樹:每個節點的值都大於或等於其左右孩子節點的值,稱為大頂堆;或者每個節點的值都小於或等於其左

右孩子節點的值,稱為小頂堆。


堆排序就是利用堆進行排序的方法.基本思想是:將待排序的序列構造成一個大頂堆.此時,整個序列的最大值就是堆頂 的根結點.將它移

走(其實就是將其與堆數組的末尾元素交換, 此時末尾元素就是最大值),然后將剩余的n-1個序列重新構造成一個堆,這樣就會得到n個元

素的次大值.如此反復執行,便能得到一個有序序列了。 時間復雜度為 O(nlogn),好於冒泡,簡單選擇,直接插入的O(n^2)

 

七、快速排序

通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。時間復雜度為O(nlogn)

下文沒有給出快速排序的實現,參考以前的文章

 

 

代碼實現:(含3種swap交換函數,6個排序算法,不含快速排序)

 

 C++ Code 
     

#include<iostream>
using namespace std;

void swap1(int *left, int *right)
{
    int temp = *left;
    *left = *right;
    *right = temp;
}

void swap2(int &left, int &right)
{
    int temp = left;
    left = right;
    right = left;
}

void swap3(int &left, int &right)
{
    if (&left != &right) 
    {
        left ^= right;
        right ^= left;
        left ^= right;
    }
}

/*****************************************************************/
/* 冒泡排序時間復雜度最好的情況為O(n),最壞的情況是O(n^2)
* 基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換 */

void BubbleSort1(int arr[], int num)
{
    int i, j;
    for (i = 0; i < num; i++)
    {
        for (j = 1; j < num - i; j++)
        {
            if (arr[j - 1] > arr[j])
                swap1(&arr[j - 1], &arr[j]);
        }
    }
}

// 改進思路:設置標志位,明顯如果有一趟沒有發生交換(flag = flase),說明排序已經完成.
void BubbleSort2(int arr[], int num)
{
    int k = num;
    int j;
    bool flag = true;
    while (flag)
    {
        flag = false;
        for (j = 1; j < k; j++)
        {
            if (arr[j - 1] > arr[j])
            {
                swap1(&arr[j - 1], &arr[j]);
                flag = true;
            }
        }
        k--;
    }
}
//改進思路:記錄一輪下來標記的最后位置,下次從頭部遍歷到這個位置就Ok
void BubbleSort3(int arr[], int num)
{
    int k, j;
    int flag = num;
    while (flag > 0)
    {
        k = flag;
        flag = 0;
        for (j = 1; j < k; j++)
        {
            if (arr[j - 1] > arr[j])
            {
                swap1(&arr[j - 1], &arr[j]);
                flag = j;
            }
        }
    }
}
/*************************************************************************/

/**************************************************************************/
/*插入排序: 將一個記錄插入到已經排好序的有序表中, 從而得到一個新的,記錄數增1的有序表
* 時間復雜度也為O(n^2), 比冒泡法和選擇排序的性能要更好一些 */

void InsertionSort(int arr[], int num)
{
    int temp;
    int i, j;
    for (i = 1; i < num; i++)
    {
        temp = arr[i];
        for (j = i; j > 0 && arr[j - 1] > temp; j--)
            arr[j] = arr[j - 1];
        arr[j] = temp;
    }
}

/****************************************************************************/

/*希爾排序:先將整個待排元素序列分割成若干子序列(由相隔某個“增量”的元素組成的)分別進行
直接插入排序,然后依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,
再對全體元素進行一次直接插入排序(增量為1)。其時間復雜度為O(n^3/2),要好於直接插入排序的O(n^2) */
void ShellSort(int *arr, int N)
{
    int i, j, increment;
    int tmp;
    for (increment = N / 2; increment > 0; increment /= 2)
    {
        for (i = increment; i < N; i++)
        {
            tmp = arr[i];
            for (j = i; j >= increment; j -= increment)
            {
                if (arr[j - increment] > tmp)
                    arr[j] = arr[j - increment];
                else
                    break;
            }
            arr[j] = tmp;
        }

    }
}

/**************************************************************************/

/* 簡單選擇排序(simple selection sort) 就是通過n-i次關鍵字之間的比較,從n-i+1
* 個記錄中選擇關鍵字最小的記錄,並和第i(1<=i<=n)個記錄交換之
* 盡管與冒泡排序同為O(n^2),但簡單選擇排序的性能要略優於冒泡排序 */

void SelectSort(int arr[], int num)
{
    int i, j, Mindex;
    for (i = 0; i < num; i++)
    {
        Mindex = i;
        for (j = i + 1; j < num; j++)
        {
            if (arr[j] < arr[Mindex])
                Mindex = j;
        }

        swap1(&arr[i], &arr[Mindex]);
    }
}

/********************************************************************************/
/*假設初始序列含有n個記錄,則可以看成n個有序的子序列,每個子序列的長度為1,然后
* 兩兩歸並,得到(不小於n/2的最小整數)個長度為2或1的有序子序列,再兩兩歸並,...
* 如此重復,直至得到一個長度為n的有序序列為止,這種排序方法稱為2路歸並排序
* 時間復雜度為O(nlogn),空間復雜度為O(n+logn),如果非遞歸實現歸並,則避免了遞歸時深度為logn的棧空間
* 空間復雜度為O(n) */


/*lpos is the start of left half, rpos is the start of right half*/
void merge(int a[], int tmp_array[], int lpos, int rpos, int rightn)
{
    int i, leftn, num_elements, tmpos;

    leftn = rpos - 1;
    tmpos = lpos;
    num_elements = rightn - lpos + 1;

    /*main loop*/
    while (lpos <= leftn && rpos <= rightn)
        if (a[lpos] <= a[rpos])
            tmp_array[tmpos++] = a[lpos++];
        else
            tmp_array[tmpos++] = a[rpos++];

    while (lpos <= leftn) /*copy rest of the first part*/
        tmp_array[tmpos++] = a[lpos++];
    while (rpos <= rightn) /*copy rest of the second part*/
        tmp_array[tmpos++] = a[rpos++];

    /*copy array back*/
    for (i = 0; i < num_elements; i++, rightn--)
        a[rightn] = tmp_array[rightn];
}


void msort(int a[], int tmp_array[], int left, int right)
{
    int center;

    if (left < right)
    {
        center = (right + left) / 2;
        msort(a, tmp_array, left, center);
        msort(a, tmp_array, center + 1, right);
        merge(a, tmp_array, left, center + 1, right);
    }
}



void merge_sort(int a[], int n)
{
    int *tmp_array;
    tmp_array = (int *)malloc(n * sizeof(int));

    if (tmp_array != NULL)
    {
        msort(a, tmp_array, 0, n - 1);
        free(tmp_array);
    }

    else
        printf("No space for tmp array!\n");
}

/************************************************************************************/
/* 堆是具有下列性質的完全二叉樹:每個節點的值都大於或等於其左右孩子節點的值,稱為大頂堆;
* 或者每個節點的值都小於或等於其左右孩子節點的值,稱為小頂堆*/

/*堆排序就是利用堆進行排序的方法.基本思想是:將待排序的序列構造成一個大頂堆.此時,整個序列的最大值就是堆頂
* 的根結點.將它移走(其實就是將其與堆數組的末尾元素交換, 此時末尾元素就是最大值),然后將剩余的n-1個序列重新
* 構造成一個堆,這樣就會得到n個元素的次大值.如此反復執行,便能得到一個有序序列了
*/
/* 時間復雜度為 O(nlogn),好於冒泡,簡單選擇,直接插入的O(n^2) */

// 構造大頂堆
#define leftChild(i) (2*(i) + 1)

void percDown(int *arr, int i, int N)
{
    int tmp, child;
    for (tmp = arr[i]; leftChild(i) < N; i = child)
    {
        child = leftChild(i);
        if (child != N - 1 && arr[child + 1] > arr[child])
            child++;
        if (arr[child] > tmp)
            arr[i] = arr[child];
        else
            break;
    }
    arr[i] = tmp;
}

void HeapSort(int *arr, int N)
{
    int i;
    for (i = N / 2; i >= 0; i--)
        percDown(arr, i, N);
    for (i = N - 1; i > 0; i--)
    {
        swap1(&arr[0], &arr[i]);
        percDown(arr, 0, i);
    }
}


int main(void)
{
    int arr[] = { 9, 2, 5, 8, 3, 4, 7, 1, 6, 10};
    HeapSort(arr, 10);
    for (int i = 0; i < 10; i++)         cout << arr[i] << ' ';     cout << endl;     return 0; }

 


 

 

注:上述7種都是比較排序,下面3種都是非比較排序,理論上可以達到O(n),比比較排序要快,但是這3種都是有其應用背景才能發揮作用的,否則適得其反。

 

八:計數排序

計數排序(Counting sort)是一種穩定的排序算法。計數排序使用一個額外的數組C,其中第i個元素是待排序數組A中值等於i的元素的個數。然后根據數組C來將A中的元素排到正確的位置。

 

算法的步驟如下:

  1. 找出待排序的數組中最大和最小的元素
  2. 統計數組中每個值為i的元素出現的次數,存入數組C的第i
  3. 對所有的計數累加(從C中的位置為1的元素開始,每一項和前一項相加)
  4. 反向填充目標數組:將每個元素i放在新數組的第C(i)項,每放一個元素就將C(i)減去1

 

由於用來計數的數組C的長度取決於待排序數組中數據的范圍(等於待排序數組的最大值與最小值的差加上1),這使得計數排序對於數據范圍很大的數組,需要大量時間和內存。

 

九:桶排序

桶排序 (Bucket sort)或所謂的箱排序,是一個排序算法,工作的原理是將數組分到有限數量的桶子里。每個桶子再個別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序)

桶排序以下列程序進行:

  1. 設置一個定量的數組當作空桶子。
  2. 尋訪串行,並且把項目一個一個放到對應的桶子去。(hash)
  3. 對每個不是空的桶子進行排序。
  4. 從不是空的桶子里把項目再放回原來的串行中。

 

十:基數排序

基數排序(英語:Radix sort)是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然后按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是只能使用於整數。

它是這樣實現的:將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。然后,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以后, 數列就變成一個有序序列。

基數排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由鍵值的最右邊開始,而MSD則相反,由鍵值的最左邊開始。

 

 

 C++ Code 
#include<stdio.h>
#include<string.h>
#include<algorithm>

using namespace std;

/*****************計數排序*******************************/
void  CountSort(int *arr, int num)
{
    int mindata = arr[0];
    int maxdata = arr[0];
    for (int i = 1; i < num; i++)
    {
        if (arr[i] > maxdata)
            maxdata = arr[i];
        if (arr[i] < mindata)
            mindata = arr[i];
    }
    
    int size = maxdata - mindata + 1;
    //申請空間並初始化為0
    int *pCount = (int *)malloc(sizeof(int) * size);
    memset(pCount, 0, sizeof(int)*size);

    //記錄排序計數,每出現一次在對應位置加1
    for (int i = 0; i < num; i++)
        ++pCount[arr[i]-mindata];

    //確定不比該位置大的數據個數
    for (int i = 1; i < size; i++)
        pCount[i] += pCount[i - 1]; //加上前一個的計數

    int *pSort = (int *)malloc(sizeof(int) * num);
    memset((char*)pSort, 0, sizeof(int) * num);

    //從末尾開始拷貝是為了重復數據首先出現的排在前面,即穩定排序
    for (int i = num - 1; i >= 0; i--)
    {
        //包含自己需要減1,重復數據循環回來也需要減1
        --pCount[arr[i]-mindata];
        pSort[pCount[arr[i]-mindata]] = arr[i];
    }
    //拷貝到原數組
    for (int i = 0; i < num; i++)
        arr[i] = pSort[i];

    free(pCount);
    free(pSort);

}

/*****************桶排序*****************************/
struct Node
{
    int key_;
    struct Node *next_;
    Node(int key)
    {
        key_ = key;
        next_ = NULL;
    }
};

#define bucket_size 10 //與數組元素個數相等

void buck_sort(int arr[], int num)
{
    Node *bucket_table[bucket_size];
    memset(bucket_table, 0, sizeof(bucket_table));

    //建立每一個頭節點,頭節點的key保存當前桶的數據量
    for (int i = 0; i < bucket_size; i++)
        bucket_table[i] = new Node(0);
    
    int maxValue = arr[0];
    for (int i = 1; i < num; i++)
    {
        if (arr[i] > maxValue)
            maxValue = arr[i];
    }

    for (int j = 0; j < num; j++)
    {
        Node *ptr = new Node(arr[j]);//其余節點的key保存數據

        //映射函數計算桶號
        // index = (value * number_of_elements) / (maxvalue + 1)
        int index = (arr[j] * bucket_size) / (maxValue + 1);
        Node *head = bucket_table[index];
        //該桶還沒有數據
        if (head->key_ == 0)
        {
            bucket_table[index]->next_ = ptr;
            (bucket_table[index]->key_)++;

        }
        else
        {
            //找到合適的位置插入
            while (head->next_ != NULL && head->next_->key_ <= ptr->key_)
                head = head->next_;
            ptr->next_ = head->next_;
            head->next_ = ptr;
            (bucket_table[index]->key_)++;
        }

    }

    //將桶中的數據拷貝回原數組
    int m, n;
    for (m = 0, n = 0;  n < num && m < bucket_size; m++, n++)
    {
        Node *ptr = bucket_table[m]->next_;
        while (ptr != NULL)
        {
            arr[n] = ptr->key_;
            ptr = ptr->next_;
            n++;
        }
        n--;
    }

    //釋放分配的動態空間
    for (m = 0; m < bucket_size; m++)
    {
        Node *ptr = bucket_table[m];
        Node *tmp = NULL;
        while (ptr != NULL)
        {
            tmp = ptr->next_;
            delete ptr;
            ptr = tmp;
        }

    }

}


/****************************************************/



/******************** 基數排序LSD*********************/

void base_sort_ISD(int *arr, int num)
{
    Node *buck[10]; // 創建一個鏈表數組
    Node *tail[10]; //保存每條鏈表尾節點指針集合,
    //這樣插入buck數組時就不用每次遍歷到末尾
    int  i, MaxValue, kth, high, low;
    Node *ptr;
    for(MaxValue = arr[0], i = 1; i < num; i++)
        MaxValue = max(MaxValue, arr[i]);

    memset(buck, 0, sizeof(buck));
    memset(tail, 0, sizeof(buck));

    for(low = 1; high = low * 10, low < MaxValue; low *= 10)
    {
        //只要沒排好序就一直排序
        for(i = 0; i < num; i++)
        {
            //往桶里放
            kth = (arr[i] % high) / low;//取出數據的某一位,作為桶的索引
            ptr = new Node(arr[i]); //創建新節點

            //接到末尾
            if (buck[kth] != NULL)
            {
                tail[kth]->next_ = ptr;
                tail[kth] = ptr;
            }
            else
            {
                buck[kth] = ptr;
                tail[kth] = ptr;
            }

        }
        //把桶中的數據放回數組中(同條鏈表是從頭到尾)
        for (kth = 0, i = 0; kth < num; i++)
        {
            while (buck[i] != NULL)
            {
                arr[kth++] = buck[i]->key_;
                ptr = buck[i];
                buck[i] = buck[i]->next_;
                delete ptr;
            }
        }

        memset(tail, 0, sizeof(buck));

    }

}
/**************************************************************/

int main(void)
{

    int arr1[] = {10, 15, 11, 20, 15, 18, 19, 12, 14, 17};
    int size1 = sizeof(arr1) / sizeof(arr1[0]);
    CountSort(arr1, size1);
    for (int i = 0; i < size1; i++)
        printf("%d ", arr1[i]);
    printf("\n");

    int arr2[] = {54, 8, 216, 512, 27, 729, 0, 1, 343, 125};
    int size2 = sizeof(arr2) / sizeof(arr2[0]);     
  base_sort_ISD(arr2, size2);

  for (int i = 0; i < size2; i++)
    printf("%d ", arr2[i]);
  printf("\n");

  int arr3[] = {49, 38, 65, 97, 76, 13, 27, 49, 132, 134};

  int size3 = sizeof(arr3) / sizeof(arr3[0]);
  buck_sort(arr3, size3);

  for (int i = 0; i < size3; i++)
    printf("%d ", arr3[i]);
  printf("\n");

  return 0;
}

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM