高级排序

希尔排序

时间效率:

                       

 

排序概述:

 

希尔排序不像快速排序和其他时间复杂度为O[N*logN]的排序算法那么快.因此对非常大的文件排序,它不是最优的选择.但是希尔排序比选择排序和插入排序这种时间复杂度为O[N^2]的排序算法还是要快得多.

希尔排序由来:

插入排序的缺陷

基于插入排序,插入排序缺点复制次数太多(总共是N^2/2次复制).

假设一个很小的数据项在靠近右端额位置上,这里本该是值较大的数据项所在的位置.

把这个小数据项移动到左边正确的位置上,所有的中间数据项(这个数据项原来所在的位置和它应该移动到的位置之间的数据项)都必须向右移动一位.

这个步骤对每一个数据项都执行了将近N次复制.虽不是所有的数据项都必须移动N个位置.

但数据项平均移动了N/2个位置.这就执行了N次N/2 个移位.平均总共是N^2/2次复制.因此插入排序的执行效率是O[N^2].

 

希尔排序的改进

 如果能以某种方式不必一个一个地移动所有中间的数据项,就能把较小的数据项移动到左边.那么这个算法的执行效率就会有很大的改进. -à增量排序

图解:

以四为增量的一趟排序

 

在完成4为增量的排序后,所有的元素离它在最终有序序列中的位置相差不到两个单元.这就是数组”基本有序”的含义.当数组完成4-增量排序之后,可以进行普通的插入排序.即1-增量排序和.4-增量排序和1-增量排序结合起来应用,比前不执行4-增量排序而仅仅应用普通的插入排序要快得多.

以一为增量的一趟排序<==插入排序

 

H<总数据项>= 13

h <间隔序列>

3 / 1

希尔排序的间隔序列:

H=3h+1;<h=(H-1)/3>

H : 总的数据量

h: 间隔序列

间隔序列                                                        总的数据量

 

间隔序列的计算

int H = 121;

     int h=1;

     while(h<(H-1)/3){

       h=h*3+1;

     }

 

希尔排序 VS 插入排序:

划分

划分定义

把数据分为两组.使所有关键字大于特定数据项的在一组.使所有关键字小于特定值的在另外一组.在数据完成划分之后,数据还不能称之为有序:这只是把数据简单的分成了两组.但是数据还是比没有划分之间更接近有序了.

 

划分算法:

开始工作

 

 

划分算法由两个指针开始工作.两个指针分别指向数组两头.

左边指针,leftPtr<相当于leftScan>向右移动.

右边的指针,rightPtr<相当于rightScan>向左移动.

实际情况:

        

leftPtr初始化时是在第一个数据项的左边一位.

rightPtr是在最后一个数据项的右边以为.

这是因为在他们工作之前,它们都要分别+1和-1.

停止和交换:

第一个内层循环:

当leftPtr遇到比枢纽小的数据项时,继续向右移动.因为这个数据项的位置已经处在数组的正确一边了.但是当遇到比枢纽大的数据项时候,他就停下来.

第二个内层循环:

当rightPtr遇到大于枢纽的数据项时候,它继续左移.但是当发现比枢纽小的数据项时候.它也停下来.

下面是一段扫描不在适当位置上的数据项的简化代码:

//find the bigger item in the left side

While(theArr[++leftPtr]<pivot);

//find the smaller item in the right side

While(theArr[--rightPtr]>pivot)

//swap element

Swap(leftPtr,rightPtr);

第一个while循环在发现比枢纽大的数据项时候退出.

第二个while循环在发现比枢纽小的数据项时候退出.

两个循环结束后.leftPt和rightPtr都指着在数组错误一方位置上的数据项.故交换这两个数据项.

交换之后,继续移动两个指针.当指向的数据项在数组错误一方时候.再次停止.交换数据项.

所有这些操作都包含在一个外部循环中.

这个外部循环停止条件:当两个指针最终相遇的时候.划分过程结束.并且退出这个外层循环.

处理异常数据:

 

异常情况:

如果所有的数据项都小于枢纽.leftPtr<左侧指针>将会遍历整个数组.徒劳的寻找大于枢纽的数据项.然后跑出数组的最右端.产生数组越界异常.

同理.右侧指针.

为了避免这些问题.必须在while循环中添加数组边界检测.

第一个循环中增加leftPtr<right

第二个循环中添加rightPtr>left

采用下面代码 <代码1>

// find bigger item

while(leftPtr < right && theArray[++leftPtr] < pivot)

 ;

而非以下代码 <代码2>:

While(leftPtr<right && theArray[leftPtr] <pivot)

         ++leftPtr;

这些改变导致只有在满足条件的情况下指针才会加1.

指针在任何情况下都必须移动.所以需要在外层循环中增加++这个附加语句强迫指针变化.

 

也有可能出现如上图的情况:

左侧指针错过右侧指针

划分算法的比较

 

划分算法的交换

Max= N/2

 

划分算法的效率:

O[N].

快速排序

效率

O[N*logN]

大多数情况下,快速排序都是最快的.

本质

快速排序算法本质上通过把一个数组划分为两个子数组,然后递归地调用自身为每一个字数组进行快速排序来实现.

额外加工.算法还必须要选择枢纽以及对小的划分区域进行排序.

 

 

 

 

 

 

 

 

 

 

 

快速排序基本步骤:

1 把数组或者子数组划分成左边(较小的关键字)的一组和右边(较大的关键字)的一组.

2 调用自身对左边的一组进行排序

3 再次调用自身对右边的一组进行排序.

经过一次划分后,所有在左边的子数组的数据项都小于在右边子数组的数据项.只要对左边子数组和右边子数组分别进行排序.整个数组就是有序的了.<---递归的调用排序算法自身就可以.

 

 

 

如何做到划分完成之后,枢纽被插入到左右子数组之间的分界处位置上呢?

为了简化把枢纽插入正确位置的操作,只要交换枢纽和右边子数组的最左端数据项就可以了.

这个交换操作把枢纽放在了它的正确位置上.也就是左右子数组之间.

 

 

快速排序算法:

 

 

 

 

代码加速:

在没有大于枢纽的数据项时候.leftPtr<right防止了leftPtr右移越过数组的右端.

现在可以去除这个检测.因为选择最右端数据项作为枢纽.所以leftPtr总会停在枢纽这个位置上.<原因 第一层循环有如下判断: while( theArray[++leftPtr] < pivot )>

 

 

posted @ 2013-07-11 00:38  王超_cc  阅读(464)  评论(0编辑  收藏  举报