排序详解
排序詳解
文章目錄
- 排序詳解
- **一、選擇排序**
- **二、插入排序**
- **三、希爾排序**
- **四、堆排**
- 五、冒泡排序:
- **六、快速排序hoare版本**
- **七、快速排序 挖坑法**
- **八、快速排序前后指針版本**
- 九、快速排序優化--三數取中
- 十、快速排序----小區間優化
- 十一、快速排序非遞歸版:
- 十二、歸并排序:
一、選擇排序
代碼:
void SelectSort(int* arr,int len) {int begin = 0;int end = len - 1;while(begin < end){int min_index = begin;int max_index = end;for(int i = begin;i <= end;i++){if(arr[i] > arr[max_index]){max_index = i;}if(arr[i] < arr[min_index]){min_index = i;}}Swap(&arr[begin],&arr[min_index]);//交換的時候需要注意,如果最大值剛好在begin位置處,經過前面的交換,最大值已經杯換到最小下標處if(max_index == begin){max_index = min_index; }Swap(&arr[end],&arr[max_index]); begin++;end--;} }
思想:
- 上述代碼的選擇排序是先定義兩個下標min_index,和max_index,用來記錄每趟比較中最小元素和最大元素的下標
- 然后在循環內部進行選擇記錄,每趟循環結束min_index和max_index已經記錄該趟的最大元素和最小元素的下標,
- 然后進行交換,把最小的元素交換到數組的begin位置處,最大的元素交換到end位置處,
- 注意??在交換完最小元素后需要進行判斷,因為如果剛好最大元素就是在begin位置處,那么后序要交換最大元素到end位置處就會出錯,因為如果最大元素在begin的情況下,經過前面的交換,現在最大元素的位置應該在min_index下標處,所以應更新最大元素位置的下標
- 在元素集合array[i]–array[n-1]中選擇關鍵碼最大(小)的數據元素
- 若它不是這組元素中的最后一個(第一個)元素,則將它與這組元素中的最后一個(第一個)元素交換
- 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重復上述步驟,直到集合剩余1個元素
二、插入排序
代碼:
void InsertSort(int* arr,int len) {int end = 0;for(end =0 ;end < len -1;end ++){int temp = arr[end +1];while(end >= 0){if(arr[end] > temp){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = temp; } }
思想:
- 先定義end來標識有序序列的最后一個元素的下標,
- 再把end后的第一個元素temp往有序序列內插
- 如果temp比end位置元素大,就直接往end+1位置放temp即可
- 如果temp比end位置元素小,就把end位置元素放到end+1位置處,繼續循環向前判斷
- 元素集合越接近有序,直接插入排序算法的時間效率越高
- 時間復雜度:O(N^2)
- 空間復雜度:O(1),它是一種穩定的排序算法
- 穩定性:穩定
- 當插入第i(i>=1)個元素時,前面的array[0],array[1],…,array[i-1]已經排好序,此時用array[i]的排序碼與
array[i-1],array[i-2],…的排序碼順序進行比較,找到插入位置即將array[i]插入,原來位置上的元素順序后移
三、希爾排序
代碼:
void ShellSort(int* arr,int len) {int gap = len;while(gap > 1){gap = gap / 3 +1;for(int i = 0;i < len - gap;i++){int end = i;int temp = arr[end + gap];while(end >= 0){if(arr[end] > temp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = temp;}} }
思想:
- 定義一個gap為分得的組數,gap越大越不接近有序,所以gap應是動態變化的,直到為1,就是插入排序
- 希爾排序法又稱縮小增量法。希爾排序法的基本思想是:先選定一個整數,把待排序文件中所有記錄分成個
組,所有距離為的記錄分在同一組內,并對每一組內的記錄進行排序。然后,取,重復上述分組和排序的工
作。當到達=1時,所有記錄在統一組內排好序 - 希爾排序的時間復雜度不好計算,需要進行推導,推導出來平均時間復雜度: O(N1.3—N2)
- 穩定性:不穩定
四、堆排
代碼:
//向下調整 void adjust(int* arr,int len,int index) {int parent = index;int child = parent * 2 + 1;while(child < len){if(child + 1 < len && arr[child] < arr[child + 1]){child = child + 1;}if(arr[child] > arr[parent]){Swap(&arr[child],&arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}} }//堆排 void HeapSort(int* arr,int len) {for(int i = (len - 1 - 1 ) / 2;i >= 0;i--){adjust(arr, len, i);}int end = len - 1;while(end > 0){Swap(&arr[0],&arr[end]);adjust(arr,end,0);end--;} }
思想:
- 堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。它是通過堆來進行選擇數據。需要注意的是排升序要建大堆,排降序建小堆
- 注意向下調整算法的寫法,首先從倒數第一個非葉子節點開始調整,把整棵樹調整為大根堆
- 建大堆使,取左右兩個孩子中較大的和父親節點比較,看是否需要交換,直到把整棵樹調整為大根堆
- 排序的過程就是把堆頂元素和數組的最后一個元素進行交換,再次進行向下調整,注意此時,交換過來的堆頂元素就相當于是有序的,再次進行向下調整時就把數組的元素個數-1,一次類推,循環排序
- 堆排序使用堆來選數,效率就高了很多。
- 時間復雜度:O(N*logN)
- 空間復雜度:O(1)
- 穩定性:不穩定
五、冒泡排序:
這個比較簡單,直接來代碼:
void bubbleSort(int *str, int len) { //冒泡排序, 升序排列 //數組當中比較小的數值向下沉,數值比較大的向上浮!int i = 0;//趟數int flg ;for(i = 0;i < len-1;i++){int j = 0;//次數flg = 0;//如果第一次已經是排序好了,就不用排序了,直接brekfor(j = 0;j < len-1-i;j++){if(str[j] > str[j+1]){int tmp = str[j];str[j] = str[j+1];str[j+1] = tmp;flg = 1;}}if(!flg){break;}} }六、快速排序hoare版本
int QuickSortChild1(int* arr,int begin,int end) {int key = begin;while(begin < end){while(begin < end && arr[end] >= arr[key]){end--;}while(begin < end && arr[begin] <= arr[key]){begin++;}Swap(&arr[end],&arr[begin]);}Swap(&arr[key],&arr[begin]); return begin; }//快排 void QuickSort(int* arr,int begin,int end) {//注意遞歸出口if(begin < end){int mid = QuickSortChild1(arr,begin,end);QuickSort(arr,begin,mid - 1);QuickSort(arr,mid + 1,end);} }七、快速排序 挖坑法
int QuickSortChild2(int* arr,int begin,int end) {int key =arr[begin];while(begin < end){while(begin < end && arr[end] >= key){end--;}arr[begin] = arr[end];while(begin < end && arr[begin] <= key){begin++;}arr[end] = arr[begin];}arr[begin] = key;return begin; } void QuickSort(int* arr,int begin,int end) {//注意遞歸出口if(begin < end){int mid = QuickSortChild2(arr,begin,end);QuickSort(arr,begin,mid - 1);QuickSort(arr,mid + 1,end);} }八、快速排序前后指針版本
//雙指針法 int QuickSortChild3(int* arr,int begin,int end) {int pref = begin - 1;int cur = begin;int key = end;//定義pref,cur 。pref指向cur的前一個while(cur < end){//cur去找比key小的元素,如果找到,就把pref++,就交換cur和pref下標所在的元素if(arr[cur] < arr[key]){pref++;Swap(&arr[cur],&arr[pref]);}//如果cur所在下標的元素比key所在元素大,就直接后移curcur++;}//最后注意和end位置的元素交換的是pref的下一個,不是它自己Swap(&arr[++pref],&arr[end]);return pref; } void QuickSort(int* arr,int begin,int end) {//注意遞歸出口if(begin < end){int mid = QuickSortChild3(arr,begin,end);QuickSort(arr,begin,mid - 1);QuickSort(arr,mid + 1,end);} }
注意:雙指針法是用cur去找比key小的元素,如果找到就把pref++,交換Swap(&arr[cur],&arr[pref]);繼續循環,最后注意不是拿pref和key進行交換,而是拿pref的下一個和key進行交換Swap(&arr[++pref],&arr[end]);
九、快速排序優化–三數取中
//快排優化--->三數取中,防止有序的情況 void medianOfThree(int* arr,int begin,int end) {int mid = (begin + end) >> 1;if(arr[mid] > arr[begin]){Swap(&arr[mid],&arr[begin]);}if(arr[mid] > arr[end]){Swap(&arr[mid],&arr[end]);}if(arr[end] < arr[begin]){Swap(&arr[end],&arr[begin]);} } void QuickSort(int* arr,int begin,int end) {//注意遞歸出口if(begin < end){medianOfThree(arr,begin,end);int mid = QuickSortChild3(arr,begin,end);QuickSort(arr,begin,mid - 1);QuickSort(arr,mid + 1,end);} }十、快速排序----小區間優化
//快排優化--->小區間優化 + 三數取中 void QuickSort2(int* arr,int begin,int end) {if(begin < end){//小區間就直接使用選擇排序,注意邊界條件的控制if(end - begin + 1 < 10){SelectSort(arr + begin,end - begin + 1);}else{medianOfThree(arr,begin,end);int mid = QuickSortChild3(arr,begin,end);QuickSort1(arr,begin,mid - 1);QuickSort1(arr,mid + 1,end);}} }十一、快速排序非遞歸版:
void QuickSort(int* arr,int left,int right) {stack<int> s;s.push(left);s.push(right);while(!s.empty()){int end = s.top();s.pop();int begin = s.top();s.pop();int mid = QuickChild(arr,begin,end);//cout<<"i am quicksort"<<endl;if(begin < mid - 1){s.push(begin);s.push(mid - 1);}if(mid + 1 < end){s.push(mid + 1);s.push(end);}}for(int i = 0;i <= right;i++){cout<<arr[i]<<" < ";}cout<<endl; }十二、歸并排序:
//歸并排序 void MergeSortChild(int* arr,int begin,int end,int* temp) {if(begin == end){return ;}int mid = (begin + end) >> 1;MergeSortChild(arr,begin,mid,temp);MergeSortChild(arr,mid + 1,end,temp);int begin1 = begin;int end1 = mid;int begin2 = mid + 1;int end2 = end;int index = begin;while(begin1 <= end1 && begin2 <= end2 ){if(arr[begin1] < arr[begin2]){temp[index] = arr[begin1];index++;begin1++;}else {temp[index] = arr[begin2];index++;begin2++;}}while(begin1 <= end1){temp[index] = arr[begin1];index++;begin1++;}while(begin2 <= end2){temp[index] = arr[begin2];index++;begin2++;}memcpy(arr + begin,temp + begin,sizeof(int) * (end - begin + 1)); }void MergeSort(int* arr,int n) {int* temp = (int*)malloc(sizeof(int) * n);MergeSortChild(arr,0,n - 1,temp); }
基本思想:
-
歸并排序(MERGE-SORT)是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide and
Conquer)的一個非常典型的應用。將已有序的子序列合并,得到完全有序的序列;即先使每個子序列有
序,再使子序列段間有序。若將兩個有序表合并成一個有序表,稱為二路歸并。 歸并排序核心步驟 -
歸并的缺點在于需要O(N)的空間復雜度,歸并排序的思考更多的是解決在磁盤中的外排序問題。
-
時間復雜度:O(N*logN)
-
空間復雜度:O(N)
-
穩定性:穩定
總結