排序算法(五类八种)

排序是日常工作和软件设计中最常用的运算之一,今天给大家带来的五类八种排序算法的具体介绍;

1.选择排序(常用,较为简单)

选择排序的基本思想是:在每一趟的排序中,在待排序子表中选出关键字最小或最大的元素放在其最终位置上。

基于这种思想,我们衍生出来的常用的有两种排序方法,即直接选择排序堆排序

A.直接选择排序:直观,不稳定,平均时间复杂度为O(n^2)

方式:通过待排序子表中完整地比较一遍以确定最大(小)元素,并将该元素放在子表的最前(后)面,啥意思呢?类比求一组数据的最小值,和交换算法就可想清楚了。

具体代码实现如下:

#include<iostream>
using namespace std;
typedef int elementtype;
int main(){
cout<<"请输入要排序的序列个数: ";
int length;
cin>>length;
int *a=new int[length];
cout<<"请依次输入未排序前的序列:"<<endl;
for(int i=1;i<=length;i++){
cin>>a[i];
}
void select_sort(elementtype a[],int n);
select_sort(a,length);
cout<<"排序后的序列(使用直接选择排序)为:"<<endl;
for( i=1;i<=length;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return 1;
}
//直接选择排序,注意排序时不稳定,如(3,3,2)因为执行排序时会改变相同3的次序。
void select_sort(elementtype a[],int n){
elementtype min;
//控制min取次数在n-1次,其实也就是最大交换次数
for(int i=0;i<n-1;i++){
elementtype temp;
min=i;//将第一个当成最小元素
for(int j=i+1;j<n;j++){
if(a[min]>a[j]) {
min =j;//找到比当前元素还要小的数
}
}
//遍历一次检查最小元素是否排好位子,没有就交换
if(min!=i){
temp =a[min];
a[min]=a[i];
a[i]=temp;
}
}

}

B.堆排序:稳定排序,算法时间复杂度为Onlog2n

所谓堆排序,相信大家都已经了解了存储结构了,所谓的堆栈其实是一体的,都是内存中的存储结构。

 不过堆这种存储结构很特殊,可以具体化的描述是使用完全二叉树来描述,因为他的要求是:

要么该结点的左子树和右子树结点的值都小于他自己的值,这时称为是大根堆

要么该结点的左子树和右子树结点的值都大于他自己的值,这时称为是小根堆

因为这样的特点,那么在选择哪种堆类型是有讲究的;当我们要从小到大排序是,无疑选择大根堆来的更容易。

那么究竟怎样建立一个大根堆无疑是今天这个问题的重点:这里使用文字来说明可能来的不直观,建议自己动手去画出建堆过程便于理解。

#include<iostream>
using namespace std;
typedef int elementtype;
int main(){
cout<<"请输入要排序的序列个数: ";
int length;
cin>>length;
int *a=new int[length];
cout<<"请依次输入未排序前的序列:"<<endl;
for(int i=1;i<=length;i++){
cin>>a[i];
}
void heap_sort(elementtype a[],int n);
heap_sort(a,length);
cout<<"排序后的序列(使用堆排序)为:"<<endl;
for( i=1;i<=length;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return 1;
}
//要使用堆排序,那么必须先要把建立堆的算法弄清楚
/*要将一个无序的序列建立成堆,也就是使用静态的数组存储方式来建立一个完全二叉树,
  不过这个二叉树有点特别而已;反复调用筛选操作来实现,这就是调整了,
  要保证左右子树都必须为堆,也就意味着建堆是从下往上逐棵子树地去进行筛选的,
  那么,便于方便,建立堆的根下标从n/2到1的次序来操作*/


//调整数组a中以k为根的子树序列为堆,其中最大的元素下标为m,
void sift(elementtype a[],int k,int m){
int i,j;
elementtype x;
x=a[k];//临时保存当前根结点的值,来空出位置
bool finished =false;//设置结束标志,当finished为false表示堆需要被调整,为true则结束。
i=k;//i来保存当前根结点
j=2*i;//j先指向他的左孩子
while( j<=m && !finished){
if(j<m && a[j]<a[j+1]) j++;//让j指向左右孩子中的最大者
if(x>=a[j]) finished =true;//若原根最大,则结束
else {
a[i]=a[j];//大值结点上移
i=j;j=2*j;//继续遍历下子树
}
}
a[i]=x;//将原根值填充到所搜索到的当前空位置中


}
void heap_sort(elementtype a[],int n){
int temp;
for(int i=n/2;i>0;i--){
sift(a,i,n);//建堆
}
//可以看到堆排序不会改变相同值得顺序,所以是稳定的
for(i=n;i>=2;i--){
temp=a[i];
a[i]=a[1];
a[1]=temp;
sift(a,1,i-1);
}
}

2.插入排序:

插入排序的基本思想是:将待排序表看做是左右两部分,其中左边为有序区,右边为无序区,整个排序的过程就是将右边无序区中的元素逐个的插入到左边的有序区中,直到全部完成排序,基于这种思想,我们构建出两种算法,我们构建出两种插入排序的算法,直接插入排序和希尔排序。

A,直接插入排序:(较简单,很直接)稳定的,平均时间复杂度O(n^2)

这个算法很简单,相信读者通过代码注释能很快理解:

#include<iostream>
using namespace std;
typedef int elementtype;
int main(){
cout<<"请输入要排序的序列个数: ";
int length;
cin>>length;
int *a=new int[length];
cout<<"请依次输入未排序前的序列:"<<endl;
for(int i=1;i<=length;i++){
cin>>a[i];
}
void insert_sort(elementtype a[],int n);
insert_sort(a,length);
cout<<"排序后的序列(使用直接插入排序)为:"<<endl;
for( i=1;i<=length;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return 1;
}
/*直接插入排序的算法描述:
  首先,把第一个元素放到有序区中
  然后,比较下一个元素和有序区中的数的大小,要找到一个位子放他
  移动元素,腾位子放那个元素,一直做下去,知道完成整个插入排序的过程
*/
//这就意味着必须要有一个空位子来为元素的插入提供缓冲
//为了保证元素下标不越界,我们使用temp来存放待插入的元素
void insert_sort(elementtype a[],int n){
int temp,j;
for(int i=1;i<=n;i++){
temp=a[i];//用temp来保存要插入的元素
j=i-1;//取有序区最后一个元素下标
//因为有序,所以从后往前找,找到比数大的就继续往前找直到有位子
while( j>=1 && a[j]>temp){
a[j+1]=a[j];
j--;
}
a[j+1]=temp;//插入元素
}
}

B.希尔排序 不稳定的,时间复杂度为Onlog2n

希尔排序可以看成是直接插入排序算法改进版,多了一个分组的概念,不会太难,相信读者能够在程序注释中理解透彻.

#include<iostream>
using namespace std;
typedef int elementtype;
int main(){
cout<<"请输入要排序的序列个数: ";
int length;
cin>>length;
int *a=new int[length];
cout<<"请依次输入未排序前的序列:"<<endl;
for(int i=1;i<=length;i++){
cin>>a[i];
}
void shell_sort(elementtype a[],int n);
shell_sort(a,length);
cout<<"排序后的序列(使用希尔排序)为:"<<endl;
for( i=1;i<=length;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return 1;
}
/*希尔排序的算法描述:
  将待排序的序列分成若干组,在每组内进行直接插入排序,以使整个序列基本有序,再对整个序列进行直接插入排序。
  看法来源:就是直接插入排序中要是正序或基本正序时,直接插入排序算法的时间复杂度为o(n),这样两次整合,
  使得改进版的希尔排序的时间复杂度减低为O(nlogn)
*/
//当d=0时,希尔排序就退化为直接插入排序了
void shell_sort(elementtype a[],int n){
//要分组,必然有一个增量自取(一般第一个步长取1<d1<n/2)
int d,j;
d=n/2;
while(d>0){
for(int i=d+1;i<=n;i++){
int temp=a[i];
j=i-d;
while(j>0&&a[j]>temp)
{
a[j+d]=a[j];
j=j-d;
}
a[j+d]=temp;
}
d=d/2;
}


}

3.交换排序

交换排序的基本思想是:两两比较待排列的元素,发现倒序就交换

基于交换排序这种思想,有的是经典的冒泡排序和快速排序。

A.冒泡排序:(较简单)时间复杂度为O(n^2)

执行方式为:

典型的做法是从后往前,或从下往上逐个比较相邻元素,发现倒序立即交换

一遍下来,一定能将最大或最小的元素交换到最终的位子上,就像水泡一样冒到水面上,所以称之为冒泡排序。

#include<iostream>
using namespace std;
typedef int elementtype;
int main(){
cout<<"请输入要排序的序列个数: ";
int length;
cin>>length;
int *a=new int[length];
cout<<"请依次输入未排序前的序列:"<<endl;
for(int i=1;i<=length;i++){
cin>>a[i];
}
void bubble_sort(elementtype a[],int n);
bubble_sort(a,length);
cout<<"排序后的序列(使用冒泡排序)为:"<<endl;
for( i=1;i<=length;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return 1;
}
void bubble_sort(elementtype a[],int n){
int i,j,temp;
for(i=1;i<n;i++){
for(j=n;j>=i;j--){
//判断是否倒序,如果是,立即交换
if(a[j]<a[j-1]) {
temp =a[j-1];
a[j-1]=a[j];
a[j]=temp;
}
}
}
B.快速排序:是对冒泡排序的一种改进,是不稳定的,时间复杂度为Onlog2n

基本思想是:首选选定一个元素作为中间元素,然后将表中所有的元素与该中间元素比较,将比他大的放后面,比他小的放前面,再将中间数放在这两部分之间以作为分界点,由此在对左右两部分分别进行快速排序。

所以要进行快速排序,必须要先知道他的划分算法partition:

1.保存中间元素的值到临时变量x以腾出其空间,并用变量i指示该空位

2.从最后边往前边搜索比中间数小的元素,并将其放置在前面的这个空位上,使后面空出一个位子(用整型变量j指示)

3.从最前边往后边搜索比中间数大的元素,并将其放置在后面的这个空位上,使前面空出一个位子(用整型变量i指示)

重复以上过程,直到完成划分。然后进行递归,就能快速排序了。由于作者时间仓促,这里的代码不能及时给出,如有需要的,可以联系我,我会及时更新补全。

4归并排序:是稳定的,时间复杂度为Onlog2n

先说一下归并的思想:是指将两个或两以上的有序表合并成一个新的有序表,有点像插入排序的变形哦,但因为会出现新表,所以不是插入排序。

怎么去产生这个新表呢?这必然是我们最要关心的问题. 

其实就是用一个表做参照,另一个表来做插入,因为有序,所以比较的时候会十分简单,下面是归并算法:就是C表等于A表加B表;

/*基于归并算法的思想,我们可以将整个表看成是n个子表,每个子表长度为1,然后两两做归并,
得到的是 n/2个子表,每个子表长度为2,继续两两合并,直到得到一个长度为n的有序表。*/
//序列的归并算法
void merge(elementtype a[], int low , int mid, int high) {
int i=low;
int j=mid+1;
int *b=new int[(high-low+1)*sizeof(int)];
while(i<=mid&&j<=high){
if(a[i]<a[j])
  b[p++]=a[i++];
else b[p++]=a[j++];
}
while(i<=mid)
b[p++]=a[i++];
while(j<=high)
b[p++]=a[j++];
for(i=low,p=0;p<(high-low+1);i++,p++)  //将结果复制到原数组当中
    arr[i] = newarr[p];    
}

5.基数排序:稳定的,时间复杂度为O(d(n+r)







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值