七種常見的陣列排序演算法整理(C語言版本)

weixin_34107955發表於2019-01-08

~~~C語言版本~~~

  • 氣泡排序
  • 選擇排序
  • 直接插入排序
  • 二分插入排序
  • 希爾排序
  • 快速排序
  • 堆排序
#define EXCHANGE(num1, num2)  { num1 = num1 ^ num2;\
num2 = num1 ^ num2;\
num1 = num1 ^ num2;}

排序演算法是否穩定:相同元素的相對在排序前後是否會發生改變,如果會,就是不穩定的,否則就是穩定的。
一.氣泡排序
氣泡排序原理很容易理解,就是重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,順序不對就交換,直至沒有相鄰元素需要交換,也就是排序完成。
這個演算法的名字由來是因為越大的元素會經由交換慢慢“浮”到數列的頂端(升序或降序排列),就如同碳酸飲料中二氧化碳的氣泡最終會上浮到頂端一樣,故名“氣泡排序”。

  • 氣泡排序是一種穩定排序演算法。
  • 時間複雜度:最好情況(初始情況就是正序)下是o(n),平均情況是o(n²)
void buddleSort(int num[],int count)
{
    for (int i = 0; i < count - 1; i++) {

        for (int j = 0; j < count - i - 1; j++) {

            if (num[j] > num[j + 1]) EXCHANGE(num[j], num[j + 1])
        }
    }
}

二、選擇排序
選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理是每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到全部待排序的資料元素排完。
選擇排序的交換操作介於 0 和 (n - 1) 次之間。選擇排序的比較操作為 n (n - 1) / 2 次之間。選擇排序的賦值操作介於 0 和 3 (n - 1) 次之間。
比較次數O(n²),比較次數與關鍵字的初始狀態無關,總的比較次數N=(n-1)+(n-2)+...+1=n*(n-1)/2。交換次數O(n),最好情況是,已經有序,交換0次;最壞情況交換n-1次,逆序交換n/2次。交換次數比氣泡排序少多了,由於交換所需CPU時間比比較所需的CPU時間多,n值較小時,選擇排序比氣泡排序快

  • 選擇排序是不穩定的排序方法。
  • 時間複雜度:最好和平均情況下都是O(n²)
void selectSort(int num[],int count)
{
    for (int i = 0; i < count; i++) {

        int min = i;

        for (int j = i; j < count; j++) {
            
            if (num[j] < num[min])  min = j;
        }

        if (i != min)   EXCHANGE(num[i], num[min]);//可以看出,最多交換count - 1次
    }
}

三、直接插入排序
插入排序的基本操作就是將一個資料插入到已經排好序的有序資料中,從而得到一個新的、個數加一的有序資料,演算法適用於少量資料的排序,
插入排序的基本思想是:每步將一個待排序的記錄,按其關鍵碼值的大小插入前面已經排序的檔案中適當位置上,直到全部插入完為止

  • 直接插入排序是穩定的排序演算法。
  • 時間複雜度:最好情況(初始情況就是正序)下是o(n),平均情況是o(n²)
void insertSort2(int num[],int count)
{
    int i,j;
    
    for (i = 1; i < count; i++) {
        
        if (num[i] < num[i - 1]) {
            
            int temp = num[i];
            
            for (j = i; j > 0; j--) {
                
                if (num[j - 1] > temp) num[j] = num[j - 1];
                
                else break;
            }
            
            num[j] = temp;
        }
    }
}

四、二分插入排序

由於在插入排序過程中,待插入資料左邊的序列總是有序的,針對有序序列,就可以用二分法去插入資料了,也就是二分插入排序法。適用於資料量比較大的情況。
二分插入排序的演算法思想:
演算法的基本過程:
(1)計算 0 ~ i-1 的中間點,用 i 索引處的元素與中間值進行比較,如果 i 索引處的元素大,說明要插入的這個元素應該在中間值和剛加入i索引之間,反之,就是在剛開始的位置 到中間值的位置,這樣很簡單的完成了折半;
(2)在相應的半個範圍裡面找插入的位置時,不斷的用(1)步驟縮小範圍,不停的折半,範圍依次縮小為 1/2 1/4 1/8 .......快速的確定出第 i 個元素要插在什麼地方;
(3)確定位置之後,將整個序列後移,並將元素插入到相應位置。

  • 二分插入排序是穩定的排序演算法。
  • 時間複雜度:最好情況(剛好插入位置為二分位置)下是o(nlogn),平均情況和最壞情況是o(n²)
void insertSortBinary(int num[],int count)
{
    int i,j;
    
    for (i = 1; i < count; i++) {
        
        if (num[i] < num[i - 1]) {
            
            int temp = num[i];
            
            int left = 0,right = i - 1;
            
            while (left <= right) {
                
                int mid = (left + right)/2;
                
                if (num[mid] < temp) left = mid + 1;
                    
                else right = mid - 1;
            }
            //只是比較次數變少了,交換次數還是一樣的
            for (j = i; j > left; j--) {
                
                num[j] = num[j - 1];
            }
            
            num[left] = temp;
        }
    }
}

五、希爾(插入)排序
希爾排序(Shell's Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序演算法的一種更高效的改進版本。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個檔案恰被分成一組,排序完成。

  • 希爾排序是非穩定排序演算法。
  • 時間複雜度:O(n^(1.3—2))
void shellSort(int num[],int count)
{
    int shellNum = 2;
    int gap = round(count/shellNum);

    while (gap > 0) {
        for (int i = gap; i < count; i++) {
            int temp = num[i];
            int j = i;
            while (j >= gap && num[j - gap] > temp) {
                num[j] = num[j - gap];
                j = j - gap;
            }
            num[j] = temp;
        }
        gap = round(gap/shellNum);
    }
}

六、快速排序
快速排序(Quicksort)是對氣泡排序的一種改進。

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

  • 快速排序是非穩定的排序演算法
  • 時間複雜度:最差為O(logn2),平均為O(nlogn),最好為O(nlogn)
void quickSort(int num[],int count,int left,int right)
{
    if (left >= right){
        
        return ;
    }
    int key = num[left];
    int lp = left;           //左指標
    int rp = right;          //右指標
    while (lp < rp) {
        if (num[rp] < key) {
            int temp = num[rp];
            for (int i = rp - 1; i >= lp; i--) {
                num[i + 1] = num[i];
            }
            num[lp] = temp;
            lp ++;
            rp ++;
        }
        rp --;
    }
    quickSort(num,count,left,lp - 1);
    quickSort(num,count,rp + 1,right);
}

七、堆排序
是指利用堆這種資料結構所設計的一種排序演算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點

在堆的資料結構中,堆中的最大值總是位於根節點(在優先佇列中使用堆的話堆中的最小值位於根節點)。堆中定義以下幾種操作:

  • 最大堆調整(Max Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點

  • 建立最大堆(Build Max Heap):將堆中的所有資料重新排序

  • 堆排序(HeapSort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算

  • 堆排序是一個非穩定的排序演算法。
  • 時間複雜度:O(nlogn)
void maxHeapify(int num[], int start, int end) {
    //建立父節點指標和子節點指標
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end) { //若子節點指標在範圍內才做比較
        if (son + 1 <= end && num[son] < num[son + 1]) //先比較兩個子節點大小,選擇最大的
            son++;
        if (num[dad] > num[son]) //如果父節點大於子節點代表調整完畢,直接跳出函式
            return;
        else { //否則交換父子內容再繼續子節點和孫節點比較
            EXCHANGE(num[dad], num[son])
            dad = son;
            son = dad * 2 + 1;
        }
    }
}

void heapSort(int num[], int count) {
    int i;
    //初始化,i從最後一個父節點開始調整
    for (i = count / 2 - 1; i >= 0; i--)
        maxHeapify(num, i, count - 1);
    //先將第一個元素和已排好元素前一位做交換,再重新調整,直到排序完畢
    for (i = count - 1; i > 0; i--) {
        EXCHANGE(num[0], num[i])
        maxHeapify(num, 0, i - 1);
    }
}

相關文章