数据结构——排序

六、排序

这部分涉及到的代码都在Blog上

(一)排序的基本概念
  1. 排序:即将原本无序的序列重新排列成有序序列的过程。
  2. 稳定性:当待排序的序列中有两个或者多个相同的关键字时,排序前和排序后这些关键字的相对位置,没有发生变化即为稳定的,否则就是不稳定的。
(二)排序算法的分类
  1. 插入类排序
    • 在一个已经有序的序列中,插入一个新的关键字
    • 属于这类的排序有:直接插入排序,折半插入排序,希尔排序
  2. 交换类排序
    • 核心在于“交换”,即每一趟都有交换的动作,最后让元素排到它的最终位置
    • 属于这类排序的有:冒泡排序,快速排序
  3. 选择类排序
    • 核心在于“选择”,每一趟排序都选出最小或者最大的关键字,何其序列中第一个或者最后一个的关键字进行交换。
    • 属于这类的排序有:简单选择排序,堆排序
  4. 归并类排序
    • 将两个或者两个以上的有序序列合并成一个新的序列。
    • 属于这类的排序有:二路归并排序
  5. 基数类排序
    • 多关键字排序,把一个逻辑关键字拆分成多个关键字。
    • 属于这类的排序有:基数排序
(三)插入类排序
  1. 直接插入排序
     基本思想:每趟将一个待排序的关键字按照其值的大小插入到已经排序的有序序列中的适当位置。
  2. 折半插入排序
     基本思想:该算法时直接插入排序算法的改进,重在插入位置的查找不同.这里采用折半查找法,也就是二分查找法来查找插入的位置. 取已经排列好的序列的中间元素,如果比插入的数据大,那么插入的数值肯定在前面部分,否则就在后面部分,一次类推,不断缩小范围,最后确定插入的位置.
  3. 希尔排序
    • 希尔排序又叫做 缩小增量排序.本质还是插入排序.
    • 基本思想:将待排序的序列按照某种规则分成几个分序列,分别对这几个子序列进行直接插入排序。这个算法体现在增量的选取,如果选择增量为5,则1,5,10,15… 被分成一组,2,6,11,16…被分为1组,然后缩小增量为2,1,3,5…为一组,2,4,6…为一组,最后增量为1,则1,2,3,4,5,6得到排序。
    • 希尔排序的每趟排序都会使序列变得更加有序,效率更高,但是不稳定。
(四)交换类排序
  1. 冒泡排序
    • 冒泡排序 是通过一系列的交换动作完成的。
    • 基本思想:首先将第一个元素和第二个元素进行比较,如果第一个大,则两者交换位置,否则不交换;然后第二个元素跟第三个元素比较,依次类推,最终最大的元素就交换到了最后了,到这里还没有结束,这才是一趟冒泡排序,只是取出了序列中的最大值,相同排序还得再多次取出最大值,直到所有元素未发生交换动作为止。
  2. 快速排序
    • 快速排序是通过多次划分操作实现排序,也是交换类排序。
    • 基本思想:以升序为例,每一趟选择当前序列中的一个关键字作为枢轴,将序列中比枢轴小的元素移到枢轴前面,比其大的元素移到其后面,这样就分成了几个子序列,然后分别对子序列进行相同操作,最终会得到一个有序的序列。
(五)选择类排序
  1. 简单选择排序
    • 简单选择排序主要的动作是选择,这类排序采用最简单的选择方式。
    • 基本思想:从头至尾顺序扫描序列,找出最小的一个关键字,和第一个关键字交换,接着从剩下的关键字中继续这种选择和交换,最终使序列有序。
  2. 堆排序
    • 堆是一种数据结构,可以将堆看作是一棵完全二叉树。
    • 二叉树满足:任何一个非叶子结点的值,都不大于(或者不小于)其左右孩子结点的值。若父亲大,孩子小,则为大顶堆;若孩子大,父亲小,则为小顶堆。
    • 基本思想:代表堆的这棵完全二叉树的根结点是最大或者最小的,找出这个值交换到序列的最后或者最前面,然后有序序列关键字增加1个,无序序列的关键字减少1个,对新的无序序列重复这样的操作,就实现了排序。
    • 最关键的操作是:将序列调整为堆
(六 )归并类排序
  1. 二路归并排序(merge sort)
    • 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
    • 基本思想:先将整个序列分为两半,对每一半进行归并排序,将得到两个有序序列,然后将这两个序列归并成一个序列即可。
    • 核心思想:将已有序的子序列合并,得到完全有序的序列
(七)基数排序
  1. 基数排序是多关键字排序。
  2. 有两种实现方式
    • 最高位优先:即先按最高位排成若干子序列,再对每个子序列按次高位排序。
    • 最低位优先:这种方式不必分成子序列,每次排序全体关键字全都参与。
  3. 应用例子:
      将多个数进行排序时,因为关键字为0-9,所以按照这几个关键字来进行。将所要排的数据,按最低位优先进行排列,先看个位,依次分配进关键字数组中,然后依次从0-9这几个关键字中收集回来,然后按照十位进行排序,依次类推,直到最高位排序完,就得到了有序序列。
(八)外部排序

下面是外部排序,我猜只看理论应该是看不懂的,至少我是这样的,我是对照例子一点一点理解的,例子上每进行一步,我就对照着理论看一步才理解的。所以下面只是一些进行的步骤,希望你们也能找到一些例子,对照着理解。

  1. 上面所说的都是内部排序,对于数据特别多,记录规模特别大,内存放不下的情况,所以才有了外部排序。
  2. 外部排序最常用的算法是:归并排序。因为其不需要将所有的记录都读入内存即可完成排序,因此这种算法可以解决由内存空间不足,导致无法排序的问题。
  3. 基本步骤:
    • 假设有一组记录有序,大小为n,将其分为m个规模较小的段,然后取k,将每k个段分为一组,最终得到m/k个组的记录段。这里m个有序记录段称为初始归并段
    • 将一组内k个段的每个段的段首元素读入内存进行比较,取出最值,然后取出值得那个段,后面的元素补上去,依次类推。
    • 上面的k就是指的是k路归并算法
    • 重要的子算法
      • 置换-选择排序:主要适用于初始化 归并段规模的,高效的且不受内存空间限制的排序算法。
        • 构造初始归并段的过程:根据缓冲区的大小自己设置,可以理解为数组的大小),由外存读入记录,当记录充满缓冲区后,选择最小的(看自己的排序了,这里是升序)写回外存即输出,其空缺位置由下一个读入记录来代替。输出的记录成为当前这一个归并段的一部分。当输出的某个记录比上一个输出的记录小的话,这个记录就不能成为当前这一个归并段的一部分,视其为下一个归并段的开始。依次类推,直到分配完m个归并段。
      • 最佳归并树:因为经过置换选择算法之后,得到的是长度不等的初始化归并段,归并策略不同。将当前m组记录归并为m个有序记录段的过程称为一趟归并,每趟归并需要两次I/O操作,读写操作是耗时的,所以为了使归并次数最少,需要用最佳归并树
        • 过程:归并过程用一棵树来描述。带权路径长度 * 2 = I/O操作次数。所以使带权路径长度最小即可。将不同的长度值作为权值,用构造哈夫曼树的方法,就得到了权值最小的树,也就是I/O操作次数最小。
      • 败者树:因为需要从k个值中取出最值,正常情况下,每两个值都得比较一次,这样就太耗费时间了,为了提高取最值的效率,这就需要败者树。
        • 过程:首先了解两个不同类型的结点:
          • 叶子节点:其值为 从当前参与归并的归并段中读入的段首元素记录。叶子节点的个数就是选取的k的值。
          • 非叶子结点:都是度为2的结点,其值为叶子节点的序号,指示了当前参与选择的记录所在的记录段。
          • 建立败者树
            • 对当前读入的k个记录,构造k个叶子结点任意两个为一组,建二叉树。如果结点不是偶数,则将多余的结点放在下一趟处理。
            • 当前参与建树的两个叶子结点,以败者(值大的)所在归并段的序号作为这两个结点的父结点(T),胜者(值小的)所在归并段的序号作为**(T)父结点**。
            • 如果参与建树的两个结点是非叶子结点,则以败者所在归并段的序号为这两个根结点子树的新根结点T,胜者所在归并段的序号作为T的父结点
            • 如果当前参与建树的两个结点,一个是叶子结点,一个是非叶子结点
              • 如果叶子结点是胜者,则叶子结点挂在非叶子结点的空分支上,并以叶子结点记录值所在归并段的序号构造新结点为非叶子结点的父结点。
              • 如果叶子结点是败者,则以叶子结点记录值所在归并段的序号构造新结点作为叶子结点和非叶子结点的新根结点T,原非叶子结点作为T的父结点。
          • 调整败者树
            • 当最小的结点值被读入内存时,后续的记录将要补上,补上之后就需要调整败者树了。
            • 过程:按照上面步骤一次进行比较,再调整。
(九)代码实现
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页