练习《算法导论》之排序:插入排序,归并排序,堆排序,快速排序
2014-03-29 15:32
453 查看
最近开始学习《算法导论(第3版)》,非常经典的厚书,从排序开始,本文目前总结了插入排序,归并排序,堆排序和快速排序,所有排序给出了用C++实现的测试代码,直接保存为Cpp文件即可运行,分享给大家,方便学习。
插入排序是最简单的排序,很容易理解,时间复杂度最高,是O(n^2)。
C++实现如下:
分解 + 解决 + 合并
MERGE-SORT(A, p, r)
关键是合并两个已排序的序列,通过写一个辅助函数MERGE(A, p, q, r) 来完成这个合并功能。
将A划分为L[] 和R[]两个数组,分别自然排序;
在堆底(即最大值后面)设置哨兵牌∞
从p到r分别取两堆中较小牌到A[]中
归并排序的时间复杂度为O(n*lgn)。
归并排序应用:查找数组中的逆序对
与归并一样,时间复杂度是O(nlgn);
与插入一样,有空间原址性,只需要常数个额外的元素空间存储临时数据。
二叉堆是可以看成一个完全二叉树。
因为最大元素总在根节点处,把它与A
互换;
从堆中去掉节点n(通过减少A.heapsize),剩余节点中原来根的孩子节点仍然是最大堆,新的根节点则不一定;
所以需要调用max_heapify()来维护其最大堆性质; n-1次调用O(lgn)
重复2~4步骤,直到全部排好。
下图可以作为中间示意图,浅灰色为确定小于pivot已排好部分,深灰色为确定大于pivot已排好的部分,白色为尚未确定的部分,直到白色只剩一个时停止,将pivot值填入白色空位。
冒泡和快排都是交换排序,无需申请新的空间。
快排和归并都是递归的,复杂度nlogn。
一.插入排序 (InsertionSort)
《算法导论》中给出的例子是许多人抓拍时排序手中扑克牌。开始左手为空且桌面上的牌面向下,每次拿到一张牌要在手中从右向左(从大到小)与手中每张牌比较。拿在手中的牌总是排序好的,原来这些牌是桌子上牌堆顶部的牌。插入排序是最简单的排序,很容易理解,时间复杂度最高,是O(n^2)。
C++实现如下:
#include <iostream> using namespace std; int main() { int N; cin>>N; if(N) { int *a = new int ; for(int i = 0; i < N; i++ ) { int cur; cin>>cur; int k; for(k = i-1; k >= 0 && a[k] > cur; k--) { a[k+1] = a[k]; } a[k+1] = cur; } cout<<a[0]; for(int i = 1; i < N; i++) { cout<<' '<<a[i]; } } return 0; }
二. 归并排序(MergeSort)
分治法 (Divde and Conquer)
分治法(Divide and Conquer)的概念在递归的求解子问题时提出。分治模式在每层递归时都有三个步骤:分解 + 解决 + 合并
归并排序
化为合并两堆牌面朝上,已排序且最小牌在上的两堆扑克牌问题。MERGE-SORT(A, p, r)
if(p < r){ int q = (p+r)/2; MERGE_SORT(A,p,q); MERGE_SORT(A,q+1,r); MERGE(A,p,q,r); }
关键是合并两个已排序的序列,通过写一个辅助函数MERGE(A, p, q, r) 来完成这个合并功能。
将A划分为L[] 和R[]两个数组,分别自然排序;
在堆底(即最大值后面)设置哨兵牌∞
从p到r分别取两堆中较小牌到A[]中
归并排序的时间复杂度为O(n*lgn)。
#include <vector> using std::vector; #include <iostream> using namespace std; void MERGE(vector <int> *A, int p, int q, int r){ //initialize Left and Right Array, which are both already sorted vector <int> Left; vector <int> Right; Left.resize(q-p+1); Right.resize(r-q); for(int i=0; i<Left.size() ;i++) Left.at(i) = A->at(p + i); //A is not started from 0 for(int j=0; j<Right.size() ;j++) Right.at(j) = A->at(q+1 + j); Left.push_back(100); Right.push_back(100); //compare each Array to choose smaller one to A int j=p; int i_left = 0, i_right = 0; while( j <= r ){ if(Left.at(i_left) <= Right.at(i_right)) A->at(j++) = Left.at(i_left++); else A->at(j++) = Right.at(i_right++); } } void MERGE_SORT(vector <int> *A, int p, int r) //no return { if(p==r) {//A_out.at(p)=A.at(p); } else if(p<r) { int q = (p+r)/2; MERGE_SORT(A,p,q); MERGE_SORT(A,q+1,r); MERGE(A,p,q,r); } } void main() { int Array[]={9,3,6,1,4,1,12}; vector <int> A; for(int i=0; i<7; i++ ) A.push_back(Array[i]); MERGE_SORT(&A,0,6); //result = MERGE(A,0,2,6); for(int i=0; i<A.size();i++) cout<<" "<<A.at(i); system("pause"); }
归并排序应用:查找数组中的逆序对
int merge(int a[], int temp[], const int l, const int r, const int end) { //合并两个已排序好的序列 int i = l; int left = l; int right = r; int cnt = 0; while(left<r && right <= end) { while(a[left] <= a[right] && left < r) { temp[i++] = a[left++]; } while(a[left]>a[right] && right <= end) { temp[i++] = a[right++]; cnt += r - left; } } if( right > end ) { while( i <= end ) temp[i++] = a[left++]; } else if( left >= r ) { while( i <= end ) temp[i++] = a[right++]; } //*************************** for(int j = l; j <= end; ++j ) { a[j] = temp[j]; } return cnt; } int MergeSortIter(int a[], int temp[], int start, int end) { if(start==end) { temp[start] = a[start]; return 0; } else if(start<end) { int len = (end - start)/2; int left = MergeSortIter(a, temp, start, start+len); int right = MergeSortIter(a, temp, start+len+1, end); int cur = merge(a, temp, start, start+len+1, end); return left+right+cur; } } int InversePairs(int a[], int n) { int res = 0; if(n<=1) return res; int * temp = new int ; return MergeSortIter(a, temp, 0, n-1); //swap(a, temp); }
三. 堆排序(HeapSort)
简介
堆排序集合了插入排序和归并排序两种算法的优点:与归并一样,时间复杂度是O(nlgn);
与插入一样,有空间原址性,只需要常数个额外的元素空间存储临时数据。
二叉堆是可以看成一个完全二叉树。
最大堆
除了根节点以外的所有节点都要满足A[parent(i)] >= A[i] 。我们可以在线性时间内把一个无序数组构造成一个最大堆。下面程序中,build_max_heap() 完成这一过程。#include <iostream> #include <vector> using std::vector; using namespace std; class MyHeap{ private: int heapsize; vector<int> heap; public: MyHeap(); ~MyHeap(); void build_max_heap(); void max_heapify(int i); void heapsort(); }; MyHeap::MyHeap(){ heap.push_back(100); int Array[7]={9,3,6,1,4,1,12}; heapsize = sizeof(Array)/sizeof(int); for(int i=0; i<heapsize; i++) { heap.push_back(Array[i]); } } MyHeap::~MyHeap(){ } /*bottoms up, max heap 最大堆 e.g. 12 / \ 4 9 / \ / \ 1 3 1 6 */ void MyHeap::build_max_heap(){ for(int i= heapsize/2; i>0; i--) { max_heapify(i); } } //Suppose left_child(i) and right_child(i) are both max heaps. // This function keeps heap i also a max heap. void MyHeap::max_heapify(int i){ int largest = 0; int left = 2*i; int right = 2*i + 1 ; //compare the three data: index, left and right. //and put the smallest right; the largest root. if( left <= heapsize && heap[i] < heap[left] ) largest = left; else largest = i; if( right <= heapsize && heap[largest] < heap[right] ) largest = right; if( largest != i ) { int temp = heap[largest]; heap[largest] = heap[i]; heap[i] = temp; max_heapify(largest); } } void main(){ MyHeap data; data.build_max_heap(); //data.heapsort(); }
堆排序
先把堆化成最大堆; 时间复杂度O(n)因为最大元素总在根节点处,把它与A
互换;
从堆中去掉节点n(通过减少A.heapsize),剩余节点中原来根的孩子节点仍然是最大堆,新的根节点则不一定;
所以需要调用max_heapify()来维护其最大堆性质; n-1次调用O(lgn)
重复2~4步骤,直到全部排好。
void MyHeap::heapsort(){ build_max_heap(); for(int i = heap.size()-1; i>1 ; i--) { int temp = heap.at(1); heap.at(1) = heap.at(i); heap.at(i) = temp; this->heapsize--; max_heapify(1); } }
四. 快速排序(QuickSort)
快速排序也采用了分治思想,算法的关键是Partition过程,它使选定的主元(pivot element)左边都比它小,右边都比它大, 并返回排好后主元的位置。具体实现
全部代码如下:#include <iostream> using namespace std; int Partition(int A[], int start, int end) { int pivot = A[start]; //tempvalue pivot int i= start, j= end ; while(i<j) { while(A[j] >= pivot && i<j ) //notice , this is not if, scan all over j--; //only if the pivot is less then the right element, pointer-- A[i] = A[j] ; //element moves every time while( A[i] <= pivot && i<j ) i++; A[j] = A[i]; //直到 >pivot了,停止,把这个补到后面的空位 } A[i] = pivot; return i; } void QuickSort(int A[], int i, int j) { if(i<j) { int pivot = Partition(A, i, j); QuickSort(A, i, pivot - 1); QuickSort(A, pivot + 1, j); } } void main() { int A[] = {0, 23, 3, 5,46, 23, 20,88}; //instead of store middle value in A[0], we use a tempvalue int Length = sizeof(A)/sizeof(int); QuickSort(A, 0, Length-1); }
Partition说明
注意在Partition内部,将选定的轴值存为中间变量(tempvalue pivot),先从右向左扫描,如果一直比轴值大,指针就一直左移,这对于本来已经大体有序的序列,就避免了大幅改动,如本例中第一个循环的A[0]= 0; 一旦不满足了就把空位填上,因为这里恰好使用初始位置的值作为轴值,所以刚好空位处在较小位置;最终的效果将是空位在上未排好的部分左右来回移动,直到全部拍好。所以这里只能用while而不是if。下图可以作为中间示意图,浅灰色为确定小于pivot已排好部分,深灰色为确定大于pivot已排好的部分,白色为尚未确定的部分,直到白色只剩一个时停止,将pivot值填入白色空位。
五. 冒泡排序(BubbleSort)
冒泡排序每排一次最大的泡沉到底,已排序部分无需再排。冒泡和快排都是交换排序,无需申请新的空间。
快排和归并都是递归的,复杂度nlogn。
相关文章推荐
- 牛客网Java刷题知识点之插入排序(直接插入排序和希尔排序)、选择排序(直接选择排序和堆排序)、冒泡排序、快速排序、归并排序和基数排序(博主推荐)
- java实现七种排序 (插入排序, 希尔排序, 插入排序, 快速排序, 简单选择排序, 堆排序, 归并排序)
- C++排序:冒泡排序,简单选择排序,直接插入排序,希尔排序,堆排序,归并排序,快速排序
- 插入排序、冒泡排序、选择排序、希尔排序、快速排序、归并排序、堆排序和LST基数排序的C++代码实现
- 插入排序、冒泡排序、选择排序、希尔排序、快速排序、归并排序、堆排序和LST基数排序——JAVA实现
- 【Java】八个常用的排序算法:插入排序、冒泡排序、选择排序、希尔排序 、快速排序、归并排序、堆排序和LST基数排序
- 八大排序算法:简单插入排序、冒泡排序、希尔排序、快速排序、堆排序、归并排序等总结。
- c++: 直接插入排序,冒泡排序,快速排序,堆排序和归并排序
- 比较排序总结——直接插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序
- java实现各种基础排序(冒泡排序、快速排序、直接选择排序、堆排序、直接插入排序、归并排序)
- 直接插入排序,冒泡排序,快速排序,简单选择排序,堆排序,2-路归并排序,文件存储
- python排序算法-冒泡排序,选择排序,直接插入排序,希尔排序,归并排序,快速排序,堆排序
- 八大排序方法汇总(选择排序,插入排序-简单插入排序、shell排序,交换排序-冒泡排序、快速排序、堆排序,归并排序,计数排序)
- 排序总结:插入(简单和改进)、希尔、选择、冒泡、快速、堆排序、归并排序
- java五种内部排序(直接插入排序、希尔排序、快速排序、堆排序、归并排序)
- 【更新】排序算法比较:插入排序,冒泡排序,归并排序,堆排序,快速排序,计数排序,基数排序,桶排序
- 数据结构6-排序算法(直接插入排序、希尔排序、快速排序、归并排序和堆排序)
- 排序算法复习(Java实现):插入,冒泡,选择,Shell,快速排序, 归并排序,堆排序,桶式排序,基数排序
- 六、内部排序综合(九种)—插入类排序(直接插入、折半插入、希尔排序);交换类排序(冒泡、快速);选择类排序(简单选择、堆排序);二路归并排序;基数排序
- 冒泡排序 快速排序 选择排序 堆排序 直接插入排序 希尔排序 归并排序