各种排序算法整理

各种排序算法整理

首先说一下算法分析
	算法分析是一个理论研究,是关于计算机程序性能和资源利用的研究,尤其关注性能。
	性能很重要吗?当然。有比性能更重要的吗?Exp:正确性,安全性,简洁,可扩展,用户友好等等?可是为什么我们来说性能,因为性能是最重要的。好比钱不重要,但是没有钱你很多事做不了。性能就是如此,它是潜在的东西。你没有它,何谈正确、安全、用户友好。用户体验和性能有关,如果一个程序点击一下运行一分钟都不出结果,你能受的了吗?可行性,如果性能达不到要求,还谈什么可行性?性能的好坏直接决定是否可行。过多的内存占用,太多的时间,这用户受不了吧。	所以性能真的很重要。

排序是很重要的,一个好的排序算法更加的重要。
	什么是排序?按规定的要求将一系列的输入重新排列成一系列的输出得到你想要的序列。要求就是按关键字,无非是升序和降序。排序分稳定和不稳定。内部排序和外部排序。甚至可以分为基于比较的和非基于比较的。

插入排序

将一个元素插入到已经排好序的有序表中,得到一个元素数加1的有序表。 算法描述:从第二个元素开始,依次将元素向前插入已排序的序列中。

void InsertionSort(int array[],int n)
{
	int key;      //注意细节是>=0和>key
	for (int i=1;i<n;i++)
	{
		key=array[i];
		for(int j=i-1;j>=0&&array[j]>key;j--)
		{
			array[j+1]=array[j];
		}
		array[j+1]=key;
	}
}

举例
	初始序列是 2  8  7  1  3  5  6  4


	完成排序后 1  2  3  4  5  6  7  8
	时间复杂度很容易得出T(n)=n(1+2+#+n-1)=O(n^2)  辅助空间是O(1),只是用了一个临时变量而已。
	直插排序是稳定排序算法,因为无论是从后向前插入,或者从前往后插入,元素都只是前后移动一个位置,没有交换,自然序列不会发生改变
       直接插入排序的改进方案是可以减少插入时的比较次数。比如二分插入,但是时间复杂度不变。插入排序另一种改进是缩小增量排序,即shell排序。

Shell排序

基本思想:将输入序列分成若干子序列,分别对子序列进行直接插入排序。这些子序列不是随便分的,是相隔相同的增量来划分,直到最后间隔为1。最后一次对基本有序的全部序列进行一次插入排序即可完成,而此时可以把直接插入看成是增量为1的shellsort)。

void ShellInsert(int array[],int n,int d)
{
	int key;      //d是增量,和直插的不同是1换成了d而已
	for (int i=d;i<n;i++)
	{
		key=array[i];
		for(int j=i-d;j>=0&&array[j]>key;j=j-d)
		{
			array[j+d]=array[j];
		}
		array[j+d]=key;
	}
	
}
void ShellSort(int array[],int n,int d[],int m)
{
	for(int i=0;i<m;i++) ///增量一般是奇数、质数。最后一个增量必须为1
		ShellInsert(array,n,d[i]);
}


 
 

举例:
	初始序列是 2  8  7  1  3  5  6  4


     完成排序后 1  2  3  4  5  6  7  8
	时间复杂度是T(n)=O(n^3/2)。如何证明?辅助空间是O(1).

     希尔排序的好处是减少的比较次数和移动次数。Shell排序不是稳定排序,因为和直插相比shell有多次相隔增量(间隔交换)的直插排序,元素的跳跃的变化会导致不稳定。

选择排序

从输入序列后n-i+1个元素中选择最小的元素和第i个元素交换,或者从前n-i个元素中选择最大的元素和第n-i个元素交换得到输出序列。

简单选择排序

是一个复杂度比较高(O(n^2))的排序算法。
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。
void SelectSort(int array[],int n)
{
	int key;
	int temp;
	for(int i=0;i<n-1;i++) //只需要n-1次select最后一个元素肯定是正确的序列
	{
		key=i;
		for(int j=i+1;j<n;j++)
		{
			if(array[j]<array[key])
			{
				key=j;
			}
		}
		if(key!=i) //如果不是key,则交换.这可以避免和本身交换。
		{
			temp=array[i];
			array[i]=array[key];
			array[key]=temp;
		}
	}
}

举例说明
举例:
	初始序列是 2  8  7  1  3  5  6  4

	
	完成排序后 1  2  3  4  5  6  7  8
时间复杂度T(n)=(n-1)(1+2+#+n)=O(n^2) 辅助空间O(1) 
简单选择不是稳定排序,因为有间隔交换,可以把相同元素的序列搞反。举个例子,序列5 8 5 2 9,第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以简单选择排序不是一个稳定的排序算法。那么有没有稳定的选择排序?没有,堆排序也是选择排序,选择都不是稳定排序,一样的道理。

堆排序

将一个输入序列看成一个满二叉树,每一元素和满二叉树的节点一一对应。然后建堆,建堆的过程就是从最后一个非叶子节点开始调整,直到第一个节点。调整的过程就是,建堆之后,将堆顶元素和最后一个元素交换,此时堆的性质就变了,要调整。此时除了堆顶的节点不满足堆的性质外,其子堆都满足。这时候就要调整,注意此时堆的元素少了一个,因为那个元素已经调整好了,满足输出序列了

如何调整堆?

void AdjustHeap(int array[],int start,int end)//调整大顶堆 这是非递归的算法,当然还有递归的算法,可以想一下
{
	int top;
	top=array[start];//top保存堆顶的值,不仅仅是整体,还有可能是子堆的堆顶
	for(int j=2*start;j<=end;j=2*j) //因为数组下标从1开始
	{
		if(j<end&&array[j]<array[j+1])
		{
			j++;//右孩子大
		}
		if(array[j]>top)
		{
			array[start]=array[j];///让孩子覆盖父节点。
			start=j;            //此时让子节点作为新的父节点,即堆顶,继续调整
		}
		else
			break;//不需要调整了,直接跳出
	}
	array[start]=top;  //让最初的堆顶值放到合适的位置
}

	调整的时间代价是O(lgn),为什么呢?因为堆的高度最高是Hmax=O(lgn),所以最多进行lgn次。

如何建堆?

void BuildHeap(int array[],int start,int end)
{
	for(int i=end/2;i>=start;i--) //从最后一个非叶子节点开始调整
		AdjustHeap(array,i,end);
}

	建堆的时间代价是T(n)=O(nlgn),AdjustHeap循环了n/2-1次,所以复杂度是O(nlgn)。可是真的是这样吗?
NO!这仅仅是一个上界罢了。你想一想,每一次都需要O(lgn)的调整代价吗?O(lgn)仅仅是最坏的调整代价而已。然而建堆的过程是从最后一个非叶子开始到堆顶,复杂度从O(1)逐渐增加,直到堆顶的O(lgn)的最坏调整。(即便是从堆顶调整,也不一定是O(lgn),这是最坏代价)。我们这么想,堆的高度为0的节点都是叶子节点,他们有N/2个,这些节点不需要执行AdjustHeap,而他们父结点需要,其父节点的高度是1,节点数目为(N/2)/2,他们执行AdjustHeap的时候执行覆盖替换的次数最多为1.而高度为2的节点是高度为1节点的父节点,节点数目为((N/2)/2)/2,他们执行AdjustHeap的时候执行覆盖替换的次数最多为2。 (注意!堆仅仅是一个满二叉树,不是完全二叉树。所以高度相同的节点可以不在同一层次)
	那么高度为h的节点数目为N/(2^(h+1)),他们执行AdjustHeap的时候执行覆盖替换的次数最多为h。也就是说节点数目为N/(2^(h+1))的节点执行代价是O(h).所以对这些非叶子节点的所有执行代价求和,就可知道建堆的代价。,这可以通过求级数最后求极限的方法得出h/(2^h)的全部和s=2。
证明一下吧。  s=1/2+2/2^2+3/2^3+……+lgn/2^lgn
		  1/2s=1/2^2+2/2^3+3/2^4+……+lgn/2^(lgn+1) 
 两式相减得  1/2s=1/2+1/2^2+1/2^3+……+1/2^lgn-lgn/2^(lgn+1)   等比数列求和求极限得s=2;ps:数学很重要啊
	所以建堆的时间复杂度就是T(n)=O(n*2)=O(n).也就是说在一个线性时间内可以将一个无序数组变成一个最大堆。
堆排序:

void HeapSort(int array[],int start,int end)
{
	int temp;
	BuildHeap(array,1,end);//先建堆
	for(int i=end;i>1;i--) //交换,调整。
	{
		temp=array[1];
		array[1]=array[i];
		array[i]=temp;
		AdjustHeap(array,1,i-1);//堆元素减1
	}
}

	堆排序代价是O(nlgn)  (注意相关函数的参数以及函数调用)
举例:
	初始序列是 2  8  7  1  3  5  6  4


	完成排序后 1  2  3  4  5  6  7  8
	时间复杂度T(n)=O(nlgn) +O(n)*O(lgn)=O(nlgn)  辅助空间O(1) 不是稳定排序。

冒泡排序

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经(提前)排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,故名。
void BubbleSort(int array[],int n)
{
	bool flag=1;//标记是否还在交换
	int temp;
	for(int i=0;flag&&i<n-1;i++)
	{
		flag=0;//初始化flag,假设不再交换
		for(int j=0;j<n-i-1;j++)
		{
			if(array[j]>array[j+1])
			{
				temp=array[j];
				array[j]=array[j+1];
				array[j+1]=temp;
				flag=1;  //还在交换
				
			}
		}
	}
}

举例:
	初始序列是 2  8  7  1  3  5  6  4


	完成排序后 1  2  3  4  5  6  7  8
时间复杂度T(n)=(n-1)(1+2+3+$+n-1)=O(n^2) 辅助空间O(1)。冒泡是一个稳定排序,虽然有交换,但只是相邻交换不会把相同元素的序列搞反

鸡尾酒排序

也成为定向冒泡排序、鸡尾酒搅拌排序、搅拌排序、来回排序,是冒泡排序的一种变形。此算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。Exp:先从前往后找到最大的元素,把他放到最后一位,然后从后往前找到最小的元素放到第一位。以此类推,直到完成排序。

void CocktailSort(int array[],int n)
{
	bool flag=1;//标记是否还在交换
	int temp;
	for(int i=0;flag&&i<n/2;i++)  
	{
		flag=0;//初始化flag,假设不再交换
		for(int j=i;j<n-i-1;j++)  //从前往后 j不再是=0.因为前后都有一部分是排好的
		{
			if(array[j]>array[j+1])
			{
				temp=array[j];
				array[j]=array[j+1];
				array[j+1]=temp;
				flag=1;  //还在交换
				
			}
		}
		for(j=n-i-2;j>i;j--)  // 从后往前排序 j=n-i-1-1表示已排好序的前一个
		{
			if(array[j]<array[j-1])
			{
				temp=array[j];
				array[j]=array[j-1];
				array[j-1]=temp;
				flag=1;     //还在交换
				
			}
		}
	}
}

	鸡尾酒排序等于是冒泡排序的轻微变形。不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。她可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。以序列(2,3,4,5,1)为例,鸡尾酒排序只需要访问两次(升序降序各一次 )次序列就可以完成排序,但如果使用冒泡排序则需要四次。
举例:
	初始序列是 2  8  7  1  3  5  6  4


	完成排序后 1  2  3  4  5  6  7  8
时间复杂度T(n)=O(n^2)  虽然减少了交换次数,可复杂度还是没变。辅助空间O(1) 稳定排序

快速排序

归并排序

快速排序归并排序已在其他文章讲述。对了,还有一种是二叉排序树,以后再说。 以上都是基于比较的排序,有的交换,有的没交换,交换也分间隔交换和相邻交换。可以证明的是基于比较的排序最快只能达到O(nlgn)。 可以通过决策树来证明,假设对<A,B,C>排序。 决策树

	对于一个输入序列有N!个输出决策序列。假设决策树高度H,叶子节点数目L。N!<=L<=2^H  可以得出 H>=log(N!)=O(nlgn)。
	决策树的高度对应于从根到任意一个可达叶子节点最长路径,也就是最坏的执行时间。所以基于比较的排序最快也就是nlgn。
	堆排序和归并排序的上界也是nlgn,所以他们是渐进最优的比较排序算法。

线性排序是可以得到线性时间O(n)的排序算法。

计数排序

计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。。 算法: 1 找出待排序的数组中最大和最小的元素 2 统计数组中每个值为i的元素出现的次数,存入数组C的第i项 3 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加) 4 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1 假设所有的输入元素都是介于0---k之间。 COUNTING-SORT(A, B, n, k) for i ← 0 to k do C[i ] ← 0      //辅助数组清0 for j ← 1 to n do C[A[ j ]] ← C[A[ j ]] + 1    //将数值序号在c中计数 for i ← 1 to k do C[i ] ← C[i ] + C[i − 1]    //c[i]包含小于或等于i的个数 for j ← n to 1       //倒序可以保证稳定性 do B[C[A[ j ]]] ← A[ j ]    //复制到B中,已排序 C[A[ j ]] ← C[A[ j ]] − 1 //复制一个减去一个,依旧是A[j]的排序位置,可能元素相同,可以保持稳定性
举例

	时间复杂度T(n)=O(k)+O(n)+O(k)+O(n)=O(n+k)  辅助空间O(n).可以算作是空间换时间了。
	具有稳定性,因为从后向前,相同元素序列不变。所以其经常作为基数排序的一个子过程。

基数排序 

WIKI:
	一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
实现:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
	基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始
	假设有d位数,则其时间复杂度就是T(n)=O(d*X) X代表所使用的中间排序方式的时间复杂度。如果中间用了计数排序,则T(n)=O(d(n+k))。其实也是基于其他排序算法的一种算法。

举例



桶排序

假定:输入是由一个随机过程产生的[0, 1)区间上均匀分布的实数。将区间[0, 1)划分为n个大小相等的子区间(桶),每桶大小1/n:[0, 1/n), [1/n, 2/n), [2/n, 3/n),…,[k/n, (k+1)/n ),…将n个输入元素分配到这些桶中,对桶中元素进行排序,然后依次连接桶输入0 ≤A[1..n] <1辅助数组B[0..n-1]是一指针数组,指向桶(链表)。或者是将数组分到有限数量的桶子里。每个桶子再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(O(n))。 算法: 1 设置一个定量的数组当作空桶子。 2 寻访串行,并且把项目一个一个放到对应的桶子去。 3 对每个不是空的桶子进行排序。 4 从不是空的桶子里把项目再放回原来的串行中。 举个例子



外排序

最后来说一下外排序,当内排序满足不了输入序列的要求的时候就需要外排序了。应用于数据量很大的情况,在排序的过程中,待排序的元素不断的在内存和外存之间进行交换,已排好的调出内存放入外存,未排序的带入内存。

归并排序

2路归并排序:1首先将待排序的大数据根据内存缓冲区的大小分为M个子文件2 然后将子文件分批调入内存,分别进行排序(中间排序算法,内排序),然后生成M个已排序的子文件,归并段。3 将已有序的归并段继续在内存和外存之间互换进行排序,知道整个文件有序。 举例 假设输入1200个数据,内存只能处理300个。可以分成4个子文件A,B,C,D,每个包含300个数据。然后将A分三等分,A分三等分。将A,B中的100+100数据放入内存排序输出,反复,直到A和B合并为一个有序的较大文件,同理处理C,D。直到完成大文件的排序。 归并趟 多路归并 2路归并是每次归并两个子文件,K路归并是每次归并K个子文件。K路归并的归并趟是。明显是节省时间。可是每一次的归并会变得麻烦。此时可利用选择树进行选择。


转载请注明出处http://blog.csdn.net/sustliangbo/article/details/9292379

 
 
 
 
 
 
 
 
 
 
 
 
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值