简单排序和高级排序(数据结构3&7章——读书笔记)

第三章 简单排序

3.1 如何排序

本章中的三个算法都包括如下的两个步骤,这两步循环执行,知道全部数据有序为止:

1. 比较两个数据项
2. 交换两个数据项,或复制其中的一项。

但是每种算法实现的细节有所不同。

3.2 冒泡排序

冒泡排序算法运行起来非常慢,但在概念上它是排序算法中最简单的,因此冒泡排序算法在刚开始研究排序技术时是一个非常好的算法。
冒泡排序要遵循的规则(假设从左往右开始):

  • 比较相邻的两位;
  • 如果左边的大,则两两交换位置;
  • 向右移动一个位置,继续比较下面两组;
  • 如此循环,当碰到第一个排定的数后,就返回最左端重新开始下一轮排序。
    不断执行这个过程,知道全部排定。

冒泡排序的效率

无论何时,只要看到一个循环嵌套在另一个循环里,例如在冒泡排序和本章中的其他算法中,就可以怀疑这个算法的运行时间为O(N2)级。外层循环执行N次,内部循环对于每一次外层循环都执行N次(或者几分之N次)。这就意味着将大约需要执行N*N或者几分之N2次某个操作。

3.3 选择排序

选择排序改进了冒泡排序,将必要的交换次数从O(N2)减少到O(N)。不幸的是比较次数仍保持为O(N2)。然而,选择排序仍然为大记录量的排序提出了一个非常重要的改进,因为这些大量的记录需要在内存中移动,这就使交换的时间和比较的时间相比起来,交换的时间更为重要。
进行选择排序就是把所有成员扫描一趟,从中挑出最小的和最左端的交换位置。

选择排序的效率

选择排序和冒泡排序执行了相同次数的比较:N*(N-1)/2。对于10个数据项,需要45次比较。然而,10个数据项只需要少于10次交换。对于100个数据项,需要4950次比较,但只进行了不到100次的交换。N值很大时,比较的次数是主要的,所以结论是选择排序和冒泡排序一样运行了O(N2)时间。但是,选择排序无疑更快,因为它进行的交换少得多。当N值较小时,特别是如果交换的时间级比比较的时间级大得多时,选择排序实际上是相当快的。

3.4 插入排序

在大多数情况下,插入排序算法是简单排序算法中最好的一种。虽然插入排序算法仍然需要O(N2)的时间,但是在一般情况下,它要比冒泡排序快一倍,比选择排序还要快一点。尽管它比冒泡排序算法和选择排序算法都更麻烦一些,但它也并不很复杂。它经常被用在叫复杂的排序算法的最后阶段,比如快速排序。
对于已经有序或基本有序的数据来说,插入排序要好得多。然而对于逆序排列,每次比较和移动都会执行,所以插入排序不比冒泡排序快。

3.6 简单排序的比较

冒泡算法过于简单,当数据量很小的时候会有些应用价值,因此一般情况不太推荐冒泡算法。
选择排序虽然把交换次数降到了最低,但比较的次数仍然很大。当数据量很小,并且交换数据相对于比较数据更加耗时的情况下,可以应用选择排序。

第七章 高级排序

7.1 希尔排序

希尔排序基于插入排序,但是增加了一个新的特性,大大地提高了插入排序的执行效率。希尔排序算法的代码既简短又简单。它在最坏情况下的执行效率和在平均情况下的执行效率相比没有差很多。
希尔排序通过加大插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,从而使数据项能大跨度地移动。当这些数据项排过一趟序之后,希尔排序算法减小数据项的间隔再进行排序,一次进行下去。进行这些排序时,数据项之间的间隔被称为增量,并且习惯上用字母h表示。
当h值大的时候,数据项每一趟排序需要移动元素的个数很少,但数据项移动的距离很长。这是非常有效率的。当h减小时,每一趟排序需要移动的元素个数增多,但是此时数据项已经接近于它们排序后最终的位置,这对于插入排序可以更有效率。正是这两种情况的结合才使希尔排序效率那么高。
使用间隔序列的绝对条件是,逐渐减小的间隔最后一定要等于1,因此最后一趟排序是一次普通的插入排序。
间隔序列中的数字互质通常被认为很重要;也就是说,除了1之外它们没有公约数。这个约束条件使每一趟排序更有可能保持前一趟排序已排好的结果。希尔排序最初以N/2为间隔的低效性就是归咎于它没有遵守这个准则。

7.2 划分

划分数据就是把数据分为两组,使所有关键字大于特定值的数据项在一组,是所有关键字小于特定值的数据项在另一组。
根据划分原因,可以选择任何值作为枢纽。
在完成划分之后,数据还不能称为有序:这只是把数据简单地分成了两组。但是数据还是比没有划分之前要更接近有序了。

划分算法

划分算法由两个指针开始工作,两个指针分别指向数组的两头。在左边的指针,leftPtr,向右移动,而在右边的指针,rightPtr,向左移动。
实际上,leftPtr初始化时是在第一个数据项的左边一位,rightPtr是在最后一个数据项的右边一位,这是因为在它们工作之前,它们都要分别的加一和减一。
当leftPtr遇到比枢纽小的数据项时,它继续右移,因为这个数据项的位置已经处在数组的正确一边了。但是,当遇到比枢纽大的数据项时,它就停下来。类似的,当rightPtr遇到大于枢纽的数据项时,它继续左移,但是当发现比枢纽小的数据项时,它也停下来。两个内层的while循环,第一个应用于leftPtr,第二个应用于rightPtr,控制这个扫描过程。
第一个while循环在发现比枢纽大的数据项时退出;第二个循环在发现比枢纽小的数据项时退出。当这两个循环都退出之后,leftPtr和rightPtr都指着在数组的错误一方位置上的数据项,所以交换这两个数据项。
交换之后,继续移动两个指针,当指向的数据项在数组的错误一方时,再次停止,然后交换数据项。所有的这些操作都包含在一个外部循环中。当两个指针最终相遇的时候,划分过程结束,并且退出这个外层while循环。
为了避免越界问题,必须在while循环中增加数组边界的检测;在第一个循环中增加leftPtr<right,第二个循环中增加rightPtr>left。

7.3 快速排序

在大多数情况下,快速排序都是最快的,执行时间为O(N*logN)级。(这只是对内部排序或者说随机存储器内的排序而言,对于在磁盘文件中的数据进行的排序,其他的排序算法可能更好。)
快速排序本质上通过把一个数组划分为两个子数组,然后递归地调用自身为每一个子数组进行快速排序来实现的。但是,对这个基本的设计还需要进行一些加工。算法还必须要选择枢纽以及对小的划分区域进行排序。
基本的递归的快速排序算法有三个基本步骤:
1、把数组或者子数组划分成左边(较小的关键字)的一组和右边(较大的关键字)的一组。
2、调用自身对左边的一组进行排序。
3、再次调用自身对右边的一组进行排序。
经过一次划分之后,所有在左边子数组中的数据项都小于在右边子数组的数据项。只要对左右两遍子数组分别进行排序,整个数组就是有序的了。如何让对子数组进行排序呢?通过递归调用排序算法自身就可以了。

选择枢纽

  • 应该选择具体的一个数据项的关键字的值作为枢纽:称这个数据项为pivot(枢纽);
  • 可以选择任意一个数据作为枢纽。为了简便,我们假设总是选择待划分的子数组最右端的数据项作为枢纽;
  • 划分完成之后,如果枢纽被插入到左右子数组之间的分界处,那么枢纽就落在排序之后的最终位置上了。

来源:《数据结构》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值