每日学习录(数据结构—排序)上

9.1排序的基本概念与分类

假设含有n个记录的序列为{r1,r2,······.rn},其相应的关键字分别为{k1,k2,······,kn},需确定1,2,······,n的一种排序p1,p2,······,pn,使其相应的关键字满足kp1≤kp2≤······≤kpn(非递减或非递增)关系,即使得序列成为一个按关键字有序的序列{pp1,rp2,······rpn},这样的操作就称为排序。

在排序问题中,通常将数据元素称为记录。显然我们输入的是一个记录集合,输出的也是一个记录集合,所以说,可以将排序看成是线性表的一种操作。

排序的依据是关键字之间的大小关系,那么,对同一个记录集合,针对不同的关键字进行排序,可以得到不同序列。 

9.1.1排序的稳定性

正是由于排序不仅是针对主关键字,那么对于次关键字,因为待排序的记录序列中可能存在两个或两个以上的关键字相等的记录,排序结果可能会存在不唯一的情况,我们给出了稳定与不稳定排序的定义。

假设ki=kj(1≤i≤n, 1≤j≤n, i≠j),且在排序前的序列中ri领先于rj(即i<j)。如果排序后ri仍领先于rj,则称所用的排序方法是稳定的;反之,若可能使得排序后的序列中rj领先ri,则称所用的排序方法是不稳定的。

9.1.2内排序与外排序

根据在排序过程中待排序的记录是否全部被放置在内存中,排序分为:内排序和外排序。

内排序是在排序整个过程中,待排序的所有记录全部放置在内存中。外排序是由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行。我们这里主要就介绍内排序多种方法。

对于内排序来说,排序算法的性能主要是受3个方面影响:

1.时间性能

排序是数据处理中经常执行的一种操作,往往属于系统的核心部分,因此排序算法的时间开销是衡量其好坏的最重要的标志。在内排序中,主要进行两种操作:比较和移动。比较指关键字之间的比较,这是要做排序最起码的操作。移动指记录从一个位置移动到另一个位置,事实上,移动可以通过改变记录的存储方式来予以避免(这个我们在讲解具体的算法时再谈)。总之,高效率的内排序算法应该是尽可能少的关键字比较次数和尽可能少的记录移动次数。

2.辅助空间

评价排序算法的另一个主要标准是执行算法所需要的辅助存储空间。辅助存储空间是除了存放待排序所占用的存储空间之外,执行算法所需的其他存储空间。

3.算法的复杂性

注意这里指的是算法本身的复杂度,而不是指算法的时间复杂度。显然算法过于复杂也会影响排序的性能。

根据排序过程中借助的主要操作,我们把内排序分为:插入排序、交换排序、选择排序和归并排序。

本章一共要讲解七种排序算法,按照算法的复杂度分为两大类,冒泡排序、简单选择排序和直接插入排序属于简单算法,而希尔排序、堆排序、归并排序、快速排序属于改进算法,后面我们依次讲解。

9.1.3 排序用到的结构与函数

为了讲清楚排序算法的代码,先提供一个用于排序用顺序表结构,此结构也将用于之后我们要讲的所有排序算法。

另外,由于排序最最常用到的操作是数组两个元素的交换,我们将它写成函数,在之后的讲解中会大量的用到。

9.2冒泡排序

9.2.1最简单排序实现

冒泡排序(Bubble Sort)一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,知道没有反序的记录为止。冒泡的实现在细节上可以有很多种变化,我们将分别就3种不同的冒泡实现代码,来讲解冒泡排序的思想。这里,我们就先来看看比较容易理解的一段。

、这段代码严格意义上说,不算是标准的冒泡排序算法,因为它不满足“两两比较相邻记录”的冒泡排序思想,它更应该是最最简单的交换排序而已。

9.2.2冒泡排序算法

下面我们来看看正宗的冒泡排序。

9.2.3冒泡排序优化

试想一下,如果我们待排序的序列是{2,1,3,4,5,6,7,8,9},也就是说,除了第一和第二的关键字需要交换外,别的都已经是正常顺序。当i=1时,交换了2和1,此时序列已经有序,但是算法仍然不依不饶地将i=2到9以及每个循环中的j循环都执行了一遍,尽管没有交换数据,但之后大量比较是多余的,如下图。

所以,我们改进如下:

9.2.4 冒泡排序复杂度分析

当最好的情况,也就是要排序的表本身就是有序的,那么我们比较次数为n-1次,没有数据交换,时间复杂度为O(n)。当最坏的情况,即待排序表是逆序的情况,次数需要比较1+2+3+···+(n-1)=n(n-1)/2次,并作等量级的记录移动。因此,总的时间复杂度为O(n2)。

9.3简单选择排序

简单选择排序法(Simple Selection Sort)就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1≤i≤n)个记录交换。

代码如下:

9.3.1简单选择排序复杂度分析

从简单选择排序的过程来看,它最大的特点就是交换移动数据次数相当少,这样也就节约了相应的时间。分析它的时间复杂度发现,无论最好最差的情况,其比较次数都是一样多的,第i趟排序需要进行n-i次关键字的比较,此时需要比较n-1+n-2+···+1=n(n-1)/2次。而对于交换次数而言,当最好的时候,交换为0次,最差的时候,也就初始降序时,交换次数为n-1次,基于最终的排序时间是比较与交换的次数总和,因此,总的时间复杂度为O(n2)。

应该说,尽管与冒泡排序同为O(n2),但简单选择排序的性能上还是要略优于冒泡排序。

9.4直接插入排序

直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。

9.4.1直接插入排序复杂度分析

从空间上来看,它只需要一个记录的辅助空间,因此关键是看它的时间复杂度。

当最好的情况,也就是要排序的表本身就是有序的,那么我们比较的次数,其实就是代码第5行每个L.r[i]与L.r[i-1]的比较,共比较了n-1次,由于没有移动的记录,时间复杂度O(n)。

当最坏的情况,即待排序是逆序的情况,此时需要比较2+3+···+n=(n+2)(n-1)/2次,而记录的移动次数也达到最大值3+4+···+n+1=(n+4)(n-1)/2次。

如果排序记录是随机的,那么根据概率相同的原则,平均比较和移动次数约为n2/4次。因此,我们得出直接插入排序法的时间复杂度为O(n2)。从这里也看出,同样的O(n2)时间复杂度,直接插入排序法比冒泡和简单选择排序的性能要好一些。

9.5希尔排序

9.5.1 希尔排序原理

前面一节讲了直接插入排序,它在记录数比较少或基本有序的情况下,效率是比较高的,但现实中记录少或者基本有序都属于特殊情况。

希尔排序是对直接插入排序的改进,它将原本有大量记录数的记录进行分组。分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,注意只是基本有序时,再对全体记录进行一次直接插入排序。

这里需要强调一下,所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,所以这里我们还需要采取跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。

9.5.2希尔排序算法

9.5.3希尔排序复杂度分析

通过这段代码,我们可以看出,希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。

这里“增量”的选取就非常关键了。我们在代码中第6行,是用increment=increment/3+1的方式选取增量的,可究竟应该选取什么样的增量才是最好的,目前还是一个数学难题,迄今为止还没有人找到一种最好的增量序列。不过大量的研究表明,当增量序列为dlta[k]=2t-k+1-1(0≤k≤t≤[log2(n+1)])时,可以获得不错的效率,其时间复杂度为O(n3/2),要好于直接排序的O(n2)。需要注意的是,增量序列的最后一个增量值必须等于1才行。另外由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。

目录

9.1排序的基本概念与分类

9.1.1排序的稳定性

9.1.2内排序与外排序

9.1.3 排序用到的结构与函数

9.2冒泡排序

9.2.1最简单排序实现

9.2.2冒泡排序算法

9.2.3冒泡排序优化

9.2.4 冒泡排序复杂度分析

9.3简单选择排序

9.3.1简单选择排序复杂度分析

9.4直接插入排序

9.4.1直接插入排序复杂度分析

9.5希尔排序

9.5.1 希尔排序原理

9.5.2希尔排序算法

9.5.3希尔排序复杂度分析


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值