1.概述算法
排序是計算機程序設計中的一個重要操做,它的功能是將一個數據記錄(或記錄)的任意序列,從新排列成一個按關鍵字有序的序列。ide
爲了方便描述,咱們先確切定義排序:函數
假設含n個記錄的序列爲{R1,R2,R3,...,Rn},其相應的關鍵字序列爲{K1,K2,K3,...,Kn},要肯定一種序列,該序列的關鍵字知足非遞減(或非遞增)關係,這種操做稱之爲排序。測試
若n個記錄的序列中的任意兩個記錄排序先後的順序一致則稱這種排序是穩定的。ui
例如:原序列中Ri排在Rj以前,1<=i<=n,1<=j<=n,i!=j,排序以後依舊Ri排在Rj以前,這就是穩定排序,反之,不穩定排序。spa
若是排序只在RAM中進行則稱之爲內部排序,若是涉及到了外部存儲器(好比磁盤、固態硬盤、軟盤和閃存等)則稱之爲外部排序。設計
爲何會有這兩種方式呢,很簡單,若是處理的數據極大,一次不能所有讀入內存,則須要藉助外部存儲器進行排序。3d
2.插入排序指針
2.1直接插入排序code
最簡單的排序方式,基本操做是將一個記錄插入到已排好序的有序表中,從而獲得一個新的、記錄長度加1的有序表。
簡單證實:
假設含n個記錄的序列爲{R1,R2,R3,...,Rn},其相應的關鍵字序列爲{K1,K2,K3,...,Kn},初始取該序列的第一個記錄爲有序序列。
第一步取該序列的第二個記錄,插入有序序列的適當位置,使之有序;
假設第n-2步以後的原有序序列有序;
第n-1步時選取含n個記錄的序列爲{R1,R2,R3,...,Rn}的第n個記錄,插入有序序列的適當位置,使之有序,則第n-1步以後的有序序列仍然有序。
證畢。
那麼如何插入有序序列的適當位置呢?
含n個記錄的序列爲{R1,R2,R3,...,Rn}中選取第i個記錄時,前面i-1個記錄已經有序,則首先比較第i個記錄與第i-1個記錄的大小,若是第i個記錄小於第i-1記錄,則交換,再從第i-2個記錄向前搜索,在搜索過程當中,只要遇到大於第i個記錄的記錄,就將當前記錄後移一個位置,直至某個記錄小於或者等於第i個記錄或者向前搜索到了第0個位置。
其算法以下:
void insertSort(seqList *l) { for (int i = 2; i <= l->length; i++) /* 初始選擇第1個元素爲有序序列,第一步選取該序列的第2個元素 */ { if (l->r[i].key < l->r[i - 1].key) /* 首先比較第i個元素與第i-1個元素的大小,若是第i個元素小於第i-1元素,則交換 */ { l->r[0] = l->r[i]; l->r[i] = l->r[i - 1]; int j = 0; for (j = i - 2; l->r[0].key < l->r[j].key; --j) /* 再從第i-2個元素向前搜索,在搜索過程當中,只要遇到大於第i個元素的元素,就將當前元素後移一個位置,直至某個元素小於或者等於第i個元素或者向前搜索到了第0個位置 */ { l->r[j + 1] = l->r[j]; } l->r[j + 1] = l->r[0]; } } }
算法時間複雜度:
排序的基本操做爲比較關鍵字大小和移動記錄,當排序序列爲逆序且關鍵字不重複時時間複雜度最大。簡單證實一下:
當選中第i個記錄,要對其操做時,最長不過搜索到第0個位置,也就是比較i次,移動i+1次;
若是有n個記錄則要進行n-1次的選記錄,若是每次搜索到第0個位置,也就是每次比較最屢次,移動最屢次。那麼總得加起來就是總的比較了最屢次。爲何每次比較最屢次、移動最屢次,總的比較次數就最多、總的 移動次數就最多。
那麼如何肯定每次比較最屢次,移動最屢次的序列爲逆序序列呢?
很簡單,若是不是每次的都是逆序,那麼必定存在某次選擇未搜索到第0個位置就中止前向搜索了,由於找到了一個不小於(或者不大於)第i個記錄的記錄。
證畢。
最大移動次數:
最大比較次數:
2.2其餘插入排序
折半插入排序,因爲插入排序的基本操做是在一個有序表中進行查找和插入,那麼咱們能夠用折半查找來實現查找操做。
算法的正確性證實與直接插入排序一致,只是查找的過程不一樣。
void bInsertSort(seqList *l) { int low = 0; int high = 0; int m = 0; for (int i = 2; i <=l->length; i++) /* 初始選擇第1個元素爲有序序列,第一步選取該序列的第2個元素 */ { l->r[0] = l->r[i]; low = 1; high = i - 1; while (low <= high) /* 折半查找 */ { m = (low + high) / 2; if (l->r[0].key < l->r[m].key)high = m - 1; else low = m + 1; } for (int j = i - 1; j >= high + 1; --j) { l->r[j + 1] = l->r[j]; } /* 找到對應插入位置,將對應插入位置到第i-1個位置的元素後移 */ l->r[high + 1] = l->r[0]; } }
算法時間複雜度:
雖然查找過程的時間複雜度爲
, 當i足夠大時
可是最大移動次數不變,因此算法時間複雜度依舊爲
2.3希爾排序
先將整個待排序列分割成若干個子序列,分別進行插入排序,等整個序列基本有序,再對全體進行一次直接插入排序。
它的證實涉及到數學上一些還沒有解決的難題,有興趣能夠查看相關論文,通常不用這個算法內部排序。
3.交換排序
3.1冒泡排序
首先將第1個位置的關鍵字和第2個記錄的關鍵字比較,若爲逆序,即第一個位置的關鍵字比第二個位置的關鍵字大,則將兩個記錄交換,而後比較第2個位置的關鍵字和第3個位置的關鍵字的大小,若爲逆序,則交換記錄,以此類推。
簡單證實:
假設含n個記錄的序列爲{R1,R2,R3,...,Rn},其相應的關鍵字序列爲{K1,K2,K3,...,Kn},
第1步從第1個記錄開始,依次與最近的後面位置的記錄比較關鍵字大小,若爲逆序,則交換,當第n-1個記錄與第n個記錄比較關鍵字 以後,關鍵字最大的記錄必定在第n個位置(爲何呢?你想嘛,每次都把關鍵字較大的日後移,比較完後固然是最大的在最後面了),有序序列爲第n個記錄
假設第n-2步從第1個記錄開始到第1個記錄結束,每一個記錄與最近的後面位置的記錄比較關鍵字大小,若爲逆序,則交換,比較完而且將關鍵字較大的記錄加入有序序列後,有序序列爲第3個記錄到n個記錄
第n-1步時,因爲無序序列只有前面兩個,比較一次,直接加入有序序列,加入後,有序序列爲第1個記錄到第n個記錄。
證畢。
void bubbleSort(seqList *l) { recordType tmp; bool swap = false; for (int i = 0; i < l->length-1; i++)/* 總共進行n-1步 */ { swap = false; for (int j = 1; j <l->length - i; ++j) /* 每一步從第1個元素開始到第n-i-1元素結束,每一個元素與最近的後面位置比較 關鍵字大小,若爲逆序則交換 */ { if (l->r[j].key > l->r[j + 1].key) { swap = true; tmp = l->r[j + 1]; l->r[j + 1] = l->r[j]; l->r[j] = tmp; } } if (swap == false)break; /* 若是在比較關鍵字大小時沒有記錄移動,則說明前面的記錄已經按關鍵字有序 */ } }
算法時間複雜度:
若含n個記錄的序列{R1,R2,R3,...,Rn}按關鍵字大小比較,爲逆序,則總的時間複雜度最高。
,因此總的時間複雜度爲
3.2快速排序
經過一趟排序將待排的記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
這裏體現了一個算法策略--分治法。將一個問題劃分爲一個個子問題,再分別求解子問題,最後合併起來就是原問題的解。
具體實現:首先任意選取一個記錄,一般是第一個記錄,做爲樞軸pivot。定義兩個指針low和high,分別指向起始段和末端。high從所指的位置開始向前搜索找到第一個關鍵字小於pivot的記錄,而後low所指位置起向後搜索,找到第一個關鍵字大於pivot的記錄,再交換low和high所指向的位置的記錄(low<high),重複這一步驟,直至low等於high。
int partition(seqList *l, int low, int high) { l->r[0] = l->r[low]; /* 在某趟快速排序中,首先任意選取一個記錄,一般是第一個記錄,做爲樞軸pivot */ recordType tmp; while (low < high) /* 若是low==high,說明某趟快速排序已經搜索完全部記錄並找到了樞軸的恰當位置 使得樞軸左邊的記錄的關鍵字都小於等於樞軸右邊記錄的關鍵字都大於等於樞軸 */ { while ((low < high)&&(l->r[0].key > l->r[low].key))++low; while ((low < high)&&(l->r[0].key < l->r[high].key))--high; if (low <high) { tmp = l->r[low]; l->r[low] = l->r[high]; l->r[high] = tmp; } low = low + 1; high = high - 1; } return low; } void qSort(seqList *l, int low, int high) { /* 將某個序列遞歸的分爲多個子序列,直至子問題的規模不超過零 */ if (low < high) { int pivotPos = partition(l, low, high);/*將序列l->[low...high]一分爲二*/ qSort(l, pivotPos + 1, high);/*對高子表遞歸排序*/ qSort(l, low, pivotPos - 1);/*對低子表遞歸排序*/ } }
算法時間複雜度分析:
,
從快速排序的過程咱們能夠獲得下面的方程:
最壞狀況:
T(n)=T(n-1)+n (n>=2,n∈N+,爲何是這個範圍呢,等於1的時候直接退出,不用比較;只有一個記錄比較啥)
T(1)=0,T(2)=2;
T(n)爲總的問題的工做量,n爲劃分過程的工做量。算法的時間複雜度分析,通常關心的是關鍵語句的執行次數,這裏的比較操做是關鍵語句。什麼叫關鍵語句呢,執行次數最多的語句就叫關鍵語句。
爲何劃分過程當中的工做量爲n呢,必須的啊,不管你怎麼玩,每次都要將樞軸與樞軸所在序列的全部記錄的關鍵字比較大小,直至low等於high。也就是總共進行了n次比較。
T函數中的自變量表示該問題下的序列長度。
最壞狀況,也就是,你每次苦逼的比較了全部的數,可是每次都只獲得一個子序列,僅比序列長度少1。
這個遞推方程是否是很熟悉,不管你用遞歸樹仍是累加法均可以垂手可得獲得
很差意思哈,最初發表時算錯了,應該是
因此最壞狀況的時間複雜度爲O(n2)。
最好狀況:
T(n)=2T(n/2)+n
每次獲得兩個相等的子序列。爲何呢?假設每次劃分後的兩個子序列分別爲a和b,a+b表示這兩個子序列在劃分過程當中的總工做量,想一想大名鼎鼎的柯西不等式,你就知道當且僅當a=b時最小原式最小。
由主定理能夠輕鬆獲得最好的時間複雜度,不過,知其然知其因此然更好,咱們就走走推導過程。
因此最好的時間複雜度爲O(nlog2n)
4.測試
完整代碼
#include<stdio.h>
#include<stdlib.h> #include<conio.h> #define MaxSize 100 typedef struct _recordType { int key; int name; }recordType; typedef struct _seqList { recordType r[MaxSize + 1]; int length; }seqList; void insertSort(seqList *l) { for (int i = 2; i <= l->length; i++) /* 初始選擇第1個元素爲有序序列,第一步選取該序列的第2個元素 */ { if (l->r[i].key < l->r[i - 1].key) /* 首先比較第i個元素與第i-1個元素的大小,若是第i個元素小於第i-1元素,則交換 */ { l->r[0] = l->r[i]; l->r[i] = l->r[i - 1]; int j = 0; for (j = i - 2; l->r[0].key < l->r[j].key; --j) /* 再從第i-2個元素向前搜索,在搜索過程當中,只要遇到大於第i個元素的元素,就將當前元素後移一個位置,直至某個元素小於或者等於第i個元素或者向前搜索到了第0個位置 */ { l->r[j + 1] = l->r[j]; } l->r[j + 1] = l->r[0]; } } } void bInsertSort(seqList *l) { int low = 0; int high = 0; int m = 0; for (int i = 2; i <=l->length; i++) /* 初始選擇第1個元素爲有序序列,第一步選取該序列的第2個元素 */ { l->r[0] = l->r[i]; low = 1; high = i - 1; while (low <= high) /* 折半查找 */ { m = (low + high) / 2; if (l->r[0].key < l->r[m].key)high = m - 1; else low = m + 1; } for (int j = i - 1; j >= high + 1; --j) { l->r[j + 1] = l->r[j]; } /* 找到對應插入位置,將對應插入位置到第i-1個位置的元素後移 */ l->r[high + 1] = l->r[0]; } } void bubbleSort(seqList *l) { recordType tmp; bool swap = false; for (int i = 0; i < l->length-1; i++)/* 總共進行n-1步 */ { swap = false; for (int j = 1; j <l->length - i; ++j) /* 每一步從第1個元素開始到第n-i-1元素結束,每一個元素與最近的後面位置比較 關鍵字大小,若爲逆序則交換 */ { if (l->r[j].key > l->r[j + 1].key) { swap = true; tmp = l->r[j + 1]; l->r[j + 1] = l->r[j]; l->r[j] = tmp; } } if (swap == false)break; /* 若是在比較關鍵字大小時沒有記錄移動,則說明前面的記錄已經按關鍵字有序 */ } } int partition(seqList *l, int low, int high) { l->r[0] = l->r[low]; /* 在某趟快速排序中,首先任意選取一個記錄,一般是第一個記錄,做爲樞軸pivot */ recordType tmp; while (low < high) /* 若是low==high,說明某趟快速排序已經搜索完全部記錄並找到了樞軸的恰當位置 使得樞軸左邊的記錄的關鍵字都小於等於樞軸右邊記錄的關鍵字都大於等於樞軸 */ { while ((low < high)&&(l->r[0].key > l->r[low].key))++low; while ((low < high)&&(l->r[0].key < l->r[high].key))--high; if (low <high) { tmp = l->r[low]; l->r[low] = l->r[high]; l->r[high] = tmp; } low = low + 1; high = high - 1; } return low; } void qSort(seqList *l, int low, int high) { /* 將某個序列遞歸的分爲多個子序列,直至子問題的規模不超過零 */ if (low < high) { int pivotPos = partition(l, low, high);/*將序列l->[low...high]一分爲二*/ qSort(l, pivotPos + 1, high);/*對高子表遞歸排序*/ qSort(l, low, pivotPos - 1);/*對低子表遞歸排序*/ } } int sum(seqList *l,int n) { if (n > 0) return sum(l, n - 1) + l->r[n].key; else return 0; } int main(void) { freopen("in.txt", "r", stdin); seqList *l = (seqList*)malloc(sizeof(seqList)); scanf("%d", &l->length); for (int i = 1; i <= l->length; i++) { scanf("%d", &l->r[i].key); } insertSort(l); printf("Straight Insertion Sort:\n"); for (int i = 1; i <= l->length; i++) { printf("%d ", l->r[i].key); } printf("\n"); fseek(stdin, 0, 0); scanf("%d", &l->length); for (int i = 1; i <= l->length; i++) { scanf("%d", &l->r[i].key); } bInsertSort(l); printf("Binary Insertion Sort:\n"); for (int i = 1; i <= l->length; i++) { printf("%d ", l->r[i].key); } printf("\n"); fseek(stdin, 0, 0); scanf("%d", &l->length); for (int i = 1; i <= l->length; i++) { scanf("%d", &l->r[i].key); } bubbleSort(l); printf("Bubble Sort:\n"); for (int i = 1; i <= l->length; i++) { printf("%d ", l->r[i].key); } printf("\n"); fseek(stdin, 0, 0); scanf("%d", &l->length); for (int i = 1; i <= l->length; i++) { scanf("%d", &l->r[i].key); } qSort(l, 1, l->length); printf("Quick Sort:\n"); for (int i = 1; i <= l->length; i++) { printf("%d ", l->r[i].key); } printf("\n"); printf("%d\n", sum(l, l->length)); getch(); }
測試文本:
測試結果