【君义精讲】排序算法
一、概念
1. 排序的定義
一般定義:將一組無序的數據元素調整為有序的數據元素。
數學定義:假設含n個數據元素的序列為R1,R2,...,Rn{R_1,R_2,...,R_n}R1?,R2?,...,Rn?,其相應的關鍵字序列為K1,K2,...,Kn{K_1,K_2,...,K_n}K1?,K2?,...,Kn?,這些關鍵字相互之間可以進行比較,即在它們之間存在這樣的關系:Kp1≤Kp2≤...≤KpnK_{p1}\le K_{p2}\le...\le K_{pn}Kp1?≤Kp2?≤...≤Kpn?,按此固有關系將上式記錄序列重新排序為:{Rp1,Rp2,...,Rpn}\{R_{p1}, R_{p2}, ... ,R_{pn}\}{Rp1?,Rp2?,...,Rpn?}的操作稱為排序。
2. 排序的前提條件
首先,要排序的數據元素必須類型相同。
其次,數據元素之間是可以比較的,即只要給定兩個元素,就可以說出誰“大”誰“小”,當然,很多情況下“大”與“小”是人為定義的。
例:
整數或小數比較:可以直接比較數值大小。
字母比較:字母在前面的被認為更小,字母在后面的被認為更大。如’a’比’z’小。
字符串比較:按字典序比較,字典序在前的被認為更小,字典序在后面的被認為更大。
數對比較:數對的第一個數字更大的數對更大,數對第一個數字相同時,第二個數字更大的數對更大。
第三,要明確排成升序還是降序。
嚴格意義下,有序序列的順序有以下4種:
- 升序序列:后面的數據元素大于前面的數據元素。
- 不降序列:后面的數據元素大于等于前面的數據元素。
- 不升序列:后面的數據元素小于等于前面的數據元素。
- 降序序列:后面的數據元素小于前面的數據元素。
以下討論時,使用非嚴格敘述,即用升序表示升序序列及不降序列,用降序表示不升序列及降序序列。
3. 內部排序和外部排序:
本文只介紹內部排序。
4. 排序中的關鍵操作
根據元素間比較規則寫出元素比較的表達式或函數,表達式的值必須是布爾值,表示第一個元素是否比第二個元素小。
常用到STL中的swap函數,可以實現任意類型變量值的交換。其實現為:
5. 排序算法的穩定性
假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,那么這個排序算法就是穩定的。
即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱為不穩定的。
5. 對排序的評價
本身存儲數據的存儲空間不算在內,只考慮由于選擇這一排序算法帶來的額外空間開銷。
6. 排序圖示及動畫
這個已經有很多人做過了,本文不再給出相關圖示及動畫。排序動畫可以在這個網站看:
visualgo.net排序動畫
二、排序算法
n為待排序的元素個數。
本文以該題為例題:洛谷 P1177 【模板】快速排序
復雜度為O(n2)O(n^2)O(n2)的排序算法只能通過該題的一個測試點。
計數排序無法完成該題。
希爾排序、基數排序以及多數復雜度為O(nlogn)O(nlogn)O(nlogn)的排序算法可以完全通過該題所有測試點。
該題幾個測試點測試了極端情況,比如序列一開始就是有序的,或都是同一個值。一些排序算法在極端情況下復雜度會退化為O(n2)O(n^2)O(n2),因而可能有幾個點過不了。
本文只給出下標從1開始的數組的排序算法代碼。下標從0開始的數組的排序算法代碼與之類似,不再贅述。
本文只給出升序排序算法代碼,降序排序一般只需要將比較符號修改一下即可實現,不再贅述。
1. 直接選擇排序
- 基本思想:每一趟從待排序的數據元素中選出最小(或最大)的一個元素,順序放在待排序的數列的最前面,直到全部待排序的數據元素排完。
第1次在下標1~n中選擇最小值,與下標1的元素進行交換。
第2次在下標2~n中選擇最小值,與下標2的元素進行交換。
第i次在下標i~n中選擇最小值,與下標i的元素進行交換。
第n-1次,在下標n-1~n中選擇最小值,與下標n-1的元素進行交換。 - 復雜度:時間復雜度:O(n2)O(n^2)O(n2) ?? 額外空間復雜度:O(1)O(1)O(1)
- 穩定性:不穩定
- 示例:
粗體數字為當前要替換的位置,斜體數字為已經確定的有序序列
8 3 2 6 4 從8到末尾的范圍內,選擇最小值2與8交換
2 3 8 6 4 從3到末尾的范圍內,選擇最小值3與3交換(實際不變)
2 3 8 6 4 從8到末尾的范圍內,選擇最小值4與8交換
2 3 4 6 8 從6到末尾的范圍內,選擇最小值6與6交換(不變)
2 3 4 6 8 所有數字都已確定位置
- 代碼:
寫法1:找出最小值而后交換
寫法2:不斷和第i位置交換
#include <bits/stdc++.h> using namespace std; int main() {int a[100005], n;cin >> n;for(int i = 1; i <= n; ++i)//輸入數據 cin >> a[i];for(int i = 1; i <= n-1; ++i)//直接選擇排序 升序排序 for(int j = i+1; j <= n; ++j)if(a[j] < a[i])//如為 > 即可實現降序排序swap(a[i], a[j]);for(int i = 1; i <= n; ++i)//輸出數據 cout << a[i] << ' ';return 0; }2. 冒泡排序
- 基本思想:重復地訪問要排序的元素序列,依次比較兩個相鄰的元素,如果兩元素的順序與預期順序不符,就將二者交換。
每趟冒泡后,最大(最小)的元素會經由交換“浮”到序列右端(或左端)。多次冒泡后,即可完成排序。 - 復雜度:時間復雜度:O(n2)O(n^2)O(n2) ?? 額外空間復雜度:O(1)O(1)O(1)
- 穩定性:穩定
- 示例
粗體數字為要比較的兩個數字,斜體數字為已經確定位置的數字
第1趟冒泡
8 3 2 6 4
3 8 2 6 4
3 2 8 6 4
3 2 6 8 4
3 2 6 4 8
第2趟冒泡
3 2 6 4 8
2 3 6 4 8
2 3 6 4 8
2 3 4 6 8
第3趟冒泡
2 3 4 6 8
2 3 4 6 8
2 3 4 6 8
第4趟冒泡
2 3 4 6 8
2 3 4 6 8
最后,第一個數字的位置自然被確定
2 3 4 6 8
- 代碼:
寫法1:易記版本:i從1到n-1,j從1到n-i
寫法2:符合概念的寫法
#include <bits/stdc++.h> using namespace std; int main() {int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = n; i >= 2; --i)//i:此次冒泡,最大值最后落在的位置。for(int j = 1; j <= i - 1; ++j)//j: 要交換的數對中第一個數的位置if(a[j] > a[j + 1])swap(a[j], a[j + 1]);for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0; }寫法3:將最小值冒泡到前面的寫法
#include <bits/stdc++.h> using namespace std; int main() {int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 1; i <= n - 1; ++i)//i:從后向前冒泡,最小值所在位置 for(int j = n; j >= i + 1; --j)//j:比較數對第二個數的位置 if(a[j] < a[j - 1])swap(a[j], a[j - 1]); for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0; }3. 插入排序
- 基本思想:插入排序是指在待排序的元素中,假設前面n-1個數已經是排好順序的,現將第n個數插到前面已經排好的序列中,使得插入后這個序列也是排好順序的。按照此法對所有元素進行插入,直到整個序列排為有序的過程。
- 復雜度:時間復雜度:O(n2)O(n^2)O(n2) ?? 額外空間復雜度:O(1)O(1)O(1)
- 穩定性:穩定
- 示例
粗體數字為當前正在插入的數字,斜體數字為已經構造好的有序序列
每次將正在插入的數字和其前面的數字比較
第1個數字自然構成一個有序序列
8 3 2 6 4
插入第2個數字
8 3 2 6 4
3 8 2 6 4
插入第3個數字
3 8 2 6 4
3 2 8 6 4
2 3 8 6 4
插入第4個數字
2 3 8 6 4
2 3 6 8 4
插入第5個數字
2 3 6 8 4
2 3 6 4 8
2 3 4 6 8
2 3 4 6 8
- 代碼
寫法1:不斷交換元素
寫法2:用一個變量保存要插入的值
#include <bits/stdc++.h> using namespace std; int main() {int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 2; i <= n; ++i)//將第i個數字插入有序序列{int j, num = a[i];//num:待插入數字 for(j = i; j > 1; j--)//j指向待插入數字的位置{if(num < a[j - 1])a[j] = a[j - 1];elsebreak;}a[j] = num;} for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0; }寫法3:可以一邊輸入一邊做插入排序(常用)
#include <bits/stdc++.h> using namespace std; int main() {int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i){cin >> a[i];for(int j = i; j > 1; --j){if(a[j] < a[j-1])swap(a[j], a[j-1]);elsebreak;}}for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0; }4. 希爾排序
- 基本思想:希爾排序是把數據元素按下標的一定增量分組,對每組使用直接插入排序算法排序。隨著增量逐漸減少,每組包含的元素越來越多,當增量減至 1 時,整個數組就已經完成排序。
- 復雜度:時間復雜度:O(n1.3)~O(n2)O(n^{1.3}) \sim O(n^2)O(n1.3)~O(n2) ?? 額外空間復雜度:O(1)O(1)O(1)
- 穩定性:不穩定
- 示例:
初始增量n/2,為3
8 3 2 6 4 1??8與6一組,3與4一組,2與1一組,分別做插入排序,結果為:6 3 1 8 4 2
增量除以2,為1
6 3 1 8 4 2 ?? 做插入排序,結果為:1 2 3 4 6 8
- 代碼:
5. 計數排序
- 基本概念:
散列思想:通過散列函數把數據值對應到一個數組下標
桶排序:將數據通過散列函數分配到有限數量的有序的桶里,每個桶內分別排序,最后將各個桶中的數據合并。
計數排序:是一種特殊的桶排序。桶的個數是可能出現的數據種類數。
計數數組:數組下標為數據值,數組的值表示數據個數。即a[i]表示數字i出現的個數。
計數排序只適用于對范圍有限的整數進行排序。 - 復雜度:
數據個數為n,數據范圍為k
時間復雜度:O(n+k)O(n+k)O(n+k)
額外空間復雜度:O(k)O(k)O(k) - 穩定性:穩定
- 示例:
待排序數據:1, 5, 3, 1, 3, 2
輸入數據并統計后,計數數組c為
| 值 | 2 | 1 | 2 | 0 | 1 |
遍歷計數數組,將i輸出c[i]次,為:1 1 2 3 3 5
- 代碼
用計數排序做不了P1177,這里用計數排序完成的問題為:
給定n個1~1000范圍內的整數(n≤104n \le 10^4n≤104),輸出其升序序列。
6. 基數排序
- 基本概念:基數排序是一種整數排序算法,其原理是將整數按每個位數分別比較。相當于多趟桶排序,適合位數有限的整數排序。
- 復雜度:
d是數字位數,n是數字個數,r是基數(進制)
時間復雜度:O(d(n+r))O(d(n+r))O(d(n+r))
額外空間復雜度:O(r+n)O(r+n)O(r+n) - 穩定性:穩定
- 示例:
待排序數字:53, 542, 3, 63, 14, 214
第一趟按個位排序后:542, 53, 3, 63, 14, 214
第二趟按十位排序后:3, 14, 214, 542, 53, 63
第三趟按百位排序后:3, 14, 53, 63, 214, 542
- 代碼:
7. 歸并排序
- 基本概念:
給定兩個長為a、b的有序序列,合并成長為a+b的有序序列。
- 復雜度:
時間復雜度:O(nlogn)O(nlogn)O(nlogn)
空間復雜度:O(n)O(n)O(n) - 穩定性:穩定
- 示例:
待排序數字序列 1 5 6 3 2 7 9 8
先進行折半分組,對于分組后的每一組,再進行折半分組,直到每組只有1個元素
1 5 6 3 | 2 7 9 8
1 5 | 6 3 | 2 7 | 9 8
1 | 5 | 6 | 3 | 2 | 7 | 9 | 8
依次對于每次分開的兩組合并有序序列
1 5 | 3 6 | 2 7 | 8 9
1 3 5 6 | 2 7 8 9
1 2 3 5 6 7 8 9
- 代碼
8. 快速排序
-
基本概念:
通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小。分別對這兩部分記錄繼續進行快速排序,以達到整個序列有序。 -
復雜度:
時間復雜度:O(nlogn)O(nlogn)O(nlogn)
額外空間復雜度:O(1)O(1)O(1) -
穩定性:不穩定
-
代碼
9. 堆排序
- 基本概念:堆是一種基于二叉樹的數據結構,借助堆可以在O(logn)O(logn)O(logn)時間復雜度下獲取n個元素中的最值。
- 復雜度:
時間復雜度:O(nlogn)O(nlogn)O(nlogn)
額外空間復雜度:O(1)O(1)O(1) - 穩定性:不穩定
實現堆排序有兩種方法
方法1:似于選擇排序,構造小頂堆,每次取出所有元素中的最小值,并刪除堆頂。循環n次即可獲得有序序列。
方法2:構造大頂堆
10. 二叉排序樹排序
- 基本概念
二叉排序樹或者是一顆空樹,或者是具有如下性質的二叉樹:
二叉排序樹插入操作:如果要插入的值比當前結點的值大,則看右孩子。若要查找的值比當前結點的值小,則看左孩子。如果要查找的值與當前結點的值相等,則該數值出現的次數加1。如果找到空結點,那么就將其插入到這個位置。
每次插入操作復雜度:O(logn)O(logn)O(logn)
輸入n個數字,將每個數字插入到二叉排序樹中,對二叉排序樹做中序遍歷,即可得到有序序列。
- 復雜度:
時間復雜度:O(nlogn)O(nlogn)O(nlogn)
額外空間復雜度:O(1)O(1)O(1) - 穩定性:穩定
- 代碼
11. 排序總結
三、STL中的排序函數
1. sort函數
內部原理是快速排序,不穩定,時間復雜度O(nlogn)O(nlogn)O(nlogn)
sort(起始元素指針/迭代器,最后一個元素的指針/迭代器的后一個位置, 比較函數);
對數組a下標1到n進行排序
sort(a+1,a+1+n, cmp);
對STL容器a進行排序
sort(a.begin(), a.end(), cmp);
若不寫比較函數cmp,默認為升序排序。如果對結構體變量進行排序,也可以通過重載小于號運算符來指定排序規則。
比較函數的寫法:傳入兩個數組中元素類型的量,返回傳入第一個參數排在前面的條件
或
bool cmp(const int &a, const int &b) {return a < b;//a < b:升序 a > b:降序 }解決:P1177 【模板】快速排序
- 用數組保存數據
- 用vector保存數據
2. stable_sort函數
內部原理是歸并排序,穩定,時間復雜度O(nlogn)O(nlogn)O(nlogn)
stable_sort(起始元素指針/迭代器,最后一個元素的指針/迭代器的后一個位置, 比較函數);
對數組a下標1到n進行排序
stable_sort(a+1,a+1+n, cmp);
對STL容器a進行排序
stable_sort(a.begin(), a.end(), cmp);
若不寫比較函數cmp,默認為升序排序。
比較函數的寫法:傳入兩個數組中元素類型的量,返回傳入第一個參數排在前面的條件
或
bool cmp(const int &a, const int &b) {return a < b;//a < b:升序 a > b:降序 }解決:P1177 【模板】快速排序
- 用數組保存數據
- 用vector保存數據
總結
以上是生活随笔為你收集整理的【君义精讲】排序算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原子自增_小学妹教你并发编程的三大特性:
- 下一篇: ts watch路由 参数变化_Type