算法導論中常見的四種排序
by方陽
版權聲明:本文為博主原創文章,轉載請指明轉載地址
http://www.cnblogs.com/fydeblog/p/7067382.html
1. 前言
好久沒寫博客了,今天來一篇最近開始看的算法導論,這篇博客主要介紹插入排序,歸並排序,堆排序和快速排序的原理,性能分析以及程序實現!廢話不多說,let's go!
2. 原理解析
2.1 插入排序
參考下面的圖片,再想想我們平時玩撲克牌,它的基本思路就清晰多了,假設待排序的記錄存放在數組R[1..n]中。初始時,R[1]自成1個有序區,無序區為R[2..n]。從i=2起直至i=n為止,依次將R[i]插入當前的有序區R[1..i-1]中,生成含n個記錄的有序區。

2.2 歸並排序
參考下面的圖片(從pdf上截的,有點歪),可以看出是將原數組進行分解,然后排序,在進行合並,歸並排序是分治(Divide and conquer)策略的一個很好的例子,將復雜的問題分成許多小問題,分開解決,然后合並。

2.3 堆排序
堆排序涉及到的知識比較多,它主要分為三部分:MAX-HESPIFY,BUILD-MAX-HEAP 和 HEAPSORT
MAX-HESPIFY:維持最大堆的性質。
BUILD-MAX-HEAP:將無序的數據數組構造成一個最大堆。
HEAPSORT:對一個數組進行原址排序。
這里面涉及蠻多知識的,下面分別介紹一下!
最大堆的定義:在最大堆中,最大堆特性是指除了根以外的每個結點i,有A[PARENT(i)] >= A[i]。這樣,堆的最大元素就存放在根結點中。
那下面的圖片來說,就是序號1為根結點(也叫父結點),要大於序號2和3的元素(2,3也叫子類結點,左子結點和右子結點),其他結點類似按照這個來推

原址排序的定義:在排序輸入數組時,只有常數個元素被存放到數組以外的空間中去。就是說不需要花數組那樣大的空間來緩存數據,大部分排序都在數組內部進行!
MAX-HESPIFY(過程見下圖,詳細看代碼)

BUILD-MAX-HEAP(過程見下圖,詳細看代碼)

HEAPSORT(過程見下圖,詳細看代碼)

2.4 快速排序
快速排序使用分治策略來把一個數組分為兩個子數組。
步驟為:
- 從數組中挑出一個元素,稱為 "基准"(pivot),
- 重新排序數組,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分割之后,該基准是它的最后位置。這個稱之為分割(partition)操作。
- 遞歸地(recursive)把小於基准值元素的子數組和大於基准值元素的子數組排序。
如下圖所示

3.性能分析
3.1 運行時間
從圖可以看出,在n很大時,歸並排序和堆排序(它們接近最優)運行時間上比插入排序和快速排序,n值小時,插入和快排較快。實際應用中,快排用的較多,它一般快於堆排序。
歸並排序和堆排序都是以二叉樹形式,它的高度是lgn,每一層都要進行n次比較,所以最后最壞結果都是nlgn,而插入排序最壞的情況是每一個都要去比較,即n的平方,快排最壞的情況是分組一邊0個元素,另一邊是n-1,平均情況還是類似歸並排序,也是分治法的最優情況,nlgn。

3.2 存儲空間
插入排序和快速排序是原址排序算法,歸並排序和堆排序不是,所以插入排序和快速排序占用空間小,更節省內存。
4. 程序實現
4.1. main.cpp
1 #include <iostream> 2 #include "fy_sort.h" 3 using namespace std; 4 5 6 int main() 7 { 8 fy_sort sort1; 9 int num; 10 int a[30],b[30],c[30],d[30]; 11 cout<<"please enter the number of input elements: "<<endl; 12 cin >> num; 13 cout << "Input the elements:\n"; 14 for(int i=0;i<num;i++) 15 { 16 cin>>a[i]; 17 b[i]=a[i]; 18 c[i]=a[i]; 19 d[i]=a[i]; 20 } 21 cout << "insert_sort:\n"; 22 sort1.insert_sort(a,num);//插入排序 23 cout << "merge_sort:\n"; 24 sort1.merge_sort(b,0,num-1);//歸並排序 25 sort1.display(b,num); 26 cout << "heap_sort:\n"; 27 sort1.heap_sort(c,num);//堆排序 28 cout << "quick_sort:\n"; 29 sort1.quick_sort(d,0,num-1);//快速排序 30 sort1.display(d,num); 31 32 return 0; 33 }
4.2 fy_sort.h
1 #pragma once 2 3 #ifndef FY_SORT_H 4 #define FY_SORT_H 5 6 class fy_sort //排序類 7 { 8 private: 9 10 void swap (int& n, int& m); //交換函數 11 void merge(int a[],int p,int q,int r); //歸並排序函數 12 inline int left (int i) {return 2 * i + 1;}//內聯函數,用於堆排序的左手序號 13 inline int right (int i){return 2 *(i + 1);}//內聯函數,用於堆排序的右手序號 14 void max_heapify (int a[], int i); //最大堆排序中的核心,維護最大堆的性質,即父類節點大於子類節點 15 void build_max_heap (int a[], int n); //建造最大堆,調用n/2次max_heapify,將無序的數組構造成最大堆 16 int Partition(int a[], int beg, int end); //快排的核心,即分割,將大於數組最后一個元素的元素移到右邊,小於的移到左邊 17 18 public: 19 20 void display(const int a[],int k); //顯示函數 21 void insert_sort(int a[],int n); //插入排序 22 void quick_sort(int a[],int beg, int end);//快速排序(遞歸調用) 23 void merge_sort(int a[],int p,int r); //歸並排序(遞歸調用) 24 void heap_sort(int a[],int n); //堆排序 25 26 }; 27 28 #endif
4.3 fy_sort.cpp
1 #include "fy_sort.h" 2 #include<iostream> 3 4 using namespace std; 5 6 int heap_size; 7 //顯示函數 8 void fy_sort::display(const int a[],int k) 9 { 10 for(int i=0;i<k;i++) 11 cout<<a[i]<<" "; 12 cout<<endl; 13 } 14 //交換函數 15 void fy_sort::swap (int& n, int& m) 16 { 17 int temp; 18 temp = n; 19 n = m; 20 m = temp; 21 } 22 23 void fy_sort::insert_sort(int a[],int k)//插入排序 24 { 25 int key; 26 for (int i=1;i<k;i++) 27 { 28 key=a[i]; //先將要比較的元素暫存,后面要替換 29 int j=i-1; 30 //循環實現的功能找到比a[i]大的前面元素(序號小的),找到替換,無則不變 31 while((j>-1)&&(key<a[j])) 32 { 33 a[j+1]=a[j]; 34 j--; 35 } 36 a[j+1]=key; 37 } 38 display(a,k); 39 } 40 41 void fy_sort::merge_sort(int a[],int p,int r) //歸並排序 42 { 43 if(p<r) 44 { 45 int q=(p+r)/2; //以q值為划分區間,將數組划分成兩個小數組 46 merge_sort(a,p,q); //小數組再划分,遞歸調用,直到剩下一個 47 merge_sort(a,q+1,r);//小數組再划分,遞歸調用,直到剩下一個 48 merge(a,p,q,r); //將分解后的最小數組,進行排序和合並 49 } 50 51 } 52 53 54 void fy_sort::merge(int a[],int p,int q,int r) 55 { 56 int n1 = q-p+1; 57 int n2 = r-q; 58 //生成兩個新空間,存划分后的數組 59 int *L = new int[n1+1]; 60 int *R = new int[n2+1]; 61 int i, j, k; 62 //將大數組的值賦給兩個小數組 63 for (i=0; i<n1; i++){ 64 L[i] = a[p+i]; 65 } 66 for (j=0; j<n2; j++){ 67 R[j] = a[q+j+1]; 68 } 69 //確定邊界 70 L[n1] = 10000000; 71 R[n2] = 10000000; 72 //按位比較,在合並成大數組 73 for (i=0, j=0, k=p; k<=r; k++) 74 { 75 if (L[i]<=R[j]) 76 { 77 a[k] = L[i]; 78 i++; 79 }else{ 80 a[k] = R[j]; 81 j++; 82 } 83 } 84 //釋放空間 85 delete []L; 86 delete []R; 87 } 88 89 void fy_sort::max_heapify (int a[], int i) 90 { 91 //左手和右手的序號 92 int l = left(i); 93 int r = right(i); 94 //largest存儲父節點和子節點中最大的數 95 int largest; 96 //子類左結點與父節點比較,將其中大的賦給largest 97 if ((l < heap_size) && a[l] > a[i]) 98 largest = l; 99 else 100 largest = i; 101 //子類左結點與largest比較,將其中大的賦給largest 102 if ((r < heap_size) && a[r] > a[largest]) 103 largest = r; 104 //若根節點變化,會導致下面分支的最大堆性質可能變化,這里對下面進行重新分堆 105 if (largest != i) 106 { 107 swap (a[largest], a[i]); 108 max_heapify (a, largest); 109 } 110 } 111 112 void fy_sort::build_max_heap (int a[], int n) 113 { 114 heap_size=n; 115 //記住一點,最大堆是以由底向上分堆 116 for (int i = (n - 1) / 2; i >= 0; i--) 117 max_heapify (a, i); 118 } 119 120 void fy_sort::heap_sort (int a[], int n) 121 { 122 build_max_heap (a, n); 123 //實現的是原址排序,將最大堆轉化成從小到大的數組 124 //原理是將最大堆的根節點換成最大堆最末尾的數,然后再進行最大堆 125 //直到只剩下一個節點,即為最小值 126 for (int i = n - 1; i >= 1; i--) 127 { 128 swap (a[i], a[0]); 129 heap_size--; 130 max_heapify (a, 0); 131 } 132 display(a,n); 133 } 134 //快速排序的核心,也跟堆排序一樣,是原址排序 135 int fy_sort::Partition(int a[], int beg, int end) 136 { 137 int sentinel = a[end]; 138 int i = beg-1; 139 //這個循環以最后一個為基准,小於a[end]的放到左邊 140 //大於a[end]放到右邊 141 for(int j=beg; j<=end-1; ++j) 142 { 143 if(a[j] <= sentinel) 144 { 145 i++; 146 swap(a[i], a[j]); 147 } 148 } 149 swap(a[i+1], a[end]); 150 151 return i+1; 152 } 153 154 void fy_sort::quick_sort(int a[], int beg, int end) 155 { 156 if(beg < end) 157 { 158 int pivot = Partition(a, beg, end); 159 //后邊這兩個是對一部分划分的左右部分在進行快排 160 quick_sort(a, beg, pivot-1); 161 quick_sort(a, pivot+1, end); 162 } 163 164 }
5.運行結果

6.結束語
建議大家去看算法導論,里面講的非常細,就是太厚了,容易看不下去!(笑哭)挑重點看吧,下次見!
這里再分享兩位網友的筆記,寫得很好!
Tanky Woo: 《算法導論》學習總結—【目錄】
Adoo : 《算法導論》筆記匯總
