​​​​​​​数据排序

数据排序

信息处理方法有多种,通常有数据的排序、查找、插入、删除、归并等操作。




下面重点介绍几种排序的方法:

1. 选择排序
(1)基本思想:每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列的最前,直到全部待排序的数据元素排完。</br>
(2)排序过程:</br>
初始关键字    \[49 38 65 97 76 13 27 49] </br>
第一趟排序后 13 \[38 65 97 76 49 27 49] </br>
第二趟排序后 13 27 \[65 97 76 49 38 49] </br>
第三趟排序后 13 27 38 \[97 76 49 65 49] </br>
第四越排序后 13 27 38 49 \[76 97 65 49] </br>
第五趟排序后 13 27 38 49 49 \[97 65 76] </br>
第六趟排序后 13 27 38 49 49 65 \[97 76] </br>
第七趟排序后 13 27 38 49 49 65 76 \[97] </br>
最后排序结果 13 27 38 49 49 65 76 97 </br>




案例1. 输入n个数,将n个数按从小到大的顺序输出(n<= 10000)。
【输入样例】:

  8

  49 38 65 97 76 13 27 49

【输出样例】:

  13 27 38 49 49 65 76 97

#include<iostream>
using namespace std; 
int main(){
	int n,k;
	float temp,a[1005];
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];		
	for(int i=1;i<=n;i++){     //i 控制当前序列中最小值存放的数据位置
		k=i;
		for(int j=i+1;j<=n;j++){   //在当前无序区a[i..n]中选最小的元素a[k]
			if(a[k]>a[j]) k=j; 
			if(k!=i){       //交换 a[i]和 a[k],将当前最小值放到 a[i]位置
				temp=a[i];
				a[i]=a[k];
				a[k]=temp;
			}
		}
	}		
	for(int i=1;i<=n;i++) cout<<a[i]<<" ";	
	return 0;	
}


 

2.冒泡排序
      冒泡排序的思想:以n个人站队为例,从第1个开始,依次比较相邻的两个是否逆序对(高在前 ,矮在后),若逆序就交换这两人,即第1个和第2个比,若逆序就交换两人,接着第2个和第3个比,若逆序就交换两人,接着第3个和第4个比,若逆序就交换两人 , …… ,直到n-1和n比较,经过一轮比较后,则把最高的人排到最后,即将最高的人像冒泡一样逐步冒到相应的位置。 原n个人的排序问题,转换为n-1个人的排序问题。</br>
      第二轮从第1个开 始,依次比较相邻的两个人是否逆序对,若逆序就交换两人,直到n-2和n-1比较。如此,进行n-1轮后,队列为有序的队列。</br>
      从上述分析中可以看出,每进行一轮 的比较之后 ,n个数的排序规模就转化为n-1个数的排序规模。</br>


 

【步骤】:
①读入数据存放在a数组中。
②比较相邻的前后两个数据,如果前面数据大于后面的数据,就将两个数据交换。
③对数组的第0个数据到n-1个数据进行一次遍历后,最大的一个数据就“冒”到数组第n-1个位置。
④n= n-1,如果n不为0就重复前面二步,否则排序完成。
程序实现方法:用两层循环完成算法,外层循环i控制每轮要进行多少次的比较,第1轮比较n-1次,第2轮比较n-2次,……,最后一轮比较1次。 内层循环j控制每轮i次比较相邻
两个元素是否逆序,若逆序就交换这两个元素。
程序如下:

#include<iostream>
using namespace std; 
int a[1005];

int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];		
	for(int i=1;i<=n-1;i++){   //进行n-1轮冒泡 
		for(int j=1;j<=n-i;j++){
			if(a[j]>a[j+1])        //相邻两个元素比较,若逆序就交换 
				swap(a[j],a[j+1]);
		}
	}		
	for(int i=1;i<=n;i++) cout<<a[i]<<" ";	
	return 0;	
}


改进的冒泡排序:
    对于有些数据.我们发现,不一定要n-1次才能排完。 例如1 5 2 3 4 6,我们发现只需一趟排序就可以将整个序列排完,于是,我们可以设置一个布尔变量,判断是否有
    进行交换,如果没有交换,说明已经排序完成,进而减少几趟排序。
    bool ok; 
    for(int i=1;i<=n-1;i++){ 
        ok=true;                    //判断是否有交换
		for(int j=1;j<=n-i;j++){
			if(a[j]>a[j+1]){
			    swap(a[j],a[j+1]);
			    ok=false;
			}
		}
		if(ok==true) break;       //没有交换就退出
	}	


 

3. 插入排序
      
插入排序思想:回忆一下打牌时抓牌的情景,为了方便打牌,抓牌时一般一边抓牌一按花色和大小插入恰当的位置,当抓完所有的牌时,手中的牌便是有序的,这种排序方法即 插入排序。</br>
      
当读入一个元素时,在已经排好序的序列中,搜寻它的正确位置,再放入读入的元素。但不该忽略一个重要的问题:在插入这个元素前,应当先将它后面的所有元素后移一位,以保证插入位置的原元素不被覆盖。</br>
      
假设:设n=8,数组a中8个元素是:36 25 48 12 65 43 20  58,执行插入排序程序后,其数据变动情况:</br>      
第0步:[36] 25 48 12 65 43 20 58 </br>      
第1步:[++25++ 36] 48 12 65 43 20 58 </br>      
第2步:[25 36 ++48++] 12 65 43 20 58 </br>      
第3步:[++12++ 25 36 48] 65 43 20 58 </br>      
第4步:[12 25 36 48 ++65++] 43 20 58 </br>      
第5步:[12 25 36 ++43++ 48 65] 20 58 </br>      
第6步:[12 ++20++ 25 36 43 48 65] 58 </br>      
第7步:[12 20 25 36 43 48 ++58++ 65]</br> </br>

程序如下:

#include<iostream>
using namespace std; 
int a[1005];

int main(){
	int n,i,j,k;
	cin>>n;
	for(i=1;i<=n;i++) cin>>a[i];		
	for(i=2;i<=n;i++){  
		k=a[i];
		for(j=i-1;j>=1;j--){   //在前面有序区间中为 a[i]找合适的插入位置 
			if(a[j] > k)  a[j+1]=a[j];    //找到比a[i]小 
			else break;
		}
		a[j+1]=k;	
	}		
	for(i=1;i<=n;i++) cout<<a[i]<<" ";	
	return 0;	
}
4.快速排序
      
快速排序是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小。则可分别对这两部分记录继续进行排序,以达到整个序列有序。</br>
      假设待排序的序列为{a\[l],a\[l+1],a\[l+2],… ,a\[r]},首先任意选取一个记录(通常可选中间一个记作为为枢轴或支点),然后重新排列其余记录,将所有关键字小于它的记录都放在左子序列中,所有关键字大于它的记录都放在右子序列中。 由此可以将该“支点 ”记录所在的位置mid作分界线,将序列分割成两个子序列和。这个过程称作一趟快速排序(或一次划分)。</br>
【分析】一趟快速排序的具体做法是:附设两个指针i和j,它们的初值分别为l和r,设枢轴记录取mid,则首先从j所指位置起向前搜索找到第一个关键字小于mid的记录,
然后从i所指位置起向后搜索,找到第一个关键字大于mid的记录,将它们互相交换,重复这两步直至i>j为止。
程序如下:

#include<iostream>
using namespace std; 
int a[1005];
void qsort(int, int);
int main(){
	int n,i,j,k;
	cin>>n;
	for(i=1;i<=n;i++) cin>>a[i];
	qsort(1,n);		
	for(i=1;i<=n;i++) cout<<a[i]<<" ";		
	return 0;	
}

void qsort(int l, int r){
	int i,j,mid,p;
	i=l; j=r;
	mid=a[(i+j)/2];     //将当前序列在中间位置的数定义为分隔数 
	do{
		while(a[i]<mid) i++;  //在左半部分寻找比中间数大的数 
		while(a[j]>mid)	j--;  //在右半部分寻找比中间数小的数 
		if(i<=j){
			swap(a[i],a[j]);    //若找到一组与排序目标不一致的数对,则交换它们 
			i++; j--;           //继续找 
		}
	}while(i<=j);
	if(l<j) qsort(l,j);     //若未到两个数的边界,则递归搜索左右区间 
	if(i<r) qsort(i,r);	
}


    快速排序的时间的复杂性是O(nlog2n),速度快,但它是不稳定的排序方法。就平均时间而言,快速排序是目前被认为是最好的一种内部排序方法。
    由以上讨论可知,从时间上看,快速排序的平均性能优于前面讨论过的各种排序方法。快速排序需一个栈空间来实现递归。若每一趟排序都将记录序列均匀地分割成长度
相接近的两个子序列,则栈的最大深度为log(n+l)。


 

5.归并排序
      
归并排序是建立在归井操作上的一种有效一的排序算法,该算法是采用分治法( Divide and ConQuer)的一个非常典型的应用。将已有序的子序列合井,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。 若将两个有序表合并成一个有序表,称为二路归并。</br>
      
例如有8个数据需要排序:10 4 6 3 8 2 5 7 </br>
      
归并排序主要分两大步:分解、合并。</br>
![image](https://note.youdao.com/yws/public/resource/02b7c5aa7d03a32f1d64ee55c43336d5/119093C954C84F6BA4B749EA1D64CADA/BA17469114A44022B3A65BE0CD8EE62E)



合并过程为:比较a[i]和a[j]的大小,若a[i]<=a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令1和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,
并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。 归并排序的算法我们通常用递归实现,
先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s, t]。

程序如下:

#include<iostream>
using namespace std; 
int a[1005], r[1005];
void msort(int, int);
int main(){
	int n,i,j,k;
	cin>>n;
	for(i=1;i<=n;i++) cin>>a[i];
	msort(1,n);		
	for(i=1;i<=n;i++) cout<<a[i]<<" ";		
	return 0;	
}

void msort(int s, int t){
	if(s==t) return;    //如果只有一个数字则返回,无须排序 
	int mid=(s+t)/2;
	msort(s,mid);        //分解左序列 
	msort(mid+1,t);      //分解右序列 
	int i=s, j=mid+1, k=s;
	while(i<=mid && j<=t){     //接下来合并 
		if(a[i]<=a[j]){
			r[k]=a[i];	k++;  i++;
		}else{
			r[k]=a[j];	k++;  j++;
		}
	}
	while(i<=mid){             //复制左边子序列剩余 
		r[k]=a[i]; k++; i++;
	}
	while(j<=t){               //复制右边子序列剩余
		r[k]=a[j]; k++; j++;
	}
	for(int i=s; i<=t; i++) 
		a[i]=r[i];
}
6.桶排序
      
桶排序的思想是若待排序的值在-个明显有限范围内(整型)时,可设计有限个有序桶, 待排序的值装人对应的桶(当然也可以装入若干个值),桶号就是待排序的值,顺序输出各桶的值,将得到有序的序列。</br> </br>

例:输入n个0到100之间的整数,由小到大排序输出。

程序如下:
#include<iostream>
using namespace std; 
int a[1005];
int main(){
	int n,i,j,k;
	cin>>n;
	for(i=1;i<=n;i++) {       //将等于 k的值全部装入第k桶中
		cin>>k;
		a[k]++; 
	}		
	for(i=0;i<=100;i++){  
		while(a[i]>0){        //相同的整数,要重复输出
			cout<<i<<" ";     //输出一个整数后,个数减1
			a[i]--;
		}
	}		
	return 0;	
}


 

各种排序算法的比较

(1).稳定性比较

插入排序、冒泡排序、二叉树排序、二路归并排序及其他线性排序是稳定的。

选择排序、希尔排序、快速排序、堆排序是不稳定的。即有跨度的交换都会导致不稳定。

(2).时间复杂性比较

      
插入排序、冒泡排序、选择排序的时间复杂性为O(n²);快速排序、堆排序、归并排序的时间复杂性为O(nlog₂n);桶排序的时间复杂性为O(n) ;

      
若从最好情况考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其他算法 的最好情况同平均情况相同;若从最坏情况考虑,则快速排序的时间复杂度为O(n²),直接插入排序和冒泡排序虽然平均情况相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情况对直接选择排序、堆排序和归并排序影响不大。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值