排序算法(一)

一般来说,排序算法有10种,今天先说4种,并给出基本的代码。如有错误,欢迎大家指正。如果大家比较忙,忙着去泡妞的话,可以直接跳到最后看我的小结部分。

为了便于后面的讨论和理解,这里先做一个约定,就是把你要排序的元素假想为大小不一的一些球,这些球都放在一个容器里。排序的过程是把这些球拿出来按照一个指定的顺序的摆放(这里指定的顺序我们先定为升序,倒序是一回事)。同时预先定义两个方法,假定我们要比较的元素是整型数字,其实要改成通用的类型,也是很容易的一件事。

public static boolean less(int a,int b){

return a<b;

}


public static void exch(int [] arr,int i,int j){

int temp=arr[i];

arr[i]=arr[j];

arr[j]=temp;

/**

这里给大家说一个不需要临时变量就交换两个数的技巧,异或就可以了。如下:

arr[i]=arr[i]^arr[j];

arr[j]=arr[i]^arr[j];

arr[i]=arr[i]^arr[j];

**/

}

1. 选择排序

顾名思义,不断地把容器中最小的球选择出来,依次摆放在前面已经拿出来的球后面。直至容器中没有球为止。大家看一下代码:

/**

** arr 要排序的数组

** start 排序数组的开始位置

** end 排序数组的结束位置

**/

public static void sort(int [ ] arr,int start,int end){

int i,j,min;

for(i=start;i<end-1;i++){

min=i;

for(j=start+1;j<=end;j++){

if(less(arr[j],arr[min]) min=j;

}

exch(arr,i,min);

}

}

现在来分析一下分析一下这个算法的性能,排序要完成,第一次选出最小的数,需要N-1比较,第二次需要N-2,…,一直到1;那么总共的比较是(N-1)+(N-2)+(N-3)+…+1=N(N-1)/2,时间复杂度O(n^2);交换次数为N-1次,这是显而易见的。

2.插入排序

插入排序,我们从容器拿球出来时,并不要求拿最大的还是最小,依次拿出来就可以了。拿出来以后,放到已经拿出来的球中的适当位置,大家想想在排队时,要把个子矮的人调整到适当的位置,都是和他前面的比较,如果比他前面的人矮,就插入到前面去,直到他前面的人都比他个子矮,这样最后就可以形成一个有序的队伍。下面是代码:

public static void sort(int [ ] arr,int start,int end){

int i,j,temp;

/**

这里是找出元素中最小的一个,避免后面插入时要判断是否已经到达数组

第一个位置。

**/

for(i=end;i>start;i--) if(less(arr[i],arr[i-1])) exch(arr,i,i-1);

for(i=start+2;i<=end;i++){

temp=arr[i];

j=i;

/**

注意,这里在处理插入时,采用赋值操作的实现当然要比交换操作快。

**/

while(less(temp,arr[j-1]){

arr[j]=arr[j-1];

j--;

}

arr[j]=temp;

}

}

现在来分析一下插入排序,通过内部循环大家可以看到,比较的次数和移动的次数是一样的。在最坏情况下,插入第i个数时,需要比较i-1和移动i-1次,因此最坏情况下,需要N(N-1)/2次比较和移动,平均情况是N(N-1)/4次比较和移动。

3.冒泡程序

冒泡,大家想想,水里气泡是不是从底向上组件冒到上面的。我们对容器里的小球采用冒泡排序的话,我们也是不断地把最小的球浮到上面来。最后形成一个有序的小球队列。还是先看代码

public static void sort(int [ ] arr,int start,int end){

int i,j;

//最多只需要N-1趟就可以排好序了,因为一次把一个小的球浮上来。

for(i=start;i<end;i++){

for(j=end;j>i;j--){

if(less(arr[j],arr[j-1])) exch(arr,j,j-1);

}

}

}

其实上面的程序还可以进一步改进,如果第i趟时,没有发生交换,就说明已经排好序了。和前面的分析一样,第i趟冒泡排序需要N-i次比较和交换。总的来说,平均和最坏情况下都需要N^2/2次比较和交换操作(理论证明有点复杂,但是牛人已经证明过了)。

4.希尔排序

希尔排序本质上就是插入排序,大家有没有注意到,如果在一个数组里,最小的一个元素在最后一个位置,插入排序时,我们是不是需要移动N-1次才能放到具体的位置?于是,shell这个人就想,我们是不是可以以一定的步长把数组里的元素分成几个序列?分别对这些子序列使用希尔排序,最后步长减小为1时再使用插入排序搞定。Ok,这就是希尔排序了。当然了,希尔排序的难点就是步长的选择,到底多少合适呢?说实话,算法大牛们到现在为止都没有证明出哪一个步长序列是最好的。我这里就是用广泛采用的步长序列吧,1,4,13,40,121…(其实就是3x+1形成的序列),这个步长下,时间复杂度是O(N^(3/2));代码如下:

public static void sort(int [ ] arr,int start,int end){

int i,j,temp,h;

//计算步长

for(h=1;h<=(end-start)/9;h=3*h+1);



//开始以不同步长进行插入排序

for(;h>0;h/=3){

//注意,这里后面i++,而不是i+=h

for(i=start+h;i<=end;i++){

j=i;temp=arr[i];

while(j>=start+h&&less(temp,arr[j-h]){

arr[j]=arr[j-h];

j-=h;

}

arr[j]=temp;

}

}

}

小结:选择排序,插入排序和冒泡排序都是O(n^2)的,希尔排序低于O(n^2)。实际应用过程中,对于数据量较小时(一般来说,小于1w),一般可以使用选择排序,插入排序和冒泡排序,中等数据量(小于100w)可以采用希尔排序,可以取得较好的效果。即使对于小数据量,还可以进一步分析,选择排序是不管输入数据的形态怎样(也就是说,不管输入的数据是随机的,还是几乎有序的,还是倒序的),时间复杂度都是O(N^2),然而如果数据几乎是有序的时候,采用插入排序和冒泡排序都可以在线性时间内完成排序。那么选择排序有没有作为首选的时候呢?答案是肯定的,如果我们要排序的文件比较大时,移动文件耗费的时间占据了很多时间,这个时候,比较操作是微不足道的,根据前面我们的分析,选择排序需要的移动(即交换)次数是最少的,这时选择排序当然是我们的首选了。

接下来,我准备说一下快速排序,归并排序和堆排序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值