1. 程式人生 > 其它 >十大排序演算法速記總結

十大排序演算法速記總結

1.快速排序 O(nlog(n)) 不穩定排序

  1. 選取第一個數為哨兵

  2. 將比哨兵小的數都交換到前面,比哨兵大的數都交換到後面

  3. 返回哨兵位置,根據哨兵位置劃分左右區間重複第二步,直到各區間只有一個數。

int partition(vector<int> &nums, int left, int right){
    int key = nums[left]; // 第一個為哨兵
    int i = left, j = right;

    while(i < j){
        // 要先從右邊開始,只要右邊的數比哨兵大,就繼續
        while(i < j && nums[j] >= key){
            j--;
        }
        // 再找左邊,只要左邊的數<=哨兵就繼續
        while(i < j && nums[i] <= key){
            i++;
        }
        swap(nums[i], nums[j]); // // 走到這步時,一定有nums[i] > nums[left] 而nums[j] < nums[left],為了nums[i] < nums[j] 所以要進行交換
    }
    swap(nums[i], key); // 最後將哨兵放到正確的位置上,完成一次partition
    return i;        // 最後返回哨兵所在位置
}

void QuickSort(vector<int> &nums, int left, int right){
    if(left >= right) return;
    int pivot = patition(nums, left, right);
    QuickSort(nums, 0, pivot - 1);
    QuickSort(nums, pivot + 1, right);
}

2.歸併排序 O(nlog(n)) 穩定排序

  1. 將陣列劃分為左右兩個區間,區間內再劃分左右區間,直到左右區間中只有一個元素(劃分),通過遞迴實現
  2. 對左右區間元素進行合併, 也就是merge的過程、
  3. merge的思想是設定一個長度為左右兩區間長度和的輔助陣列,設定兩個指標分別從左子區間和右子區間的起始處開始,比較這兩個指標指向的元素,誰更小就把誰放到輔助陣列中,同時該指標+1。
  4. 當某個區間的指標已經走到頭時,跳出迴圈。將剩餘的沒有走到頭的區間的元素全部搬到輔助陣列中。
  5. 將輔助陣列中的元素更新到原陣列中。完成一次合併排序。
void merge(vector<int> &nums, int left, int mid, int right){
    vector<int> tmp(right - left + 1);  // 設定輔助陣列
    int i = left, j = mid + 1, idx = 0;

    while(i <= mid && j <= right){
        if(nums[i] <= nums[j]){
            tmp[idx++] = nums[i++];
        }
        else{
            tmp[idx++] = nums[j++];
        }
    }
    while(i <= mid){
        tmp[idx++] = nums[i++];
    }
    while(j <= right){
        tmp[idx++] = nums[j++];
    }

    for(int i = 0; i < tmp.size(); i++){
        nums[left + i] = tmp[i];
    }
}

void MergeSort(vector<int> &nums, int left, int right){
    if(left >= right) return;
    int mid = left + (right - left) / 2;
    MergeSort(nums, left, mid);
    MergeSort(nums, mid + 1, right);
    merge(nums, left, mid, right);
}

3.堆排序O(nlog(n)) 不穩定排序

思想: 堆是用陣列構建的,一個節點i它的左右子節點在陣列中的位置分別為2 * i + 1, 2 * i + 2。
首先建立一個堆,然後每次從堆中拿出堆頂元素與陣列的最後一個元素交換,再對堆進行調整,迴圈n次,可以得到排序後的陣列。

// 小根堆
void sift(vector<int> &nums, int index, int size){
    int i = index, j = 2 * i + 1;
    while(j < size){
        if(j + 1 < size && nums[j] > nums[j + 1]){  // 比較i的左右孩子,j指向較小者
            j++;
        }
        if(nums[i] < nums[j]) break;
        else{
            swap(nums[i], nums[j]);
            i = j; j = 2 * i + 1;   // i指向交換後的新位置,繼續向下比較,一直下沉到葉子節點
        }
    }
}

void HeapSort(vector<int> &nums){
    int n = nums,size();
    // 初始建堆, 從最後一個非葉子結點開始
    for(int i = n / 2 - 1; i < n; i++){
        sift(nums, i, n);
    }
    // 排序,重複移走堆頂元素,再調整堆
    for(int i = 0; i < n; i++){
        swap(nums[0], nums[n - i - 1]);
        sift(nums, i, n - i - 1);
    }
}

4.氣泡排序(O(n^2) 穩定排序

氣泡排序就是把小(或大)的元素往前調,比較的是相鄰兩個元素,交換也發生在這兩個元素之間。

  1. 比較相鄰的元素,如果第二個比第一個大,就交換他們兩個。
  2. 對每一對相鄰的元素執行步驟一,從開始到結尾,最後一個元素會是最大的數。
  3. 每次對越來越少的元素重複上面的步驟
void BubbleSort(vector<int> &nums){
    for(int i = 0; i < nums.size(); i++){
        for(int j = 0; j < nums.size() - i - 1; j++){
            if(nums[j] < nums[j + 1]){
                swap(nums[j], nums[j]);
            }
        }
    }
}

5.簡單插入排序(O(n^2)) 穩定

每一步將一個待排序的資料插入到前面已經排好序的有序序列中,直到插完所有元素為止。

初始有序序列為陣列第一個元素


void InsertSort(vector<int> &nums){
    int n = nums.size();
    for(int i = 1; i < n; i++){
        int key = nums[i];               // 待插入的資料key
        for(int j = i - 1; j >= 0; j--){
            if(key >= nums[j]){         
                break;
            }
            nums[j + 1] = nums[j];
        }
        nums[j + 1] = key;
    }
}

6.希爾排序(O(n^2 - nlog(2n)) 不穩定)

希爾排序是對直接插入排序的改進,採用插入排序的方法,先讓陣列中任意間隔為h的元素有序,剛開始h=n/2,接著讓h=n/4,讓h一直縮小,當h=1時,也就是此時陣列中任意間隔為1的元素有序。
排序完成。

void ShellSort(vector<int>& nums){
    int n = nums.size();
    for(int dis = n / 2; dis >= 1; dis /= 2){
        for(int i = dis; i < n; i++){
            int key = nums[i];
            for(int j = i - dis; j >=0; j--){
                if(key >= nums[j])
                    break;
                nums[j + dis] = nums[j];
            }
            nums[j + dis] = key;
        }
    }
}

7.選擇排序(O(n^2) 不穩定)

思想:給每個位置選擇當前元素最小的

  1. 在未排序的陣列中找到最大(小)元素,存放到排序序列的起始位置
  2. 從剩餘未排序的元素中繼續找最大(小)元素放到已排序序列的末尾
  3. 重複直到所有元素均排序

void SelectSort(vector<int> &nums){
    int n = nums.size();
    int maxIndex = 0;
    for(int i = 0; i < n; i++){
        maxIndex = i;
        for(int j = i + 1; j <n; j++){
            if(nums[j] > nums[maxIndex]){
                maxIndex = j;
            }
        }
        swap(nums[i], nums[maxIndex]);
    }
}

8.計數排序(O(n + k) 穩定)

思想:利用額外的陣列空間,但是元素需要在0~k之間,不能小於0

  1. 首先找到待排序陣列中最大和最小的元素,以最大元素值為長度構建輔助陣列
  2. 統計待排序陣列中每個值出現的次數,以值為下標,次數為值放入輔助陣列中
  3. 遍歷輔助陣列,從中按順序拿出陣列值(出現次數)不為0的下標,每拿一次,次數減一,直到為0
  4. 拿出所有後排序完成。

本質是將值轉化為陣列索引進行排序。

9.桶排序

將陣列分到有限數量的桶裡,每個非空的桶再分別排序,從不是空的桶子裡把元素再拿出來放回原來序列中。

10.基數排序,O(k * n) 穩定

也是一種桶排序,將整數按位數切分為不同的數字(個位, 十位,百位...),從最低為開始,依次進行桶排序。
所有位數字排序完成後,排序完成

總結:
計數排序,桶排序和基數排序都用到了同的概念,

  • 計數排序:每個桶只儲存單一鍵值。
  • 桶排序:每個桶儲存一定範圍的數值
  • 基數排序:根據鍵值的每位數字來分配桶