数据结构 排序 (图解+C语言)

★★★★★排序

	当数据量非常大时,排序的效率就非常重要;所以以下学习的排序的数据起步量都是10000个以上
	
	各种排序的统一函数名规范:
		void X_Sort( ElementType A[],int N );
		  排序名称     待排元素      数组大小
		              放在数组里
		              
		元素类型包括所有能排大小的数据结构,比如数字,字符串等等,都可以用排序算法;
		
		.默认为将整数从小到大排序:为了简单起见,所有排序算法的理解和代码都是以整数为例;
		.输入的N为正整数;
		.只讨论基于比较的排序(> = < 有定义):对于要排序的数据类型,有明确的大小相等的定义;
		.只讨论内部排序:假设内存空间充足,所有数据能一次性的导入内存空间;排序过程在内存中完成;
			外部排序:数据量大于内存,需要分若干次数据的导入导出才能完成的排序;
		.稳定性:任意两个相等的数据,排序前后的相对位置不发生改变;
		.没有一种排序时任何情况下都表现最好的:每一种算法都有它存在的理由
			当数据具有某种特征的时候,某一种算法可能是最好的

 测试用例说明:

给定N个(长整型范围内的)整数,要求输出从小到大排序后的结果。

本题旨在测试各种不同的排序算法在各种数据情况下的表现。各组测试数据特点如下:

  • 数据0:只有1个元素;
  • 数据1:11个不相同的整数,测试基本正确性;
  • 数据2:10^3个随机整数;
  • 数据3:10^4个随机整数;
  • 数据4:10^5个随机整数;
  • 数据5:10^5个顺序整数;
  • 数据6:10^5个逆序整数;
  • 数据7:10^5个基本有序的整数;
  • 数据8:10^5个随机正整数,每个数字不超过1000。

    输入格式:

    输入第一行给出正整数N(≤105),随后一行给出N个(长整型范围内的)整数,其间以空格分隔。

    输出格式:

    在一行中输出从小到大排序后的结果,数字间以1个空格分隔,行末不得有多余空格。

    输入样例:

    11
    4 981 10 -17 0 -20 29 50 8 43 -5
    

    输出样例:

    -20 -17 -5 0 4 8 10 29 43 50 981
代码长度限制                                                                16 KB
时间限制                                                                 10000 ms
内存限制                                                                    64 MB
	※简单排序:		
▶♥ 冒泡排序
		
		20          把数据想象成泡泡,大数据=>大泡泡,小数据=>小泡泡;
		12 	        不断地做从上到下扫描,每当看到更大的大泡泡时,就把它往下压; 
		39          每次从上到下扫描一遍:如果A[i] > A[i+1];Swap(A[i],A[i+1]);
		9           所以每一次扫描就是只将最大的泡泡交换到最底下
		60          当扫描一遍发现已经都排好序了,即没有发生Swap,那就跳出循环
		8           
		42     	void Bubble_Sort( ElementType A[],int N )
		3  		{
		28	    	int i,Tag = 1; //存在需要排序的情况
			        while(Tag){
			        	Tag = 0;  //假设已经都排好了
			        	for(i=0;i<N;i++){
			        		if(A[i] > A[i+1]){
			        			Swap( &A[i],&A[i+1] );
			        			Tag = 1; //有Swap发生,还需要扫描
							}
						}
					}
				}
				
				代码优化:由于每一次扫描一定是将最大的泡泡压到了最底下,所以下一次扫描时,只需扫描到倒数第二的位置就可以啦
					     以此类推,每扫描一遍,下次需要扫描的长度就-1;
	
		    void Bubble_Sort( ElementType A[],int N )			void Bubble_Sort( ElementType A[],int N )
	  		{													{
		    	int i,Tag = 1; 										int i,P,flag;
		        while(Tag){											for(P=N-1;P>0;P--){
		        	Tag = 0;  											flag = 0;
		        	for(i=0;i<N-1;i++){									for(i=0;i<P;i++){
		        		if(A[i] > A[i+1]){									if(A[i] > A[i+1]){
		        			Swap( &A[i],&A[i+1] );								Swap( &A[i],&A[i+1]);
		        			Tag = 1;											flag = 1;
						}													}
					}													}
																		if(flag == 0){
					N--;													break;
				}														}
			}														}
																}
		
			时间复杂度:最好情况 T=O(N)
					   最坏情况 T=O(N^2)
					   
			冒泡排序的缺点:时间复杂度达到O(N^2)
			          优点:①如果待排元素放在链表里,同样适用
			               ②具有稳定性
	.

 测试结果:

 代码:

#include <stdio.h>

void Swap( int* a,int* b );

void Print( int A[],int N );

void Bubble_Sort( int A[],int N );

int main()
{
	int N;
	scanf("%d",&N);
	
	int i,A[N];
	for(i=0;i<N;i++){
		
		scanf("%d",&A[i]);
	}
	
	Bubble_Sort( A,N );	
	
	Print( A,N );
	
	return 0;
}

void Bubble_Sort( int A[],int N )
{
	int i,P,IsAllInOrder;
	for(P=N-1;P>0;P--){
		IsAllInOrder = 1;
		
		for(i=0;i<P;i++){
			
			if(A[i]>A[i+1]){
				
				Swap( &A[i],&A[i+1] );
				IsAllInOrder = 0;
			}
		}
		
		if(IsAllInOrder){
			break;
		}
	}
}

void Swap( int* a,int* b )
{
	int Temp = *a;
	*a = *b;
	*b = Temp;
}

void Print( int A[],int N )
{
	int i;
	for(i=0;i<N-1;i++){
		
		printf("%d ",A[i]);
	}
	
	printf("%d",A[N-1]);
}
▶♥ 插入排序	

	 将排序的过程模拟为抓牌的过程,
	 抓第一张,放在A[0],不动
	 抓第二张 和前面的比较 如果前面的比较大 那就后移一位 即A[1] = A[0];
	 抓第三张 和第二张比较 如果第二张比较大 那就后移一位 即A[2] = A[1];再和第一张比较 第一张大 那就A[1] = A[0];然后A[0] = 这张牌
	 ... ...                                                                          手里这张牌大 那就A[1] = 这张牌
	 如此循环 直到抓完最后一张牌
	 
	 抓第一张牌10 放手里        10
	 抓第二张牌8  对比          8  10
	 抓第三张牌J  对比          8  10   J
	 抓第四张牌3  一张张 对比   3   8   10    J
	 
	 void Insertion_Sort( ElementType A[],int N )
	 {
	 	for(P=1;P<N;P++){//从第二张牌来时比较 直到最后一张
	 		Temp = A[P]; //抓起来放手里	
	 		for(i=P;i>0;i--){//依次和前面的牌比较
	 			
	 			if(A[i-1] > Temp){ //如果不符合顺序
	 				A[i] = A[i-1]; //后移一位
				}else{             //否则
					A[i] = Temp;   //新牌落座
					break;         //不用再比了,跳出循环
				}
			}	
		}
	 }
	 
	 代码优化:
	 void Insertion_Sort( ElementType A[],int N )
	 {
	 	for(P=1;P<N;P++){
	 		Temp = A[P];  /* 摸下一张牌 */
	 		for(i=P;i>0 && A[i-1]>Temp;i--){
	 			A[i] = A[i-1];	/* 移出空位 */
			}
			A[i] = Temp;  /* 新牌落座 */
		}
	 }
	
	 时间复杂度:最好情况 T=O(N)
	            最坏情况 T=O(N^2)
	       	
		优点:程序简单,步骤少,具有稳定性
	.

 测试结果:

 比冒泡好很多;

代码:

#include <stdio.h>

void Print( int A[],int N );

void Insertion_Sort( int A[],int N );

int main()
{
	int N;
	scanf("%d",&N);
	
	int i,A[N];
	for(i=0;i<N;i++){
		
		scanf("%d",&A[i]);
	}
	
	Insertion_Sort( A,N );	
	
	Print( A,N );
	
	return 0;
}

void Insertion_Sort( int A[],int N )
{
	int P,i,Temp;
	for(P=1;P<N;P++){
		Temp = A[P];
		
		for(i=P;i>=1 && A[i-1]>Temp;i--){
			
			A[i] = A[i-1];
		}
		
		A[i] = Temp;
	}
}

void Print( int A[],int N )
{
	int i;
	for(i=0;i<N-1;i++){
		
		printf("%d ",A[i]);
	}
	
	printf("%d",A[N-1]);
}
※排序进阶:
	※时间复杂度下界
	
		.对于下标i<j,如果A[i]>A[j],则称(i,j)是一对逆序对(inversion)
		
		.问题:序列{34,8,64,51,32,21}中有多少逆序对?
		(34,8) (34,32) (34,21) (64,51) (64,32) (64,21) (51,32) (51,21) (32,21)  9对
		
			简单排序(比如冒泡排序 插入排序 Low选择排序) 每次都只是交换一个逆序对,所以交换的次数都是9次
		
		.交换2个相邻元素正好消去1个逆序对
		
		.插入排序:T(N,I)=O(N+I)  I是逆序对的个数
			口 如果序列基本有序,则插入排序简单且高效
			
		.定理:任意N个不同元素组成的序列平均具有N(N-1)/4个逆序对
		
		.定理:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为Ω(N^2) Ω指的是下界
		
		.这意味着:要提高算法效率,我们必须
			 ==>每次消去不止1个逆序对!
			 ==>每次交换相隔较远的2个元素!
	.
		
▶♥ 希尔排序	(by Donald Shell)

	 利用插入排序的简单,同时克服插入排序每次只交换相邻两个元素的这个缺点
	 
	 例如:            81 94 11 96 12 35 17 95 28 58 41 75 15 
	 先做5-间隔排序 即(81 35 41) (94 17 75) (11 95 15)  (96 28) (12 58)
	                  35 17 11 28 12 41 75 15 96 58 81 94 95
	 再做3-间隔排序   28 12 11 35 15 41 58 17 94 75 81 96 95
	 最后1-间隔排序   
	 
	 .定义增量序列Dm>D(m-1)>...>D1=1
	 .对每个Dk进行"Dk-间隔"排序(k=M,M-1,...,1)
	 .注意到:更小间隔的排序没有将上一步的结果变坏
	         "Dk-间隔"有序的序列,在执行"Dk-1-间隔"排序后,仍然是"Dk-间隔有序"的
	         
	 原始希尔排序 Dm=N/2,Dk=D(K+1)/2
	 
	 void Shell_Sort( ElementType A[],int N )
	 {
	 	for(D=N/2;D>0;D/=2){
	 		/* 插入排序 */
	 		for(P=D;P<N;P++){
	 			
	 			Temp = A[P];
	 			for(i=P;i>=D && A[i-D]>Temp;i-=D){
	 				A[i] = A[i-D];
				}
				
	 			A[i] = Temp;
			}	
		}
	 }
	 希尔排序不具有稳定性
	 
	 最坏情况:T=Θ(N^2) Θ既是上界又是下界  O是一个上界(有可能达不到)  Ω是一个下界
	 
	 例如: 1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16
	 按8-间隔 4-间隔 2-间隔排序后没有任何变化
	 
	 问题点:增量元素不互质,则小增量可能根本不起作用
	 
	 更多增量序列:
	 	◎Hibbard增量序列
	 		.Dk = 2^k-1  ————————相邻元素互质
	 		.最坏情况:T=Θ(N^3/2)
	 		.猜想:Tavg = O(N^%/4)
	 		
		◎Sedgewick增量序列
			.{1,5,19,41,109,...} ——————9*4^i-9*2^i+1 或 4^i-3*2^i+1
			.猜想:Tavg = O(N^7/6),Tworst = (N^4/3)
			
			希尔排序本质上还是插入排序,只不过先用增量调整了不相邻的逆序对。
			Sedgewick增量序列:int Sedgewick[] = {260609,146305,64769,36289,16001,8929,3905,2161,929, 505,209, 109, 41, 19, 5, 1, 0};
			
		即使一个简单的算法,但是关于它的时间复杂度分析可能非常的难
		
		
	 void Shell_Sort( ElementType A[],int N )
	 {  /* 希尔排序—— 用Sedgewick增量序列 */
		int Si,D,P,i;
		ElementType Temp;
		/* 这里只列出一小部分增量 */
		int Sedgewick[] = {929,505,209,109,41,19,5,1,0};
		
		for(Si=0;Swdgewick[Si]>=N;Si++);//初始增量Sedgewick[Si]不能超过待排序列长度
		
		for(D=Sedgewick[Si];D>0;D=Sedgewick[++Si]){
			
			for(P=D;p<N;P++){
				
				Temp = A[P];
				for(i=P;i>=D && A[i-D]>Temp;i-=D){
					
					A[i] = A[i-D];
				}
				
				A[i] = Temp;
			}
		} 			
	 }	
	.

总过程:

 分解步骤:

第一步:增量为4

 第二步:增量为2

 第三步:增量为1

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值