《算法导论》学习总结——第二部分3快速排序

        曾经在程序员杂志上看到快速排序的作者,Hoare,曾经的图灵奖获得者啊,牛光闪闪的。不过当时,对快速排序什么的,印象不算深刻,毕竟没好好学。记得当时杂志上说到的是,快速排序,应该是目前最快的内部排序算法(虽然独立到语言上,C++的sort会比调用快速排序快)。现在就进入快速排序的美好复习吧。

        与归并排序类似,快排也用分治模式。主要是三个步骤:

     1)分解:将数组A[p....r]划分为2个子数组A[p....q-1]和A[q+1....r],使前一个每个元素都小于A[q],后一个数组,每个元素都大于A[q](q在划分过程中计算)

     2)解决:递归调用快速排序,对2个子数组进行排序

     3)合并:因为2个子数组是就地排序,所以合并不用操作,数组已排序

      看到这个合并,就想到啊,和归并比,一个从小到大,一个从大到小,差距就是这么大,快排么得合并开销,一下就省了很多啊,说明,方向很重要啊,如同那句,同样一个B,S与N的差别,大家都懂的。

      快速排序的实现代码如下:

//===============================================================
// Name        : Qsort.cpp
// Author      : xia
// Copyright   : NUAA
// Description : 快速排序的实现
//===============================================================
#include <iostream>
#include <vector>
#include <fstream>
#include <algorithm>
#include <ctime>

using namespace std;
const int MAX = 1000 ;

void WriteToFile(vector<int> v)
{//将v写入文件,纯看排序结果是否正确,也可以写个test()
	int i;
	ofstream result("Qsort.txt");
	if (result.fail())
	{
		cout << " open data error " << endl;
		exit(EXIT_FAILURE);
	}
	for (i=0 ; i<v.size() ; i++)
	{
		result << v[i] << " " ;
	}
	result.close();
}
int Partion(vector<int> &A,int p ,int r)
{//数组划分
	int x=A[r];//x都感觉没用
	int i=p-1;
	for (int j=p ; j<r ;j++)
	{
		if ( A[j] <= x )
		{
			i++;
			swap(A[i],A[j]);
		}
	}
	swap(A[i+1],A[r]);
	return i+1;
}
void Qsort(vector<int> &A, int p ,int r)
{//递归快排
	if (p < r)
	{
		int q = Partion(A,p,r);
		Qsort(A,p,q-1);
		Qsort(A,q+1,r);
	}
}
int main(int argc, char **argv)
{
	vector<int> v;
	int i;
	for (i=0 ; i< MAX ;i++)
		v.push_back(i);
	random_shuffle(v.begin(),v.end());//打乱

	Qsort(v,0,v.size()-1);
	WriteToFile(v);

	return 0;
}
      说到代码,很惭愧的, http://www.cnblogs.com/chinazhangjie/archive/2010/12/09/1901491.html张杰同学的c++模板类实现,(这里也得感谢Tanky Woo,看了他的总结,得以看很多的链接),果然我写的只是C的扩充啊,像用vector只是防止溢出,也就是数组来使用,一些操作也只是作为一个扩充。

      继续说正题,这里生成随机数没有用常规的C语言的srand()和rand()(参考http://blog.sina.com.cn/s/blog_42af30e4010002qo.html)由于以下两个原因:

     1)做格式化时,结果常常是扭曲的,所以得不到正确的随机数(如某些数的出现频率要高于其它数)

     2)rand()只支持整型数;不能用它来产生随机字符,浮点数,字符串或数据库中的记录

     所以采用了STL函数random_shuffle(),先随机生成0到MAX-1的随机数,用random_shuffle()打乱,再进行排序。


     另外, 其实Hoare老师用的快排并不是如上代码所示,也就是说,最原始的快速排序,是这样滴:

int HoarePartion(vector<int> &A, int p , int r)
{
	int x=A[p];
	int i=p-1;
	int j=r+1;
	while (1)
	{
		while (A[--j] > x)
			;
		while (A[++i] < x)
			;
		if (i<j)
			swap(A[i],A[j]);
		else
			return j;
	}
}

void Qsort(vector<int> &A, int p ,int r)
{//递归快排
	if (p < r)
	{
		int q = HoarePartion(A,p,r);
		Qsort(A,p,q);
		Qsort(A,q+1,r);
	}
}

     也可以参考: http://tayoto.blog.hexun.com/25048556_d.html ,区别只是我的代码直接while里面用A[--j],可读性不高,因为着实不喜欢do-while结构。

     对于最原始的快排,严蔚敏老师的《数据结构》是这样实现的:

int Partion(vector<int> &v ,int low ,int high)
{//对vector进行划分,返回枢轴下标
	int pivotkey;
	pivotkey = v[low] ;
	while ( low < high )
	{
		while (low < high && v[high] >= pivotkey)
			high -- ;
		v[low] = v[high];
		while (low < high && v[low] <= pivotkey )
			low ++ ;
		v[high] = v[low];	
	}
	v[low] = pivotkey ;
	return low ;
}
void quickSort(vector<int> &number ,int left ,int right)
{
	if ( left < right )
	{
		int i = Partion(number , left, right) ; 
		quickSort(number, left, i-1);   // 对左边进行递归 
		quickSort(number, i+1, right);  // 对右边进行递归 	
	}
}
      当然,区别都只是在划分的过程,毕竟分治,才是快排的精髓嘛,不过这俩大同小异。

      快排的运行时间,显然与划分是否对称有关,要是直接划分出来,是一个最不均衡的二叉树,那就够喝一壶的了,跟插入排序似的。下面网址有说法,是快排隐藏的二叉排序树思想,其实可以参考,虽然只是个人理解http://bbs.chinaunix.net/viewthread.php?tid=1011316。其实说到二叉,堆排序不也是吗?区别只是堆排序显式的建堆,也就构成了一笔不小的开销,如果考虑隐藏排序二叉树的话,倒是可以理解为毛快排快于堆排。

      由于快排平均情况下效果显然很良好,那么怎么得到平均情况就是个值得思考的问题,所以书上给出了,在划分的时候,随机获取一个数作为枢轴,而不是用我们的A[low]。于是我们得到了快排的随机化版本如下:

      

int Partion(vector<int> &A,int p ,int r)
{//数组划分
	int x=A[r];
	int i=p-1;
	for (int j=p ; j<r ;j++)
	{
		if ( A[j] <= x )
		{
			i++;
			swap(A[i],A[j]);
		}
	}
	swap(A[i+1],A[r]);
	return i+1;
}
int RandomPartion(vector<int> &A,int p ,int r)
{//在A[p]到A[r]中随机划分
	int i= p + rand()%(r-p+1); //i<-RANDOM(p,r)
	swap(A[r],A[i]);
	return Partion(A,p,r);
}
void RandomQsort(vector<int> &A, int p ,int r)
{//递归快排
	if (p < r)
	{
		int q = RandomPartion(A,p,r);
		RandomQsort(A,p,q-1);
		RandomQsort(A,q+1,r);
	}
}
      与常规快排的区别,就是在划分的时候,获取一个随机数下标,再用其数组中的值作为枢轴,当然,这样就充分考虑平均性能了。

      还有一种改进RANDOM-QUICKSORT的方法,就是根据从子数组更仔细地选择的(而不是随机选择的元素)作为枢轴来划分。常用的做法是三数取中。可以参考:

http://blog.csdn.net/zhanglei8893/article/details/6266915

      本章最后还提到个很蛋疼的Stooge排序,实现如下:

void StoogeSort(vector<int> &A, int i ,int j)
{//递归快排
	if (A[i] > A[j])
		swap(A[i],A[j]);
	if (i+1 >=j)
		return;
	int k = (j-i+1)/3;
	StoogeSort(A,i,j-k);//前2/3
	StoogeSort(A,i+k,j);//后2/3
	StoogeSort(A,i,j-k);//又前2/3
// 	StoogeSort(A,i,i+k-1);//  如果采用1/3排不出来啊
}
    

       对于数组A[i...j],STOOGE-SORT算法将这个数组划分成均等的3份,分别用A, B, C表示。第8、9行从宏观上来看它进行了两趟,结果是最大的1/3到了C,最小的1/3到了B,从宏观上来看,整个数组的三个大块就有序了,再进行递归,整个数组就有序了。第8和第9行,可以看做一个冒泡过程。

       不过从运行时间的测试来讲,很不给力(具体数据就不列了)。STOOGE-SORT最坏情况下的运行时间的递归式   T(n) = 2T(2n/3)+Θ(1)  由主定律可以求得T(n)=n^2.71,相比插入排序、快速排序的Θ(n^2)和 堆排序、合并排序的Θ(nlgn),不给力啊。参考自:http://blog.csdn.net/zhanglei8893/article/details/6235294

       本章最后, 练习7-4还提出个尾递归的概念,起因是QuickSort的第二次递归调用不是必须的,可以用迭代控制结构来替代。如:

QUICKSORT'(A, p, r)

1  while p < r

2        do  Partition and sort left subarray.

3             q ← PARTITION(A, p, r)

4             QUICKSORT'(A, p, q - 1)

5             p ← q + 1

     具体 有效性的证明可以参考: http://blog.csdn.net/zhanglei8893/article/details/6236792,需要说明的是,当数组正序时,其递归深度和栈深度都为 Θ(n)。

    突然记笔记到这,发现《算法导论》的练习题,很给力啊,好书,真不是盖的。啃起来不是那么容易,不过得继续了


      菜鸟goes on ~~~下面解决flex读取和修改配置文件的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值