精华内容
下载资源
问答
  • 用程序实现插入法排序、起泡法、选择法、快速法、合并法排序; 输入的数据形式为任何一个正整数,大小不限。 输出的形式:数字大小逐个递增的数列。 功能要求:给出多组不同元素个数的输入数据(可考虑用随机函数生成...
  • 各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶,或大概执行时间。试通过随机的数据比较各算法的关键字比较次数和关键字移动次数,以取得直观感受。 二.基本要求 (1)对以下10种常用的内部排序...
  • 内部排序算法比较

    2018-12-13 18:54:10
    内部排序算法比较》 【问题描述】 在教科书中,各种内部排序算法的时间复杂度分析结果只给出算法的大致执行时间。试通过随机数据比较各算法的关键字比较次数和关键字移动次数,以获得直观感受 【基本要求】 (1)...
  • 数据结构与算法实验报告 需求分析 问题描述在教科书中各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶或大概执行时间试通过随机数据比较各算法的关键字比较次数和关键字移动次数以取得直观感受 基本...
  • PAGE 实 验 报 告 课程名称 数据结构 实验名称 常用的内部排序算法 作者 育人 一实验目的 掌握常见的内部排序算法的思想及其适用条件 掌握常见的内部排序算法的程序实现 二实验内容及要求 任务描述 1任务设计一个...
  • 内部排序算法比较.rar

    2019-05-22 14:07:45
    在教科书中,各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶或大概执行时间。试通过随机数据比较各算法的关键字比较次数和关键字移动次数,以取得直观感受。 【基本要求】 (1)对以下6中常用的...
  • ADT OrderableList RandomizeList 随机大乱的可排序表 BubbleSort &c & s c 和移动次数 s c 和移动次数 s c 和移动次数 s c 和移动次数 s c 和移动次数 s c 和移动次数 s visit 2 for(j=i+1;j;j++ 3 int i,j;int ) s...
  • 输入n个整数,分别用希尔排序、快速排序、堆排序和归并排序实现由小到大排序并输出排序结果。要求n=10,15,20进行三组排序实验。 实验目的:掌握希尔排序、快速排序、堆排序、归并排序算法。 (zip中含代码及运行...
  • 六种内部排序算法比较:直接插入排序、希尔排序、冒泡排序、快速排序、选择排序、堆排序。包含实验报告和源代码设计。
  • NULL 博文链接:https://hxraid.iteye.com/blog/646760
  • 内部排序算法比较 第一章 问题描述 排序是数据结构中重要的一个部分也是在实际开发中易遇到的问题所以研究各种排 算法的时间消耗对于在实际应用当中很有必要通过分析实际结合算法的特性进行选择和使 用哪种算法可以...
  • 内部排序 9排序的基本概念 92插入类排序 93交换类排序法 94选择类排序法 95归并排序 96分配类排序 97各种排序方法的综合比较 98总结与提高 9排序的基本概念 排序:有n个记录的序列R1,R2,Rn},其相应 关键字的序列是1,...
  • 内部排序的文档说明,和各排序算法的代码实现
  • 10.1 排序的定义和方法 ;一般情况下对排序的定义为 假设含有n个记录的序列为 ... 根据涉及的存储器不同将排序方法分为两大类 1内部排序在排序进行的过程中不使用计算机外部 存储器的排序过程 2外部排序在排序进行的过程
  • 内部排序算法

    2017-05-10 08:57:38
    描述了几种主要的排序算法,有测试截图和源代码等等
  • C语言内部排序算法比较,数据结构课程设计所用
  • 数据结构课件:第10章 内部排序.ppt
  • 三快速排序 首先对无序的记录序列进行一次划分之后分别对分割所得两个子序列递归进行快速排序 无 序 的 记 录 序 列 无序记录子序列(1) 无序子序列(2) 枢轴 一次划分 分别进行快速排序 void QSort( SqList &L, int ...
  • 内部排序算法全面总结

    千次阅读 2020-03-27 21:44:28
    本文的主要内容是关于八大排序算法,以及其衍生算法的总结,系统的分析了直接插入排序,快速排序等重要排序算法的思路,代码实现等。

    排序的概念

    排序,就是重新排列表中的元素,使表中的元素按照关键字有序的过程。
    我所了解的 表 多半是顺序表,因为顺序表实现较为简单,但是链表结构同样可以实现很多排序算法。
    定义中的元素一般指什么元素呢?
    一般可分为两大类 基本类型 自定义类的类型

    基本类型指的就是整型,浮点型,字符型这些属于语言基本类型的东西,比如在js中基本类型就包括 string number bool object symbol,undefined,null
    自定义类一般就是 语言中封装的结构体或者类,对象等,不同编程语言可能有些差别,C语言中就是struct结构体 js中就是包含相同子段的对象的集合。
    在这里插入图片描述
    上图展示C语言中的结构体,一个Student结构体中包括了name,
    age,score三个字段。

    我们为什么需要对排序的元素做这样的划分呢?

    排序的稳定性

    回答上面的问题牵涉到排序算法的稳定性。首先给出排序算法稳定性的一个拗口的定义:

    假设待排序表中有两个元素Ri和Rj,其对应的关键字相同即keyi = keyj,且在排序前Ri在Rj之前,若使用某一算法排序后,Ri仍在Rj之前,则称这个排序算法是稳定的,反之则不稳定。

    注意,此时的排序算法是针对关键字key的。那么这段定义到底表述的是什么意思呢?我们可以先把目光集中到上面定义的Student结构体中。
    在这里插入图片描述
    假设我们现在有个关于Student的顺序表,对于关键字age来说,顺序表中的结构体是无序的,因为我们期待的是age从小到大排列,实际情况却是从大到小。
    那么如果一个排序算法要针对上述age字段进行排序,我们可以发现的一点是张三和李四的年龄是一样的,并且张三在李四的前面,排序算法按照上述定义如果是稳定的那么排完序之后,王二到了第一位,因为他年龄最小,并且张三和李四的前后相对位置不能发生改变。
    排序的稳定性有何意义?
    它可以记录数据排列之前的状态,举一个实际的例子,假设我们一张表,表中记录了一个学校全部学生的高考成绩,字段包括姓名,班级,分数。
    一开始表中数据状态是散乱的,首先我们按照成绩进行从大到小排序,我们会得到一张新表,表中的记录是按照成绩的高低从大到小依次展示的。 然后我们再按照班级来进行排序,那么结果会是怎么样的呢?
    如果我们使用的排序算法是稳定的话,表中的记录会是这样的:一班同学全体在表的开始,然后是二班,三班。。。
    并且每班同学的成绩是从高到低依次排列的
    现在大家应该可以理解排序算法稳定性的重要性意义了。
    我们回到最开始的问题,排序元素为什么要分为基础数据和自定义类呢?
    要知道基础数据是长得一模一样的,那么排序的稳定性就不需要关注了,可是自定义类就不一样了,就向上面所举的例子,因为一条记录包含不同的字段,实际过程中不可能有两条一模一样的记录。

    排序的分类

    内部排序

    排序期间元素全部放在内存中的排序

    外部排序

    指排序期间元素无法全部同时存放在内存中,必须在排序的过程中根据要求不断在内外存之间移动的排序。

    主要内容

    我们研究的排序算法主要是内部排序,内部排序包括 插入排序,交换排序,选择排序,归并排序,基数排序等。下面分块总结

    排序总结

    插入排序

    插入排序包括三种排序方法 直接插入排序,折半插入排序, 希尔排序。

    直接插入排序

    在这里插入图片描述
    思路:
    (1)假设我们有一个序列List,它的前一小部分已经是有序的了,后面的部分还是无序的。
    我们从无序的部分中拿出第一个元素 A ,将他与前面的有序部分的最后一个元素进行 S 比较,如果A比S大,那么不需要改变A的位置,插入结束。如果 A 比 S小,那么就需要将A与S交换位置,然后一直比较下去,直到找到第一个小于等于他的数字为止,交换结束,插入完毕。
    (2)第二种思路,找到L(i)在L[0…n]中的插入位置k,将L[k…i-1]中的所有元素后移一个位置,将L(i)复制到前面已经排好的序列中去。

    代码解释:首先我们定义了一个swap的方法,这个方法就是为了交换一个数组中指定位置的两个数。内部排序算法多半就基于比较和交换的,所以我们在其他的排序算法中同样会使用swap方法,到时候就不再赘述。
    上面的直接排序算法是根据思路1写出,思路2的代码本人认为不够简洁以及容易理解,在此不再赘述。

    空间复杂度:O(1)
    时间复杂度:O(n²)
    排序稳定性:为稳定的排序算法,因为只有插入项小于比较项的时候才会发生交换。
    适用性:适用于顺序表和链表。

    折半插入排序

    折半插入排序就是在寻找插入位置的时候不是一个个向前比较,而是利用二分法迅速的找到我们需要插入的位置,但是我们需要向后移动元素的个数并没有减少,所以折半插入排序的时间复杂度依然是O(n²)
    在这里插入图片描述
    总之折半直接插入算法,就是直接插入算法的基础上加入了二分查找法。

    希尔排序

    直接插入算法适用于基本有序的排序表和数据量不大的排序表。也就是说数组越有序,那么希尔排序的效率越高,因为他所需要移动的元素越少。
    又有一位比较牛逼的科学家 Donald Shell提出了一个观点,要是可以把待排序表分割成若干组长度较小的序表,然后对各个子表进行直接插入排序,当整个表中的元素已呈基本有序,再对全体记录进行一次直接插入排序。
    通俗点说我们 可以理解为表中各个小的地方都有序了,那么整体自然更加的趋向有序。
    那么具体操作过程是什么?我们怎么去选择子序列?
    希尔提出,假设我们的数组长度是n 我们第一次取d1 = [n / 2]
    所有距离为d1倍数的元素被分在了一组。
    如果我们的数组长度为10,{0,1,2,4,5,6,7,8,9},那么第一次取d1 = 5,则数组中的分组为
    【0 5】 【1 6】 【2 7】 【3 8】 【4 9】
    我们先在这些个子序列中利用直接插入排序把他弄有序,然后缩短步长,取d2 = [d1 / 2] = 3(注意这里是向上取整)
    那么此时的分组就是
    【0 3 6 9】 【1 4 7】 【2 5 8】
    注意d等于几我们就分成了几组
    最后一组d一定是1,因为我们需要对整个数组进行插入排序
    希尔排序
    代码解析:希尔排序的代码并不是那么好理解,首先我们取步长
    d为 length / 2 说明我们要分d组 ,所以第一层for循环表示的是,每次循环表示一个分组。内层循环表示遍历这个组的所有数,一个某个数满足插入的条件进行交换。循环结束后改变步长为 d / 2;d最小为1,比1小就跳出循环。
    稳定性:希尔排序不是稳定的排序算法

    插入排序的一些注意点

    • 对n个不同的数据元素进行直接插入排序,最多需要的比较次数为 n(n - 1)/ 2,最小的比较次数为 n - 1
    • 待排序的元素序列基本有序的前提下,直接插入排序的时间复杂度接近O(n),且空间复杂度极低
    • 折半插入排序和直接插入排序的区别仅仅在于元素之间的比较次数

    交换排序

    概念

    王道教材上交换排序是指序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。主要排序算法是冒泡排序和快速排序。其实我觉得这个分类并不够准确,因为插入排序也可以实现为交换排序,就跟我上面思路一实现的代码一模一样。

    冒泡排序

    冒泡排序很简单,但是实际做项目里面也很少会用得到。
    思路:从后往前(或者从前往后)两两比较相邻元素的值,若为逆序则交换他们,直到序列比较完。第一趟冒泡的结果总是将最小的元素交换到待排序列的第一个位置(或者是把最大的元素交换到待排序列的最后一个位置)。
    代码实现
    1.我见过最简单的一种冒泡,对于长度为n的序列来说,需要经过n -1次冒泡,每次固定住一个元素的位置。
    在这里插入图片描述
    这种算法有个明显的缺陷,那就是比较次数,就算是序列本身已经是有序的了,还是需要比较,这就说明比较次数是和数据状态无关的。
    2.第二种代码对上面代码进行了改进,如果在某躺冒泡中,我们发现元素并没有进行交换了,那么说明此时序列已经排完了,可以跳出循环。
    在这里插入图片描述
    一个简单的实验,用一个count变量记录第一种冒泡和第二种冒泡对于一个正序序列(长度为7)进行比较的次数,我们发现第一种count等于21,第二种为6.
    时间复杂度:最好情况O(n),最坏情况O(n²)
    空间复杂度:O(1)
    比较次数:最好情况 n -1,最坏情况 n(n -1)/2
    稳定性:稳定的排序算法。

    快速排序

    快速排序是所有内部排序算法中性能最优的排序算法,并且也是最不容易理解的一种排序算法。
    思路
    快排是我们接触的第一种基于分治法的算法,他将一个大问题划分成了两个规模更小的小问题。我们首先从排序表中找到一个基准元素(pivot)通过一趟排序将待排序表分为独立的两部分,前半部分小于pivot,后半部分大于等于pivot。然后递归这两个子部分,直到每部分只有一个元素或者空为止。
    现在出现了一个主要问题,如何使得待排序表能够被划分成两部分,其中一部分小于pivot,另一部分大于等于pivot?这个过程我们把它称为partition。
    在这里插入图片描述
    上述图的过程很清晰的展现了快速排序一个partition的过程。
    代码实现:
    在这里插入图片描述
    快速排序的性能分析较为复杂,由于快速排序是递归的(实际上所有的递归算法都可以变化成非递归),需要借助一个递归工作栈来保存每层递归调用的必要信息,其容量应与递归调用的最大深度一致。最好情况下为O(log2n),最坏情况下为O(n)
    时间复杂度:最佳情况为O(n * logN),提升快排算法的方法,pivot尽量选择为中间大小的数字,使得能够平分两个子过程。
    稳定性:快速排序算法是不稳定的。

    快速排序算法的partition思想特别重要,它可以在排序的同时,准确定位到排序表中特定位置的元素,划分算法一定要想到快速排序的思想,下面举一个题目的例子。
    找出一个无序表中第k小的元素
    在这里插入图片描述

    选择排序

    基本思想:每一趟(第i躺),在后面n - i + 1个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到n - 1躺做完,待排序元素只剩一个,排序结束。
    选择排序和冒泡排序的差不多,都是在,每一趟中网有序子序列中添加一个元素。

    简单选择排序

    思路:假设我们有排序表L[1…n],第 i 躺排序从L[i…n]中选出一个最小的,将其与第 i 位交换,每一趟都可以确定一个元素的最终位置,并且保证已排的子序列有序。
    代码实现:
    在这里插入图片描述
    时间效率:我们可以发现,简单选择排序的比较次数与序列的初始状态无关,始终为n (n - 1) / 2
    空间效率:O(1)
    稳定性:不稳定

    堆排序

    堆排序可以说是和快速排序同样重要的一种排序。
    堆排序将一个数组看成一棵完全二叉树(关于完全二叉树的定义请自行百度,篇幅有限,不再赘述)。第i个结点的左孩子的序号是2i + 1,
    右孩子的序号为2i + 2,他的双亲结点的序号为(i - 1)/ 2.
    一个特殊的结点为数组中序号为0的结点,它的双亲结点还是自己,所以能够和其他结点之间进行区分。
    在这里插入图片描述
    知道了堆以后,还需要明白两个概念:大根堆和小根堆
    大根堆:所有子树的根结点都是最大的
    L[i] >= L[2i + 1] && L[i] >= L[2i + 2]
    小根堆:所有子树的根结点都是最小的
    L[i] <= L[2i + 1] && L[i] <= L[2i + 2]
    大根堆示意图
    上述图展现了一个大根堆,所有树的根结点必然最大,并且左右孩子结点的大小不定。
    那么知道啥叫大根堆以后,我们又如何来根据这一特殊的结构来进行排序呢?
    首先我们拿到一个顺序表,将其转化成一个大根堆,那么我们可以发现大根堆的根结点一定是最大的元素(这点极其重要!!!)
    然后我们将大根堆的堆顶(就是根结点)和最后一个元素交换,将这个位置固定。此时,我们看上图,假设我们将100和7交换位置,那么此时的堆就不再是大根堆了,需要将其再次转换成大根堆,但是注意的是100的位置我们就不需要去动了。
    也就是说,堆排序一趟排序确定了当前堆中最大的元素,并将其放到堆的最后一个位置,n - 1躺过后必然已经称为一个有序的数组。
    现在有两个问题:

    1. 我们如何将一个数组初始化为一个大根堆?
    2. 当大根堆的堆顶元素发生改变后我们又怎么将其重新转换成大根堆?
      在这里插入图片描述
      上述代码中heapSort中使用了一个for循环,这个for循环的作用是,将第i位元素添加到堆中,并且保证堆是一个大根堆。
      代码的灵魂在于heapHelper方法,当新加入的元素比自己的双亲结点大的时候,将其互换,一直持续到,自己的某个双亲结点不必自己大了,就跳出循环。
      这里有个特殊的点是0,当index = 0的时候,我们发现(index - 1)/2
      依旧是0,所以二者相等,跳出循环。

    好了,至此,我们已经掌握了一个数组转化成大根堆的方法了。
    接下来的问题就是将堆顶元素与堆中最后一个元素交换以后,怎么将它再次的变成一个大根堆。
    在这里插入图片描述
    这里我们着重论述一下heapify的过程,heaplify有三个参数,第一个是待排序的数组,第二个是开始大根堆化的根结点index(一般都是0),第三个是标识当前堆大小的heapSize。
    首先获取当前结点的左孩子结点left(通过对应的序号关系就可以得到),当left并未超过当前堆长度的时候,我们开始循环。
    思路是找到index结点的左右孩子中较大的那个,给largest赋值的这句长代码就是完成了这个任务。
    第二步比较 index根结点 与 左右孩子结点中较大值 之间的大小 并重新给largest赋值
    第三步 做判断 如果largest就等于index根结点,说明 index根结点本身就是最大的,不需要往下交换了。否则,就需要和较大结点之间做交换 ,并且给index重新赋值,进行下一次循环。

    上面的代码我觉得是较容易理解的版本了,逻辑和变量语义化来看都非常好。

    关于堆排我们已经完成了最主要的两个任务了,下面是堆排序的完整代码。
    在这里插入图片描述
    值得注意的是,当我们每次将根结点(最大值)swap到最后的时候,需要通过–heapSize使得堆的长度减1来固定这个最大值。

    空间效率:O(1)
    时间效率:O(n * logn)
    稳定性:不稳定的排序算法

    堆排序的一些常考的注意点

    堆排建堆的时间效率不会超过4n(虽然我也不知道咋算出来的)。
    在尾部插入一个元素或者从堆顶弹出一个元素都是 logN的时间效率。
    当我们需要在大量数据中找出k个最大或者最小的元素时都采用堆排序。

    归并排序与基数排序

    归并排序的思想独树一帜,它是将长度为n的待排序列表看成 n 个小组,两两和并 使其有序,这样我们就获得了【n / 2】(向上取整)个有序的小组,这个过程被称为一趟2路归并,直到合并成长度为n的有序序列。
    在这里插入图片描述
    空间效率:需要空间为n的辅助数组O(n)
    时间效率:O(n * logN)
    稳定性:稳定的排序算法

    基数排序

    基数排序较为奇特,一般是基于链表来实现的一种排序,它不基于移动和比较,而是将单个关键字转换成多个关键字进行排序的方法
    比如对于一群小于1000的数字来说,每个数字可以看成有个,十,百三个关键字组成。于是我们可以把数字一个关键字,拆分成个,十,百三个关键字。
    基数排序包含两种方法,第一种是最高位优先(MSD)法,按关键字位权重递减一次逐层划分成若干更小的子序列,最后将子序列依次连接。第二种最低位优先法,与上个方法是相反的过程。
    那么基数排序具体如何操作呢?下面以最低位优先法举例。
    First,先定一个基数r,这个基数r代表了拆分出来的关键字集合(如上述的个,十,百位)中可能取值的个数。比如个,十,百,他们的取值可能都是0 ~ 9,所以取值个数为10,那么就把r定为十。
    Second,定义r个队列,并将他们置空。
    Then,进行分配和收集。
    啥叫分配和收集?
    我们有多少个拆分出来的关键字,就需要多少回分配和收集。
    一次分配的过程如下:一一扫描线性表中的结点,发现了关键字与队列对应的取值相等,那么就将该结点添加进这个队列。比如我们首先开始个位数关键字的分配,总共r(r = 10)个队列分别对应0 ~ 9,我们扫描链表中的结点的时候,发现个位数等于5那么就加到对应5的队列中去。
    一次收集的过程如下:将刚刚被分配完毕的队列依次首尾相连获得一个新的队列。
    一次收集和分配也被称为一趟排序的过程。
    在这里插入图片描述
    基数排序刚刚接触可能觉得有点奇怪,但是把上面的图看懂了以后应该就能大致理解,基数排序到底在干什么了。
    基数排序代码实现一般不会考察,我们只需要它的过程就可以。
    空间复杂度:O®,r个队列
    时间复杂度:d躺分配收集O(d),一次分配是O(n),一次收集是O®
    所以时间复杂度是O(d(n + r))
    稳定性:因为队列先入先出的特点,是稳定的排序算法。

    总结

    八大内部排序算法内容还是不少的,在记忆时,应着重于原理时间复杂度空间复杂度算法稳定性适用场景几个方面进行记忆。
    下面进行一个总结。

    冒泡排序算法总结

    原理:两两比较相邻的记录,向左或者向右冒泡,一趟排序可以确定一个元素的最终位置。
    时间效率:冒泡排序的时间效率是和待排序列的初始状态有关的。
    最好的情况下,初始列表有序:
    比较次数为n - 1次,移动次数为0次,时间复杂度为O(n)
    最坏情况下,初始列表无序:
    比较次数:第i趟排序需要比较n - i次 i从1 到n - 1求和为 n(n - 1) / 2
    移动次数:3n(n - 1)/2 移动次数是比较次数的三倍
    时间复杂度:O(n²)
    空间效率:O(1)
    算法稳定性:因为每次比较,如果两个元素相等,那么就不会发生交换,所以是稳定的排序算法。
    适用场景:冒泡排序是和待排序列的初始条件有关的,所以当待排序列几乎是有序的,那么冒泡排序的时间是线性的,可以使用。

    快速排序算法总结

    原理:快排的基本思想是基于分治的,从待排序列中挑出一个基准,将整个序列分成小于基准的,大于等于基准的两部分。一趟排序可以确定一个元素的最终位置。快速排序对于划分数组来说有奇效,比如我要找一个数组中第k大的数字,相当于就把数组划分成了比k小的部分和比k大的部分。再比如把数组较小的一半和较大的一半分开等等。
    时间效率:快排的时间效率是和partition分不开的,究其根本就是pivot的选定问题,如果partition能使得划分出的两部分均一,那么时间效率就会较高。
    最坏情况:pivot选的太大或者太小此时的时间复杂度为O(n²)
    最好情况:pivot选的正中间,此时的时间复杂度为O(n * log n)
    空间效率:快排的空间效率同样和pivot的选定有关
    最坏情况:调用栈的深度为O(n)
    最好情况:调用栈的深度为O(n * log n)
    算法稳定性:不稳定
    适用场景:当n较大,关键字趋近于随机分布,不要求稳定性的时候,那么用的一定是快排,因为快排被认为是效率最高的内部排序算法。

    插入排序算法总结

    原理:子序列已经排好,插入排序就是把无序序列中的元素,往前面排好的子序列里面插入。直接插入与折半插入的区别在于定位插入的位置,也就是比较元素的次数不同。希尔排序规定了步长,在对应步长的子序列中应用直接插入排序,并一步步缩短步长直至为1.

    时间效率:关于直接插入排序的比较次数和移动次数我是存在疑虑的,在严蔚敏的教材里把哨兵的比较也算了进去,至于什么是哨兵其实我也不太理解。但是我认为直接插入排序的时间效率是和冒泡的情况类似的。
    最好的情况下,初始列表有序:
    比较次数为n - 1次,移动次数为0次,时间复杂度为O(n)
    最坏情况下,初始列表无序:
    比较次数:第i趟排序需要比较n - i次 i从1 到n - 1求和为 n(n - 1) / 2
    移动次数:3n(n - 1)/2 移动次数是比较次数的三倍
    时间复杂度:O(n²)
    空间复杂度:O(1)

    算法的稳定性:稳定的

    适用场景:n较小或者待排数列基本有序,仔细分析算法的化,直接插入排序肯定是比冒泡排序好的,所以遇到两个都可以用的时候,要选直接排序。此外直接排序可以用链表结构实现,当所含记录数据量庞大,移动记录耗费大量时间,采用链表作为存储结构。

    简单选择排序算法总结

    原理:第i趟在后面n - i + 1个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到n - 1趟完成。简单选择排序同样也是每一趟排序就能确定一个元素的最终位置。

    时间效率:比较次数是一定的,与数据原始状态无关,为n(n - 1)/2.元素移动次数不定。最好情况下,待排序列有序,无序移动。
    最坏情况下,待排序列逆序,移动次数3* (n - 1),所以时间复杂度始终是O(n²)

    稳定性:因为每次找到最小值以后需要交换位置,可能会把数字相同在前的换到后面去。,所以是不稳定的。

    适用场景:简单排序移动元素的次数少,所以当n比较小的时候,元素的信息量较大,那么就可以使用简单选择排序。

    堆排序算法总结

    原理:堆排序是把数组按照下标的关系视作了一棵完全二叉树,我们排序所用到的堆是大根堆。使用堆排序对数组进行排序的时候首先将数组转化成大根堆,从第一个非叶子节点开始筛选,直至变成大根堆。获得大根堆以后,每次将堆顶的元素与最后的元素交换位置,重新的构造大根堆。堆排序每次可以确定最后一位元素的位置。

    时间效率:建堆的时间为O(n),添加新元素,弹出堆顶,调整大根堆的时间都是O(logn),最好,最坏和平均情况都是O(N * logN)

    空间效率:O(1)

    稳定性:不稳定的排序

    适用场景:通常,取一大堆数据中k个最大或最小元素时都优先采用堆排序

    归并排序算法总结

    原理:我们说的归并排序一般都是2路归并排序,归并排序一般是将两个有序表归并为一个有序表,所有用归并排序的思想可以解决两个两个有序表合并的问题。归并排序每个数都可以找到比自己大的数,所以还可以用来解决小和逆序对问题。

    时间效率:每次归并的时间复杂度是O(n),与原始状态无关,需要进行【logn】次归并(向上取整),所以时间复杂度就是O(n * logN)

    空间效率:O(N)

    稳定性:当左右两边元素相等的时候,右边的先入辅助表,所以不改变元素之间的相对位置,是稳定的。

    适用场景:当n较大的时候,需要使用O(n * logN)排序算法,如果还要求稳定性,这个时候可以使用归并算法。

    基数算法总结

    基数算法上面的总结的很到位了,这里做个补充,当n很大的时候,关键字位数较烧且可以分解的时候,我们可以采用基数排序。如按照出生日期的月,日来给中国人排序。

    展开全文
  • 常用的内部排序的源代码,不需要修改,可直接使用,多加支持,谢谢
  • 目录各种内部排序方法的比较地址排序(基于关键字比较的)内部排序在最坏情况下的最快速度 各种内部排序方法的比较 nnn较小/基本有序:可采用简单排序方法(直接插入、简单选择、冒泡排序) nnn较大:可采用快排、...

    作为数据结构的课程笔记,以便查阅。如有出错的地方,还请多多指正!

    各种内部排序方法的比较

    在这里插入图片描述

    • n n n 较小/基本有序:可采用简单排序方法 (直接插入、简单选择、冒泡排序)
    • n n n 较大:可采用快排、堆排。若要求稳定性,则采用归并排序
    • 从平均时间性能上看,快排最佳,但快排在最坏情况下的时间性能不如堆排和归并排序
    • n n n 较大时,归并排序比堆排序用时少,但所需的辅助存储量多
    • 基数排序适用于 n n n 很大,关键字个数较少的序列。若关键字个数较多,而序列中大多数记录的最高为关键字均不同,则可先按最高为关键字将序列分为若干子序列,然后进行直接插入排序

    地址排序

    在顺序存储结构上进行排序需要移动大量记录。当记录很大时,时间耗费很多,此时可用静态链表。

    但是快排、堆排等无法实现表排序,这种情况下可以使用地址排序,即另设一个地址向量指示相应记录,在排序过程中不移动记录而移动地址向量中相应分量的内容。在排序结束后,地址向量中的值指示排序后的记录次序。
    例如:对记录r[8]附设向量adr(1:8),开始排序时令adr[i]=i。排序中的r[i]=r[j]操作均以adr[i]=adr[j]代替。排序结束后r[adr[8]]即为关键字最大的记录,r[adr[1]]即为关键字最小的记录
    r(1:8)49 65 38 27 97 13 76 49*
    adr(1:8)6 4 3 1 8 2 7 5

    最后可根据需要按adr的值来重排记录的物理位置,重排算法如下:

    • i=1起,检查每个分量位置上的记录是否在正确位置
    • adr[i]=i,则位置正确,不需要调整
    • adr[i]=k ≠ \neq =i,则说明r[i]上应该存放r[k]。可以先暂存r[i],然后将r[k]移至r[i]。然后继续检查位置k,若adr[k] ≠ \neq =k,则将r[adr[k]]移至r[k]。直至找到j=adr[adr[...adr[k]...]]使adr[j]=i,将暂存记录移至r[j]
    //根据地址数组对实际物理地址进行重排
    void Rearrange(SqList_t* list, int adr[])
    {
    	for (int i = 1; i <= list->len; ++i)
    	{
    		if (adr[i] != i)
    		{
    			int j, k;
    			list->rec[0] = list->rec[i]; //暂存记录
    			for (j = i; adr[j] != i; j = k)//找到位置i应该存放的记录
    			{
    				k = adr[j]; //位置j处应该存放记录k
    				list->rec[j] = list->rec[k];
    				adr[j] = j; //改变地址数组
    			}
    			//位置i上的元素实际应该存放在位置j
    			list->rec[j] = list->rec[0];
    			adr[j] = j;
    		}
    	}
    }
    

    (基于关键字比较的) 内部排序在最坏情况下的最快速度

    • 考虑一棵比较 3 个关键字 K 1 K_1 K1, K 2 K_2 K2, K 3 K_3 K3判定树,它表示了直接插入排序的过程。每个非终端结点表示两个关键字的一次比较,叶结点表示 6 种排序结果。而每一个初始序列经排序达到有序所需的比较次数,则为从根到对应叶结点的路径长。判定树深度为 4,则在最坏情况下,对 3 个记录排序至少要 3 次比较
      在这里插入图片描述
    • 推广至 n n n 个记录的情况:含 n n n 个记录的序列可能出现 n ! n! n! 个初始状态,对应的判定树有 n ! n! n! 个叶结点。又因为高度为 h h h 的二叉树,叶结点个数 ≤ 2 h − 1 \leq2^{h-1} 2h1。因此若有 x x x 个叶结点,则 h ≥ ⌈ l o g 2 x ⌉ + 1 h\geq\lceil log_2x \rceil+1 hlog2x+1。也就是说, n ! n! n! 个叶结点的判定树必定存在一条 ⌈ l o g 2 n ! ⌉ \lceil log_2n! \rceil log2n! 的路径。根据斯特林公式, ⌈ l o g 2 n ! ⌉ = O ( n l o g n ) \lceil log_2n! \rceil=O(nlogn) log2n!=O(nlogn)
      • 因此,基于关键字比较的内部排序在最坏情况下的最好时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
    展开全文
  • 各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶,或大概执行时间。试通过随机的数据比较各算法的关键字比较次数和关键字移动次数,以取得直观感受。
  • 内部排序算法的比较

    2012-07-04 22:46:33
    对以下六种常用的内部排序算法进行比较:希尔排序、直接选择排序、快速排序、直接插入排序、堆排序、冒泡排序。
  • 内部排序算法比较 【问题描述】 在教科书中,各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶,或大概执行时间。试通过随机数据比较各算法的关键字比较次数和关键字移动次数,以取得直观感受。 ...
  • 1、本演示程序对以下6种常用的内部排序算法进行实测比较:起泡排序、直接插入排序、简单选择排序、快速排序、希尔排序、堆排序。 2、待排序表的表的元素的关键字为整数,表长不小于100;其中的数据要用伪随机数产生...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 463,788
精华内容 185,515
关键字:

内部排序