您的位置:首页 > 其它

练习《算法导论》之排序:插入排序,归并排序,堆排序,快速排序

2014-03-29 15:32 453 查看
最近开始学习《算法导论(第3版)》,非常经典的厚书,从排序开始,本文目前总结了插入排序,归并排序,堆排序和快速排序,所有排序给出了用C++实现的测试代码,直接保存为Cpp文件即可运行,分享给大家,方便学习。

一.插入排序 (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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐