一,插入排序
插入排序基本思想:
在一個已經有序的序列里插入新的元素,直到有序序列包含所有被排序元素。
例子:
代碼實現:

void InsertSort(vector<int> &v) { for(int i = 1;i < v.size(); ++i)//i表示有序集合里的元素數目和待插入元素下標 { for(int j = i; j > 0 && v[j-1] > v[j]; --j) { int temp = v[j-1]; v[j-1] = v[j]; v[j] = temp; } } }
時間復雜度為O(N^2)
空間復雜度為O(1)
插入排序在小規模數據時或者基本有序時比較高效。
二,希爾排序
希爾排序基本思想:
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。本質上是分組插入排序。
例子:
在取增量d的時候一般有以下要求:
①最后一次排序d = 1;
②每次取的d並不包含處1以外的公因子;
③采用縮小增量法,d的取值逐步縮小。
代碼實現:

void ShellSort(vector<int> &v) { int k = 0; int t = v.size() - 1; int delta = 2*t - k - 1;//有研究表明,delta取2*t-k-1時時間復雜度可達O(N^1.5) for(k = 0;k < v.size(); ++k) { delta = (2*t-k-1) == t?(2*t-k-1):1;//最后一次delta要取1 for(int i = 0; i < v.size()-delta; ++i) { if(v[i] > v[i+delta]) { int temp = v[i]; v[i] = v[i+delta]; v[i+delta] = temp; } } } }
希爾排序delta開始會很大,所以該排序不穩定。
希爾排序的復雜度與delta相關:
{1,2,4,8,...}使用這個增量序列的時間復雜度(最壞情形)是O(N^2)
代碼中所給序列的時間復雜度時O(N^1.5)
空間復雜度為O(1)
希爾排序為插入排序的一種優化,其適用范圍為:大規模且無序的數據。
三,冒泡排序
冒泡排序基本思想:
將第一個記錄的關鍵字與第二個記錄的關鍵字進行比較,若逆序則交換;然后比較第二個記錄與第三個記錄;依次類推,直至第n-1個記錄和第n個記錄比較為止——第一趟冒泡排序,結果關鍵字最大的記錄被安置在最后一個記錄上。
對前n-1個記錄進行第二趟冒泡排序,結果使關鍵字次大的記錄被安置在第n-1個記錄位置。重復上述過程,直到在一趟排序過程中沒有進行過交換操作”為止。
例子:
代碼實現:

void BubbleSort(vector<int> &v) { for(int i = 0; i < v.size(); ++i) { for(int j = 0;j < v.size() - i; ++j) { if(v[j] < v[j-1]) { int temp = v[j]; v[j] = v[j-1]; v[j-1] = temp; } } } }
時間復雜度為O(N^2)
空間復雜度為O(1)
冒泡排序是穩定的排序算法。
四,快速排序
快速排序基本思想:
快速排序的本質是分治法。從數列中挑出一個元素,稱為 “基准”(pivot) 分區(partition):將序列分區,使所有元素比基准值小的放在基准前面,所有元素比基准值大的放在基准的后面(相等的數可以到任一邊)(Divide) 排序:遞歸地(recursive)把小於基准值元素的子數列和大於基准值元素的子數列排序(Conquer)。每次遞歸都會把基准放在正確的位置,即每次遞歸都排好一個元素,並將小於這個元素的放左邊,大於這個元素的放右邊。
例子:
記low為起始位置,high為末位置。基准一般取R[low],R[high],R[(low+high)/2]處於中間大小的數。這里敘述方便,直接取R[0]為基准。
①首先從后半部分開始,如果掃描到的值大於基准數據就讓high減1,如果發現有元素比該基准數據的值小,就將high位置的值賦值給low位置 。
②然后開始從前往后掃描,如果掃描到的值小於基准數據就讓low加1,如果發現有元素大於基准數據的值,就再將low位置的值賦值給high位置的值,指針移動並且數據交換后的結果如下:
③繼續進行以上步驟,直到low >= high 停止掃描,此時基准R[0]處於正確的位置上。
④然后遞歸的處理左子序列和右子序列。
代碼實現:

int Parition(vector<int>&v,int low,int high) { int ref = v[low]; if(low < high) { while(low < high) { while(low < high && v[high] >= ref) { --high; } v[low] = v[high]; while(low < high && v[low] <= ref) { ++low; } v[high] = v[low]; } v[low] = ref; } return low; } void QSort(vector<int>&v,int low,int high) { if(low < high) { int index = Parition(v,low,high); QSort(v,low,index-1); QSort(v,index+1,high); } } void QuickSort(vector<int>&v) { QSort(v,0,v.size()-1); }
時間復雜度為O(NlogN)
空間復雜度為最優的是O(logN),最差的為O(N)
不穩定。
五,簡單選擇排序
簡單選擇排序基本思想:
首先通過n-1次關鍵字比較,從n個記錄中找出關鍵字最小的記錄,將它與第一個記錄交換;
再通過n-2次比較,從剩余的n-1個記錄中找出關鍵字次小的記錄,將它與第二個記錄交換;
重復上述操作,共進行n-1趟排序后,排序結束。
例子:
代碼實現:

void SelectSort(vector<int>& v) { for(int i = 0; i < v.size(); ++i) { int MIN = v[i],pos = i;; for(int j = i; j < v.size(); ++j) { if(MIN > v[j]) { MIN = v[j]; pos = j; } } v[pos] = v[i]; v[i] = MIN; } }
時間復雜度為O(N^2)
空間復雜度為O(1)
不穩定,交換過程中可能發生相同數字的次序顛倒。
六,堆排序
堆排序基本思想:
堆是一個具有這樣性質的完全二叉樹:每個非終端結點(記錄)的關鍵字大於等於(小於等於)它的孩子結點的關鍵字。堆排序是利用堆的特性對記錄序列進行排序的一種排序方法。將無序序列建成一個堆,得到關鍵字最小(或最大)的記錄;輸出堆頂的最小(大)值后,使剩余的n-1個元素重又建成一個堆,則可得到n個元素的次小值;重復執行,得到一個有序序列。因為堆是完全二叉樹,所以我們可以用數組來儲存。只要將需要排序的數組建立成堆,然后每次取出根結點,就把剩下的結點調整成堆;再取出根結點,如此下去,最后便能得到排好序的數據。
例子:
建堆:
給定需要排序的6個數字:
①保證[0,0]范圍內是個最大堆,12本身是個堆。
②保證[0,1]范圍內是個最大堆,因為34 > 12,12在34的父節點上,所以需要調換12和34
③再保證[0,3]內是個最大堆,12 54都是34的兒子,所以調換34和54
④重復以上步驟,直到[0,n-1]都是個最大堆
建堆結果是:
調整堆並排序:
成為堆以后那么heap[0]一定是該組數的最大值/最小值(這里建的是最大堆,所以heap[0]是最大值)。
①交換63和24,已知63是最大值,則63在排序后的正確位置上,我們假裝數組只有n-1個數,把63踢出去。
②然后對[0,n-2]的所以數據進行調整,挑24左右兒子最大的與其比較,54 > 24,所以交換位置。
然后12和31是交換位置后的24的左右兒子,又因為31 > 24交換24,31。
這已經是一個最大堆了,將54和24交換位置(首位交換),54處在正確的位置上。
繼續重復上述過程,直到每個數都排好。
代碼實現:

void GetHeap(vector<int>& v)//將數組v調整為最大堆 { for(int i = 0; i < v.size(); ++i) { int CurrentIndex = i;//新插入的結點 int Father = (CurrentIndex - 1) / 2; while(v[CurrentIndex] > v[Father]) { int temp = v[CurrentIndex]; v[CurrentIndex] = v[Father]; v[Father] = temp; CurrentIndex = Father; Father = (CurrentIndex - 1) / 2; } } } void AdjustHeap(vector<int>& v,int index,int size)//交換首尾元素后調整最大堆 { int right = 2*index + 2; int left = 2*index + 1; while(left < size) { int LargestIndex; if(v[left] < v[right] && right < size) { LargestIndex = right; } else { LargestIndex = left; } if(v[index] > v[LargestIndex]) { LargestIndex = index; } if(index == LargestIndex) { break; } int temp = v[LargestIndex]; v[LargestIndex] = v[index]; v[index] = temp; index = LargestIndex; left = 2*index + 1; right = 2*index + 2; } } void HeapSort(vector<int>& v) { GetHeap(v); int size = v.size(); while(size > 1) { int temp = v[0]; v[0] = v[size - 1]; v[size - 1] = temp; --size; AdjustHeap(v,0,size); } }
時間復雜度為O(NlogN)
空間復雜度為O(1)
不穩定。
七,歸並排序
歸並排序基本思想:
歸並——將兩個或兩個以上的有序表組合成一個新的有序表,叫歸並。
2-路歸並排序:將兩個位置相鄰的記錄有序子序列歸並為一個記錄的有序序列。
如果記錄無序序列 R[s..t] 的兩部分 R[s...(s+t)/2] 和 R[(s+t)/2+1..t] 分別按關鍵字有序, 則很容易將它們歸並成整個記錄序列是一個有序序列。具體看例子。
例子:
對一個序列不斷遞歸(分治法),然后兩兩歸並並排序。
代碼實現:

void MergeArray(vector<int> &v, int left, int mid, int right, int temp[])//歸並並排序 { int i = left,j = mid+1; int n = mid,m = right; int k = 0; while(i <= n && j <= m) { if(v[i] < v[j]) { temp[k++] = v[i++]; } else { temp[k++] = v[j++]; } } while(i <= n) { temp[k++] = v[i++]; } while(j <= m) { temp[k++] = v[j++]; } for(int i = 0; i < k; ++i) { v[left+i] = temp[i]; } } void Divide(vector<int>& v,int left,int right,int temp[])//分治 { if(left < right) { int mid = (left+right)/2; Divide(v,left,mid,temp); Divide(v,mid+1,right,temp); MergeArray(v,left,mid,right,temp); } } bool MergeSort(vector<int>& v) { int* temp = new int[v.size()]; if(temp == NULL) { return false; } Divide(v,0,v.size()-1,temp); delete [] temp; return true; }
時間復雜度非常穩定,最好和最差都是O(NlogN)
空間復雜度為O(N) (new了一個大小為n的數組)