赞
踩
一、排序的概念
排序:有n个记录的序列{R1,R2,…,Rn},其相应关键字的序列是{K1,K2,…,Kn},相应的下标序列为1,2,…,n。通过排序,要求找出当前下标序列1,2,…,n的一种排列p1,p2,…,pn,使得相应关键字满足如下的非递减(或非递增)关系,即:Kp1≤Kp2≤…≤Kpn,这样就得到一个按关键字有序的记录序列:{Rp1,Rp2, …,Rpn}。
(1)内部排序
整个排序过程完全在内存中进行,称为内部排序。
(2)外部排序
由于待排序记录数据量太大,内存无法容纳全部数据,排序需要借助外部存储设备才能完成,称为外部排序。
(3)排序算法
(1)直接插入排序
①基本思想
基本操作是将第i个记录插入到前面i-1个已排好序的记录中,具体过程为:将第i个记录的关键字Ki顺次与其前面记录 的关键字Ki-1,Ki-2,…K1进行比较,将所有关键字大于Ki的记录依次向后移动一个位置,直到遇见一个关键字小于 或者等于Ki的记录Kj,此时Kj后面必为空位置,将第i个记录插入空位置即可。
② 步骤
从空间角度来看,它只需要一个辅助空间r[0]。
从时间耗费角度来看,主要时间耗费在关键字比较和移动元素上。
稳定的排序算法
(2)折半插入排序
①基本思想
把数组折成一半进行排序
②算法分析
采用折半插入排序法,可减少关键字的比较次数。每插入一个元素,需要比较的次数最大为折半判定树的深度,如插入第i个元素时,设i=2j,则需进行log2i次比较,因此插入n-1个元素的平均关键字的比较次数为O(nlog2n)。虽然折半插入排序法与直接插入排序法相比较,改善了算法中比较次数的数量级,但其并未改变移动元素的时间耗费,所以折半插入排序的总的时间复杂度仍然是O(n2)。
③ 步骤
{for( i=2 ; i<=length ; ++i )
{x=r[i];low=1; high=i-1;
while (low<=high ) /* 确定插入位置l */
{mid=(low+high) / 2;
if ( x.key< r[mid].key ) high=mid-1;
else low=mid+1;}
for( j=i-1 ; j>= low; --j ) r[j+1]=r[j]; /* 记录依次向后移动*/
r[low]=x; /* 插入记录 */
}
}
(3)表插入排序
① 基本思想
表插入排序是采用链表存储结构进行插入排序的方法。表插入排序的基本思想是:先在待插入记录之前的有序子链表中查找应插入位置,然后将待插入记录插入链表。
② 步骤
intn=length;
r[0].next=n; r[n].next=0;
for ( i=n-1 ; i>= 1; --i)
{ p=r[0].next;q=0;
while( p>0 && r[p].key<r[i].key ) /* 寻找插入位置*/
{q=p;p=r[p].next;}
r[q].next=i; r[i].next=p;/*修改指针,完成插入 */
}
} /* SLinkListSort */
③算法分析
每插入一条记录,最大的比较次数等于已排好序的记录个数,即当前循环链表长度。总的比较次数为:
(4)希尔排序
① 基本思想
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量
② 步骤
input: an array a of length n with array elements numbered 0 to n − 1
inc ← round(n/2)
while inc > 0 do:
for i = inc .. n − 1 do:
temp ← a[i]
j ← i
while j ≥ inc and a[j − inc] > temp do:
a[j] ← a[j − inc]
j ← j − inc
a[j] ← temp
inc ← round(inc / 2.2)
③ 算法分析
希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O(
(5)归并排序
① 基本思想
比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
② 步骤
由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
② 步骤
初始化堆:将R[1..n]构造为堆;
将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。
因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。
③ 算法分析
它的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。
在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为log2i+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。
所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n2)的时间复杂度了。
空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的比较与交换是跳跃式进行,因此堆排序是一种不稳定的排序方法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。