排序(《编程珠玑》第11章)

下面分别介绍插入排序,快速排序,选择排序和希尔排序,堆排序在第十四章介绍。

其中选择排序是不稳定的,时间复杂度是O(n^2),输入不敏感,即使是输入序列有序,仍然需要O(n^2)时间。插入排序是稳定的,时间复杂度是O(n^2),但是在数组是接近有序的情况下,选择排序将会很快,因此是输入敏感型的算法。希尔排序是插入排序的一个快速版本,时间复杂度是O(n^3/4),也是输入敏感型的,当输入数据已经排序好时,其复杂度为O(n),是不稳定的排序,因为其交换间隔为h,因此可能会破坏稳定性。选择排序时间复杂度为O(nlogn),是不稳定的排序。

插入排序,希尔排序和选择排序没有什么特别的地方,程序也很常规,重点讨论一下快速排序。

快速排序在这里总共给了三个版本,quickSort,quickSort1和quickSort2。其中quickSort是最简单的一个版本,从左到右遇到一个小于A[0]的就交换,比较简单,但是这里也有问题,就是如果A[N]中都是相同的数,则每次必然只有第一个数会就位,这样就会出现严重的不平衡性,导致算法降低到O(n^2),因此用了一个改进的算法。quickSort1是一个改进的版本,该版本在循环中没有用到swap,只是赋值,因此速度很快,而且当与A[0]值相等的时候也进行赋值交换,这样虽然赋值交换的次数增加了,但是使得随后的两个递归调用更加平衡,从而使总体的时间复杂度仍然在O(nlogn),这个可以把<和>号改成<=和>=来尝试改后的版本,用A[N]都是相同的数来测试,将耗时非常长。quickSort2是《编程珠玑》第11章中讲的算法,这个和quickSort1类似。

当n<10时,一般采用插入排序会有较好的性能,因为此时n较小,而用快速排序时递归的调用会浪费时间和空间。而且,快排由于需要递归调用,在VS2010中,当N=100000时,就会出现栈溢出的错误。而用迭代版的选择或者希尔排序则可以正常运行。

再说一下调试时的方法:看了《编程珠玑》中测试的方法,很有启发。对于排序来说,有两个是为了保证排序算法正确的必须要测试的数据。其中一个是随机数,任意多的随机数,这样可以保证算法在相对多的数据下的正确性和速度。另一个是各种排列的数据的测试。如尝试0-9这10个数据的10!种排列的排序,如果正确,就能说明这个排序算法在这些情况下都可以正常运行,就可以相信这个算法是正确的了。

当然这种测试方法对于其他算法的测试也是适用的。

#include<iostream>
#include<algorithm>
using namespace std;

void insertSort(int A[],int n)
{
	for(int i=1;i<n;i++)
	{
		int temp=A[i];
		int j=i-1;
		while(j>=0 && A[j]>temp)
		{
			A[j+1]=A[j];
			j--;
		}
		A[j+1]=temp;
	}
}
//这个算法的运行效率不高,因为交换两个元素开销还是有的,而且
//这里当i==k时是A[i]本身自己交换
//当N=100000;且A[N]填充相同数字,如10,此时,每次quickSort时
//分成的两部分都极度不均匀,导致栈溢出。
void quickSort(int A[],int n)
{
	if(n<=1)
		return ;
	int k=0;
	for(int i=0;i<n-1;i++)
	{
		if(A[i]<=A[n-1])//这里<和<=差别不大
		{
			swap(A[i],A[k]);
			k++;
		}
	}
	swap(A[k],A[n-1]);
	quickSort(A,k);
	quickSort(A+k+1,n-k-1);
}

//通过把比A[0]小的元素不断放到左边,而把
//大于等于A[0]的元素放到右边,没有交换,
//只有赋值,效率高,但是比较次数多
void quickSort1(int A[],int n)
{
	if(n<=10)
	{
		insertSort(A,n);
		return;
	}
	swap(A[0],A[rand()%n]);
	int temp=A[0];
	int lo=0,hi=n-1;
	while(lo<hi)
	{
		while(hi>lo && A[hi]>temp)//这里>和>=差别很大,尽管加上=
			hi--;				 //后会减少交换,但是这样使得分成的两部分更加均匀了
		if(hi==lo)
			break;
		A[lo++]=A[hi];
		while(lo<hi && A[lo]<temp)//前面已经保证了必然有元素大于等于A[0],
			lo++;		//因此lo<hi在这里必然成立
		A[hi--]=A[lo];
	}
	A[lo]=temp;
	quickSort1(A,lo);
	quickSort1(A+lo+1,n-lo-1);
}

//《编程之美》第11章程序
void quickSort2(int A[],int n)
{
	if(n<=10)
	{
		insertSort(A,n);
		return;
	}
	swap(A[0],A[rand()%n]);
	int temp=A[0],lo=0,hi=n;//始终保证lo中存的是<=A[0]的元素
	while(lo<hi)
	{
		while(--hi>lo && A[hi]>A[0]);
		if(hi==lo)
			break;
		while(++lo<hi && A[lo]<A[0]);
		swap(A[lo],A[hi]);
	}
	swap(A[0],A[lo]);
	quickSort2(A,lo);
	quickSort2(A+lo+1,n-lo-1);
}

void selectSort(int A[],int n)
{
	for(int i=0;i<n;i++)
	{
		int k=i,min=A[i];
		for(int j=i;j<n;j++)
		{
			if(A[j]<min)
			{
				k=j;
				min=A[j];
			}
		}
		swap(A[i],A[k]);
	}
}

void shellSort(int A[],int n)
{
	int h=1;
	for(h=1;h<n;h=h*3+1);
	for(h=h/3;h>=1;h=h/3)
	{
		cout<<h<<endl;
		for(int i=h;i<n;i++)
		{
			int j=i-h;
			int temp=A[i];
			while(j>=0 && A[j]>temp)
			{
				A[j+h]=A[j];
				j=j-h;
			}
			swap(temp,A[j+h]);
		}
	}
}

int main()
{
	const int N=10000;
	int A[N];
//	fill_n(A,N,10);
	generate_n(A,N,rand);
	shellSort(A,N);
	bool b=is_sorted(A,A+N);

	system("pause");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值