各種常規排序演算法總結
1.插入排序
基本思想是:每次將一個待排序的記錄,按其關鍵字大小插入到前面已經排好序的子表中的適當位置,直到全部記錄插入完成為止。常規插入排序分兩種,即直接插入排序和希爾排序。
1.1直接插入排序
假設待排序的記錄放在陣列R[0...n-1]中,排序過程的某一中間時刻,R被劃分成兩個子區間R[0..i-1]和R[i..n-1],其中:前一個區間是已經排好序的有序區,後一個子區間是當前未排序的部分,不妨稱為無序區。直接插入排序的基本操作是將當前無序區的第一個記錄R[i]插入到R[0..i-1]中適當的位置,使R[0..i]變為新的有序區-------演算法時間複雜度o(n^2)。
// 插入排序 void insertSort(int array[], int length) { int i, j, key; for (i = 1; i < length; i++) { key = array[i]; // 把i之前大於array[i]的資料向後移動 for (j = i - 1; j >= 0 && array[j] > key; j--) { array[j + 1] = array[j]; } // 在合適位置安放當前元素 array[j + 1] = key; } }
1.2shell排序
希爾排序也是一種插入排序方法,實際上是一種分組插入方法。基本思想是:先取定一個小於n的整數d1作為第一個增量,把表的全部記錄分成d1個組,所有距離為d1的倍數的記錄放在同一個組中,在各組內進行直接插入排序;然後取第二個增量d2(<d1),重複上述的分組和排序,直至所取的增量dt=1(dt<dt-1<..d2<d1),及所有記錄放在同一組中進行直接插入排序為止。演算法最好時間複雜度o(nlogn)。
// shell排序 void shellSort(int array[], int length) { int key; // 增量從陣列長度的一半開始,每次減小一倍 for (int increment = length / 2; increment > 0; increment /= 2) for (int i = increment; i < length; ++i) { key = array[i]; // 對一組增量為increment的元素進行插入排序 for (int j = i - increment; j >= 0 && array[j] > key; j -= increment) { array[j+increment] = array[j]; } // 在合適位置安放當前元素 array[j+increment] = key; } }
2.交換排序
基本思想:兩兩比較待排序記錄的關鍵字,發現兩個記錄的次序相反時,即進行交換,直到沒有反序的記錄為止。
2.1氣泡排序
基本思想是,通過無序區中相鄰記錄關鍵字間的比較和位置的交換,使關鍵字最小的記錄如氣泡一般逐漸往上“漂浮”直至“水面”。整個演算法是從最後面的記錄開始,對每兩個相鄰的關鍵字進行比較,且使關鍵字較小的記錄換至關鍵字較大的記錄之上,使得經過一趟氣泡排序後,關鍵字最小的記錄到大最上端,接著,再在剩下的記錄中找到關鍵字次小的記錄,並把它換在第二個位置上,依次類推,一直到所有記錄都有序為止。演算法時間複雜度o(n^2)。
//氣泡排序 void bubble_sort(int a[], int length) { int i, j, tag; for(i = length - 1; i > 0; i--)//不斷把前面無序區最大元素移到後面有序區 { tag = 1;//標誌位 for(j = 0; j < i; j++)//前面無序區最大元素不斷向後移 { if(a[j] > a[j + 1]) { a[j] ^= a[j + 1]; a[j + 1] ^= a[j]; a[j] ^= a[j+1]; tag = 0; } } if(tag == 1) { break; } } }
2.2快速排序
快速排序是由氣泡排序改進而得的,它的基本思想是:在待排序的n個記錄中任取一個記錄(通常取第一個記錄),把該記錄放入適當位置後,資料序列被此記錄劃分成兩部分。所有關鍵字比該記錄關鍵字小的記錄放置在前一部分,所有比它大的記錄放置在後一部分,並把該記錄排在這兩部分的中間(稱為該記錄歸位),這個過程稱作一趟快速排序。之後對所有的兩部分分別重複上述過程,直至每個部分內只有一個記錄或為空為止。簡單的說,每趟使表的第一個元素放入適當位置,將表一分為二,對子表按遞迴方法繼續這種劃分,直至劃分的子表長為1或0.-------演算法最好時間複雜度o(nlog2n)
// 對一個給定範圍的子序列選定一個樞紐元素,執行完函式之後返回分割元素所在的位置, // 在分割元素之前的元素都小於樞紐元素,在它後面的元素都大於這個元素 int partition(int array[], int low, int high) { // 採用子序列的第一個元素為樞紐元素 int pivot = array[low]; while (low < high) { // 從後往前在後半部分中尋找第一個小於樞紐元素的元素 while (low < high && array[high] >= pivot) --high; // 將這個比樞紐元素小的元素交換到前半部分 array[low] = array[high]; // 從前往後在前半部分中尋找第一個大於樞紐元素的元素 while (low < high && array[low] <= pivot) ++low; // 將這個比樞紐元素大的元素交換到後半部分 array[high] = array[low]; } array[low] = pivot; // 返回樞紐元素所在的位置 return low; } // 快速排序 void quickSort(int array[], int low, int high) { if (low < high) { int n = partition(array, low, high); quickSort(array, low, n); quickSort(array, n + 1, high); } }
3.選擇排序
基本思想:每一趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排好序的子表的最後,直到全部記錄排序完畢。由於選擇排序方法每一趟總是從無序區中選出全域性最小(或最大)的關鍵字,所以適合於從大量的記錄中選擇一部分排序記錄,如,從10000個記錄中選擇出關鍵字前10位的記錄,就適合使用選擇排序法。
3.1直接選擇排序
直接選擇排序法:第i趟排序開始時,當前有序區和無序區分別為R[0..i-1]和R[i..n-1](0≤ i≤n-1),該趟排序則是從當前無序區中選出關鍵字最小的記錄R[k],將它與無序區的第一個記錄R[i]交換,使得R[0..i]和R[i+1..n-1]分別為新的有序區和新的無序區
//直接選擇排序 void selectSort(int array[],int nLength) { int i,j,k; for(i = 0; i < nLength - 1; i++) { k = i; for(j = i+1; j < nLength; j++) { if(array[j] < array[k]) k = j; } if(k != i) swap(&array[k],&array[i]); } }
3.2堆排序
n個關鍵字序列Kl,K2,…,Kn稱為堆,當且僅當該序列滿足如下性質(簡稱為堆性質):
(1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤)
若將此序列所儲存的向量R[1……n]看做是一棵完全二叉樹的儲存結構,則堆實質上是滿足如下性質的完全二叉樹:樹中任一非葉結點的關鍵字均不大於(或不小於)其左右孩子(若存在)結點的關鍵字。
堆的這個性質使得可以迅速定位在一個序列之中的最小(大)的元素。
堆排序演算法的過程如下:1)得到當前序列的最小(大)的元素 2)把這個元素和最後一個元素進行交換,這樣當前的最小(大)的元素就放在了序列的最後,而原先的最後一個元素放到了序列的最前面 3)交換可能會破壞堆序列的性質(注意此時的序列是除去已經放在最後面的元素),因此需要對序列進行調整,使之滿足於上面堆的性質。重複上面的過程,直到序列調整完畢為止。
// array是待調整的堆陣列,i是待調整的陣列元素的位置,length是陣列的長度 void heapAdjust(int array[], int i, int nLength) { int nChild, nTemp; for (nTemp = array[i]; 2 * i + 1 < nLength; i = nChild) { // 子結點的位置是 父結點位置 * 2 + 1 nChild = 2 * i + 1; // 得到子結點中較大的結點 if (nChild != nLength - 1 && array[nChild + 1] > array[nChild]) ++nChild; // 如果較大的子結點大於父結點那麼把較大的子結點往上移動,替換它的父結點 if (nTemp < array[nChild]) array[i] = array[nChild]; else// 否則退出迴圈 break; } // 最後把需要調整的元素值放到合適的位置 array[i] = nTemp; } // 堆排序演算法 void heapSort(int array[], int length) { //建立大頂堆 for (int i = length / 2 - 1; i >= 0; --i) heapAdjust(array, i, length); // 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素 for (int j = length - 1; j > 0; --j) { // 把第一個元素和當前的最後一個元素交換, // 保證當前的最後一個位置的元素都是在現在的這個序列之中最大的 swap(&array[0], &array[j]); // 不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值 heapAdjust(array, 0, j); } }
4.歸併排序
歸併排序是多次將兩個或兩個以上的有序表合併成一個有序表。最簡單的歸併是直接將兩個有序的子表合併成一個有序的表。
兩個有序表直接合併成一個有序表的演算法Merge().設兩個有序表存放在同一陣列中相鄰的位置上:R[low..mid],R[mid+1..high],先將他們合併到一個區域性的暫存陣列R1中,待合併完成後將R1複製到R中。每次從兩個段中取出一個記錄進行關鍵字的比較,將較小者放入R1中,最後將各段中餘下的部分直接複製到R1中。
void merge(int array[],int start,int mid,int end) { int i,j,k; i = start; j = mid + 1; k = 0; int * p = (int *)malloc((end - start + 1) * sizeof(int)); while((i <= mid) && (j <= end)) { if(array[i] < array[j]) p[k++] = array[i++]; else p[k++] = array[j++]; } while(i <= mid) p[k++] = array[i++]; while(j <= end) p[k++] = array[j++]; k = 0; while(start <= end) array[start++] = p[k++]; free(p); } //歸併排序 void mergeSort(int array[],int start,int end) { if(start < end) { int i = (start + end) / 2; mergeSort(array,start,i); mergeSort(array,i+1,end); merge(array,start,i,end); } }
5.計數排序和基數排序
在之前介紹過的排序方法,都是屬於[比較性]的排序法,也就是每次排序時,都是比較整個關鍵字的大小以進行排序。計數排序是一個非基於比較的線性時間排序演算法。
計數排序演算法的基本思想是對於給定的輸入序列中的每一個元素x,確定該序列中值小於x的元素的個數。一旦有了這個資訊,就可以將x直接存放到最終的輸出序列的正確位置上。例如,如果輸入序列中只有17個元素的值小於x的值,則x可以直接存放在輸出序列的第18個位置上。
//計數排序 //nMaxValue為陣列array中最大值 void countingSort(int array[],int nMaxValue,int nLength) { int i; int *pTemp = (int *)malloc((nMaxValue + 1) * sizeof(int)); int *pResult = (int *)malloc((nLength + 1) * sizeof(int)); for(i = 0; i <= nMaxValue; i++) pTemp[i] = 0; // 此過程記錄每一個元素的個數 for(i = 0; i < nLength; i++) pTemp[array[i]]++; // 此過程確定小於元素x的元素的個數 for(i = 1; i <= nMaxValue; i++) pTemp[i] += pTemp[i-1]; for(i = nLength-1; i >= 0; i--) { pResult[pTemp[array[i]]] = array[i]; pTemp[array[i]]--; } for(i = 1; i <= nLength; i++) array[i-1] = pResult[i]; }
基數排序的主要思路是,將所有待比較數值(注意,必須是正整數)統一為同樣的數位長度,數位較短的數前面補零. 然後, 從最低位開始, 依次進行一次穩定排序.這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列.
比如這樣一個數列排序: 342 58 576 356, 以下描述演示了具體的排序過程
第一次排序(個位):
3 4 2
5 7 6
3 5 6
0 5 8
第二次排序(十位):
3 4 2
3 5 6
0 5 8
5 7 6
第三次排序(百位):
0 5 8
3 4 2
3 5 6
5 7 6
結果: 58 342 356 576
//基數排序 //nMaxBit為數字中的最高位數,如最大數為235,則nMaxBit為3 void radixSort(int *array, int nLength ,int nMaxBit) { int i,j; int temp[10]; int nTmp; int nRadix = 1; int *pResult = (int *)malloc(nLength * sizeof(int)); for(j = 1; j <= nMaxBit; j++) { // 以下和計數排序一樣 for(i = 0; i < 10; i++) temp[i] = 0; // 此過程記錄每一個元素的個數 for(i = 0; i < nLength; i++) { nTmp = (array[i] / nRadix) % 10; temp[nTmp]++; } // 此過程確定小於元素x的元素的個數 for(i = 1; i < 10; i++) temp[i] += temp[i-1]; for(i = nLength-1; i >= 0; i--) { nTmp = (array[i] / nRadix) % 10; temp[nTmp]--; pResult[temp[nTmp]] = array[i]; } for(i = 0; i < nLength; i++) array[i] = pResult[i]; nRadix *= 10; } }