常见的排序可分为比较排序及其非比较排序,其中比较排序较常用的有冒泡排序,归并排序,插入排序,选择排序,快速排序,梳排序等等;非比较排序有桶排序,基数排序等等。我们将介绍其中的梳排序,归并排序,快速排序,基数排序。网上关于这些实现排序的方法可能有多种,这里每一种排序仅提供其中一种实现方法作为参考。
第一种:比较排序
梳排序:冒泡排序的优化版。
原理:交换的范围由冒泡的与旁边的数比较并,变成了与隔一段距离的数比较并交换,并每过一个循环减少这个距离,当距离为1时,就相当于冒泡,但是在退化成冒泡前,我们已经对数据进行了多次比较排序,所以进行冒泡的步骤就十分少了,变形加速了冒泡。所以速度>=冒泡
#include<stdio.h>
#define N 1000
int main()
{
int n;//要处理数据的大小
int i;//计数器
int j;//距离
int a[N];//存储数据的数组
double k=1.3;//距离递减值,可以是其他,但1.3测试的效果较好
scanf("%d",&n);
for(i=0;i<n;i++){scanf("%d",&a[i]);}
j=n-1;
i=0;
int flag=1;//判断是否有交换,如有交换循环还要继续,否则(部分)排序结束
//两个条件同时满足时,才能认为数据中每一个数都被比较过并处理过
while(j>1||flag==1){
j=(j/k>1)?(j/k):1;//每循环一次让 距离/递减值,减少距离,直到为1,则退化成冒泡
flag=0;
i=0;
//下面进行冒泡
while(i+j<n){
if(a[i]<a[i+j]){
int temp=a[i];
a[i]=a[i+j];
a[i+j]=temp;
flag=1;
}//大到小排序,交换
i++;
}
}
for(i=0;i<n;i++){printf("%d ",a[i]);}//输出
return 0;
}
归并排序:稳定的高速算法
原理:借助分治法,将要处理的数据分半,再分半,直到只剩两个数的时候不能再分,开始对分半到最后的那一堆数据(子数据)分别进行排序
步骤:
1. 每个子数据中的数比较大小并换位
2. 递归到上一步,和旁边的(排好序的)子数据进行合并
(合并步骤:)
- 临时建立一个空数组Z[ n ]
- 比较两个子数据a[ i ],b[ j ]首元素大小,大的(如果是a[0])则放进Z[0]
- 然后比较a[1]和b[0],大的放进Z[1](每放进一次,Z和放进元素的数组下标+1)
- 重复
- 如果两个子数据长度不等,把剩下的元素不按顺序直接放进Z
- 回到2,直到全部合并。
因为该方法无论数据大小只会影响分半速度,所以该方法比较稳定,无论如何速度>冒泡。
但不一定大于梳排序。
代码描述可能更容易理解:
#include<stdio.h>
#define N 1000
void Mergesort(int a[],int L,int R);
int main()
{
int n,i;
int a[N];
scanf("%d",&n);
for(i=0;i<n;i++){scanf("%d",&a[i]);}
Mergesort(a,0,n);
for(i=0;i<n;i++){printf("%d ",a[i]);}
return 0;
}
void Mergesort(int a[],int L,int R)
{
if(L>R-2){return;}//当数据只剩下1个的时候,结束,回到上一层递归
int middle=(L+R)/2;//中间值,负责分半
Mergesort(a,L,middle);
Mergesort(a,middle,R);//把数据分开两半
int x=L;//子数据1的下标
int y=middle;//子数据1旁边的子数据2的下标
int t=L;//空白数组的下标
int turn[N]={0};//空白数组
while(x<middle||y<R)//对a[L]……a[middle-1],和对a[middle]…….a[R-1]数据进行合并
{
if(y>=R||(x<middle&&(a[x]>=a[y]))){turn[t++]=a[x++];}//把大的先放进去turn[ ],达到从大到小排序的目的。忘了x<middle时循环可能会越过middle边界。如果子数据2已经越了界,则把子数据1全部放入turn[ ]中
else{turn[t++]=a[y++];}
}
for(int i=L;i<R;i++){
a[i]=turn[i];
}//把turn[ ]中排好的数据放回原来的数组
}
这种递归算法虽然会有空间上的开销(空白数组的开设),和时间上的开销(递归,调用函数)。但是比较好理解,容易易写和相对不容易出错,有兴趣可以上网查一下非递归版本
快速排序:不稳定的更高速算法
原理:也同样用到分治法,将将数据分成两部分再分。不同的是对于每一组数据,将选取其中一个数作为基准,然后分别将大于基准数的元素置于基准数左边,小于则放于右边,最后将基准数两边的数据分开,分别继续刚才的步骤,直到不能再分,但此时,全部数据已经完成排序。
不稳定性:假如我们要将一组数据按大到小排序,而这组数据原来是由小到大排序的话,这时快速排序效率等于冒泡排序,甚至还慢了一点(函数调用),但大部分情况下,快速排序效率高于其他排序,广泛运用于各种函数库。
#include<stdio.h>
#define N 1000
void quicksort(int a[],int L,int R);
void Swap(int *a,int*b);
int main()
{
int n,i;
int a[N];
scanf("%d",&n);
for(i=0;i<n;i++){scanf("%d",&a[i]);}
quicksort(a,0,n);
for(i=0;i<n;i++){printf("%d ",a[i]);}
return 0;
}
void quicksort(int a[],int L,int R){
if(L==R-1){return;}
int x=L+1,y=R-1;
while(x!=y){
while(a[y]<a[L]&&y>x){y--;}
while(a[x]>a[L]&&x<y){x++;}
if(x<y){Swap(&a[x],&a[y]);}
}
if(a[L]<a[y])Swap(&a[L],&a[y]);
quicksort(a,L,x);
quicksort(a,x,R);
}
void Swap(int *a,int*b){
int temp=*a;
*a=*b;
*b=temp;
return;
}
第二种:非比较排序
基数排序:处理小数据的有效排序
输入数据,边输入边边比较各个元素的位数,取出最大位数
进入排序的函数中,从1位(个位数)开始,将元素按照个位数(此时个位数为排序基数)大小的顺序临时放在一个空白数组中,大的放前面,小的放后面。放完后,对2位(十位,此时十位数为排序基数)开始,重复刚才的步骤,如果小于2位,则优先放在最前面。
重复刚才步骤,直到位数过了最大的位数。排序结束了。
该方法通常用于处理小的自然数数据,稳定,比冒泡快。
#include<stdio.h>
#define N 1000
int main()
{
int n,i;
int a[N];
scanf("%d",&n);
int all=-1;
for(i=0;i<n;i++)
{
int s=0;
scanf("%d",&a[i]);
int x=a[i];
while(x){s++;x/=10;}
if(s>all){all=s;}//找到最大位数
}
int lo=1;//排序从1(个位)开始
while(lo<=all){
int k=1;
int Lo=lo;
while(--Lo){
k=k*10;//用来处理元素的工具
}
int A[n+3]={0};
int j,c=0;
for(i=9;i>=0;i--){
for(j=0;j<n;j++){
if((a[j]/k)%10==i)//对每一个元素,先/k使其最后一位变成我们现在进行排序的位数,如我们现在要排2(十位),使123/10=12,12%10=2,2即为123的十位数。
{
A[c++]=a[j];//基数大元素的先放进空白数组。
}
}
}
for(i=0;i<n;i++){a[i]=A[i];}//空白数组中的数据回到原数组
lo++;
}
for(i=0;i<n;i++){printf("%d ",a[i]);}
return 0;
}
所学排序的简单梳理就到这里,欢迎大家提出建议。