排序技术总结(数据结构C++,大二下写,初学者)

这段时间,我学到了查找技术和排序技术,这两种操作在许多程序中应用,例如大一写的核酸作业就用到了这两种技术。这两章主要是学习查找和排序的各种算法,拓宽我的思路,并且非常的实用,也非常的有趣。关于查找技术和排序技术的知识,我总结为三方面:课本上学到的知识、上机实现课本上的例子的过程所学到的知识和做课后题学到的知识和技巧。

排序技术:

在排序技术中,我首先学到的新概念就是排序算法的稳定性,所谓稳定性,就是排序前和排序后相同元素的相对次序保持不变时,则说明算法稳定,否则不稳定。例如,排序前的序列为:2(1,表示第一个2),9,7,8,1,4,2(2),3,若排序后的序列为:1,2(1),2(2),3,4,7,8,9时该排序算法稳定,若排序后的序列为:1,2(2),2(1),3,4,7,8,9时该排序算法不稳定。然后我学到了5中排序算法,分别为插入排序、交换排序、选择排序、归并排序以及分配排序,最后一种排序属于不基于比较的内排序,而其余四种属于基于比较的内排序。在学会每一种排序算法时还应该知道该算法的时间复杂度、空间复杂度以及稳定性如何。我对每一种算法的理解分析、模拟过程以及代码实现如下:

插入排序

  1. 插入排序:在插入排序中我学到了直接插入排序和希尔排序两种算法。

直接插入排序

基本思想

顾名思义,通过插入进行排序,依次将待排序列中插入到已经排好序的序列当中,插入时不是随便插,而是在已经有序的序列中找到合适的位置,即所插元素值大于该位置后的元素,小于该位置前的元素,所以插完之后依旧是有序序列,等到所有元素插完后就得到想要的有序序列。

模拟排序过程

模拟排序过程:待排序列 :5,28,9,67,53,42,18,71,3

初始:5

第一趟排序结果:【5,28,】9,67,53,42,18,71,3

第二趟排序结果:【5,9,28,】67,53,42,18,71,3

第三趟排序结果:【5,9,28,67,】53,42,18,71,3

第四趟排序结果:【5,9,28,53,67,】42,18,71,3

第五趟排序结果:【5,9,28,42,53,67,】18,71,3

第六趟排序结果:【5,9,18,28,42,53,67,】71,3

第七趟排序结果:【5,9,18,28,42,53,67,71,】3

第八趟排序结果:【3,5,9,18,28,42,53,67,71】

性能分析

性能分析:时间复杂度:O(n^{2}),空间复杂度:O(1),稳定。

代码实现细节:通过数组来存储元素,所以在插入时,为了方便移动元素,算法从后往前遍历每当要插元素值比遍历到的位置的值小时,移动元素,让该位置的后一个位置值等于该位置的值,直到遍历到的位置值小时,跳出for循环,且由于跳出循环前执行过j—操作,所以j+1才是要插位置的下标。并且注意循环是从i=1即第二个元素开始遍历的,该插入操作通过一个数组就可以完成,非常的巧妙。代码实现如下:

希尔排序

基本思想

希尔排序是在直接插入排序的基础上改进的,首先将待排序列划分为多个子序列,在每个子序列中进行插入排序,然后缩短分组间隔即增大子序列长度,继续对每个子序列进行插入排序,直到对全体序列进行一次插入排序。

模拟排序过程

模拟排序过程:待排序列 :35,21,99,15,22,43,78,1,3

d=4分隔子序列:【35,22,3】,【21,43】,【99,78】,【15,1】

第一趟排序结果:3,21,78,1,22,43,99,15,35

d=2分隔子序列:【3,78,22,99,35】,【21,1,43,15】

第二趟排序结果:3,1,22,15,35,21,78,43,99

d=1分隔子序列:【3,1,22,15,35,21,78,43,99】

第三趟排序结果:1,3,15,21,22,35,43,78,99

性能分析

性能分析:时间复杂度:O(n^{1.3}),空间复杂度:O(1),不稳定。

代码实现细节

代码实现细节:通过设置分组间隔d(初始为序列长度的一半)来划分不同的子序列,在待排序列中只要间隔为d就为一组,对每组进行一次插入排序,然后依次缩短d,每次取原来的一半,每确定一次分组间隔就对新划分的各组进行一次插入排序,直到d为1对全体进行插入间隔。具体实现也很简单,对直接插入排序中间隔1改为间隔d,在外面加上一个依次缩短间隔d的for循环即可。具体代码如下:、

交换排序

交换排序:在交换排序中我学到了起泡排序和快速排序两种算法。

起泡排序

基本思想

两两比较序列中相邻元素,若为反序则交换,直到没有反序的元素为止。每进行一趟两两排序,至少会确定一个最大的,也可能会确定多个,所以经过多次比较交换,最终会生成有序的序列。

模拟排序过程

模拟排序过程:待排序列 :45,27,9,19,23,4,8,61,3

 第一趟排序结果:27,9,19,23,4,8,45,3,61

第二趟排序结果:9,19,23,4,8,27,3,45,61

第三趟排序结果:9,19,4,8,23,3,27,45,61

第四趟排序结果:9,4,8,19,3,23,27,45,61

第五趟排序结果:4,8,9,3,19,23,27,45,61

第六趟排序结果:4,8,3,9,19,23,27,45,61

第七趟排序结果:4,3,8,9,19,23,27,45,61

第八趟排序结果:3,4,8,9,19,23,27,45,61

性能分析

性能分析:时间复杂度:O(n^{2}),空间复杂度:O(1),稳定。

代码实现细节

代码实现细节:从上述基本思想中可知,问题的关键在于确定每趟两两排序后有几个最大元素被确定,知道这一点,则下一次两个排序就不用考虑哪些已经确定的元素。我通过记录每一次交换时两个元素中小的下标,则经过一趟两两排序后,最后被记录的下标后面的元素都是已经确定的,理清楚这些代码实现就手到擒来了,代码实现如下:

快速排序

基本思想

选定一个轴值,同过一趟排序将待排序列分成左右两部分,左边所有元素的值都小于轴值,而右边所有元素的值都大于轴值,即该位置就是该轴值在最终有序序列中的位置,相当于固定了一个元素,之后对左右两部分依次执行排序,直到整个序列为有序序列。可以发现一次排序之后的特点和二叉排序树的特点很像,所以可以用二叉排序树其排序的过程就是二叉排序树的建立过程。

模拟排序过程

模拟一次划分过程: 待排序列,轴值为75:【75】,27,9,4,23,58,83,66,2

右侧扫描,75于2交换:2,27,9,4,23,58,83,66,【75】

左侧扫描,75于83交换:2,27,9,4,23,58,【75】,66,83

右侧扫描,75于66交换:2,27,9,4,23,58,66,【75】,83

模拟快速排序过程:待排序列 :75,27,9,4,23,58,83,66,2

第一趟排序结果:2,27,9,4,23,58,66,【75】,【83】

第二趟排序结果:【2】,27,9,4,23,58,66,【75】,【83】

第三趟排序结果:【2】,23,9,4,【27】,58,66,【75】,【83】

第四趟排序结果:【2】,4,9,【23】,【27】,【58】,【66】,【75】,【83】

第五趟排序结果:【2】,【4】,【9】,【23】,【27】,【58】,【66】,【75】,【83】

性能分析

性能分析:时间复杂度:O(n\log_2 n),不稳定。

代码实现细节

代码实现细节:在实现该算法中,我编写了三个函数,第一个是进行一次快速排序的函数确定一个元素,第二个是递归的调用第一个函数,让整个序列有序,在第三个函数中调用第二个递归函数,然后调用输出函数,至此,快速排序的算法彻底完成。在第一个函数一次划分的实现过程中,利用双指针,一个指针指向轴值,另一个指针从一侧开始遍历,若从右侧开始遍历,则所指元素值大于等于轴值时左移指针,小于轴值时交换两指针内容。若从左侧开始遍历,则所指元素值小于等于轴值时右移指针, 大于轴值时交换两指针内容。 注意注意!!!高明的地方来了,双指针不必每次交换都将轴值于指针所指值各自赋值,只需保留轴值将轴值赋给扫描指针所指值,并让扫描指针作轴指针,轴指针作扫描指针。实现代码如下:

选择排序

  1. 选择排序:在选择排序中我学到了简单选择排序和堆排序两种算法。

简单选择排序

基本思想

每次确定待排序列中最小的一个,将其归为有序序列中,重复上述操作,执行序列长度-1次,就可以得到一个有序序列。如何找到最小的呢,只需遍历无序区的序列,保留每次比较中小的元素的下标,最终得到的下标对应的元素值就是待排序列中最小的。

模拟排序过程

模拟排序过程:待排序列 :45,27,9,19,23,4,8,61,3

第一趟排序结果:【3】,27,9,19,23,4,8,61,45

第二趟排序结果:【3,4】,9,19,23,27,8,61,45

第三趟排序结果:【3,4,8】,19,23,27,9,61,45

第四趟排序结果:【3,4,8,9】,23,27,19,61,45

第五趟排序结果:【3,4,8,9,19】,27,23,61,45

第六趟排序结果:【3,4,8,9,19,23】,27,61,45

第七趟排序结果:【3,4,8,9,19,23,27】,61,45

第八趟排序结果:【3,4,8,9,19,23,27,45】,61

性能分析

性能分析:时间复杂度:O(n\log_2 n),空间复杂度:O(1),不稳定。

代码实现如下:

堆排序

基本思想

首先引入堆的概念,一个完全二叉树,并且满足每个结点的值都小于或等于其左右孩子结点的值称为小根堆,大于等于其左右孩子结点的值称为大根堆。以大根堆为例,通过将待排序列构造成大根堆,我们很容易找到其最大结点的值,然后将该值移入有序序列,将剩下的待排序列重复上述构造移入的过程,最终整个序列就为有序序列。

模拟建堆过程

模拟建堆过程:初始序列:5,28,9,67,53,42,18,71,3

【注意,将初始序列看成完全二叉树的层次遍历,在演草纸上画出结构图就很好做了。】

               调整67:5,28,9,71,53,42,18,67,3

                调整9:5,28,42,71,53,9,18,67,3

               调整28:5,71,42,28,53,9,18,67,3

           继续调整28:5,71,42,67,53,9,18,28,3

               调整28:5,71,42,67,53,9,18,28,3

                调整5:71,5,42,67,53,9,18,28,3

            继续调整5:71,67,42,5,53,9,18,28,3

            继续调整5:71,67,42,28,53,9,18,5,3

模拟堆排序过程

模拟堆排序过程:待排序列 :5,28,9,67,53,42,18,71,3

初始建堆结果:71,67,42,28,53,9,18,5,3

71与3交换:3,67,42,28,53,9,18,5,【71】

调整3,重建堆:67,53,42,28,3,9,18,5,【71】

67与5交换:5,53,42,28,3,9,18,【67,71】

调整5,重建堆:53,28,42,5,3,9,18,【67,71】

53与18交换:18,28,42,5,3,9,【53,67,71】

调整18,重建堆:42,28,18,5,3,9,【53,67,71】

42与9交换:9,28,18,5,3,【42,53,67,71】

调整9,重建堆:28,9,18,5,3,【42,53,67,71】

28与3交换:3,9,18,5,【28,42,53,67,71】

调整3,重建堆:18,9,3,5,【28,42,53,67,71】

18与5交换:5,9,3,【18,28,42,53,67,71】

调整5,重建堆:9,5,3,【18,28,42,53,67,71】

9与3交换:3,5,【9,18,28,42,53,67,71】

调整3,重建堆:5,3,【9,18,28,42,53,67,71】

5与3交换:3,【5,9,18,28,42,53,67,71】

完成排序:【3,5,9,18,28,42,53,67,71】

性能分析

性能分析:时间复杂度:O(n\log_2 n),空间复杂度:O(1),不稳定。

代码实现细节

代码实现细节:值得注意的是!!!堆排序并不是真的建立了树结构,而是通过完全二叉树孩子结点和双亲结点间的关系,在数组中进行操作的,实质是运用树的逻辑思想。通过树形结构来处理线性排序问题的思想非常值得我学习,拓宽了我的思路。在实现堆排序的过程中,我编写了两个函数,第一个是进行对调整的函数,为了方便数组实现,我规定完全二叉树的编号从0开始,则编号为i的左孩子是2*i+1,右孩子是2*i+2,为了让根结点为最大,我先比较选出左右孩子中最大的那个,记录其编号,然后再和根结点比较,大于根结点则交换。第二个是进行堆排序的函数,由于初始时将无序序列建成堆,第一个函数操作一次只能让局部成为堆,所以我在第二个函数多次调用,从最后一个分支结点到根结点进行调整,然后得到一个堆,让待排序列中第一个元素与最后一个交换,待排序列个数减一,在已有堆(只有开头不是堆其余分支都是堆)的基础上调整,只需调用一次调整函数即可。代码实现如下:

归并排序

基本思想

  1. 归并排序:基本思想:归并排序采用的是分治的思想,将若干个有序序列逐步归并,最终归并为一个有序序列。在归并排序中,我主要学到的就是二路归并排序,即首先将待排序列每个元素各自归为一个有序序列,然后两两合并,注意,合并时要有序的合并,此时要用到双指针思想进行合并,该思想我早在线性表中就已经学到。最终将所有的有序序列合并为一个有序序列就结束。

模拟排序过程

模拟排序过程:待排序列 :45,27,9,19,23,4,8,61,3

初始化:【45】,【27】,【9】,【19】,【23】,【4】,【8】,【61】,【3】

第一趟排序结果:【27,45】,【9,19】,【4,23】,【8,61】,【3】

第二趟排序结果:【9,19,27,45】,【4,8,23,61】,【3】

第三趟排序结果:【4,8,9,19,23,27,45,61】,【3】

第四趟排序结果:【3,4,8,9,19,23,27,45,61】

性能分析

性能分析:时间复杂度:O(n\log_2 n),空间复杂度:O(n),稳定。

代码实现细节

代码实现细节:关于合并两个有序序列这个函数,课本上的方法是使用一个数组,然后再函数中动态的申请一个数组,通过指针进行操作,最后将指针所指内容传给数组,PPT中的方法是设置两个数组为参数进行操作。我选择的是ppt中的方法,因为在最终的排序函数中两个数组交替使用,比较巧妙。还有就是要注意排序算法中循环的范围,一旦范围出错那结果就会千差万别了,切记要理清!在两两合并时,有三种情况要考虑,第一,两个序列长度相等,第二,一个序列为h,另一个小于h,第三,最后只剩下一个序列(此时不用进行合并,因为不管有没有剩余,两两合并后最终都会合并为一个序列)。代码实现如下:

 

分配排序

  1. 分配排序:在分配排序中我学到了桶排序和基数排序两种算法。分配排序是不基于比较的排序,这种思想非常的新颖,也非常的有趣。

桶排序

基本思想

将数据分配到不同的桶中,然后再将桶收集起来,所谓桶就是按大小有序的存储空间,比如连续的数组,其下标就是有序的,我们将待排序列的元素值作数组下标存储起来,数组中的结构为链表类型,相同元素值得在同一个链表中,且为了保证算法的稳定性,链表的插入使用尾插法进行。等到待排序列所有元素都分配完毕后,依次将数组中有元素的链表串起来,最终返回一个指针,遍历就能得到有序序列。还有一点要切记!!!若桶得个数为m个,则待排记录得值就必须在0到m-1之间。

实现代码如下:

基数排序(稳定)

基本思想

基本思想:排序时从最低位开始,每一位进行一次排序,具有相同位数的元素组成的序列进行排序时,经过从低位到高位进行排序后,最终最高位排序的结果,最高位相同的元素在同一个桶中,并且在同一个桶中的元素是有序排列的,然后运用上面桶排序的收集函数,输出即可。

模拟排序过程

模拟排序过程:待排序列 :543,234,453,678,978,654,343,129,121,565

第一趟排序结果:121,543,453,343,234,654,565,678,978,129

第二趟排序结果:121,129,234,543,343,453,654,565,678,978

第三趟排序结果:121,129,234,343,453,543,565,654,678,978

实现代码如下:

排序技术课后题:

1.双向起泡排序

在每一轮循环中先从左向右进行冒泡,将待排序列中最大的元素移到最右边,然后从右向左进行冒泡排序,将待排序列中最小的元素移到左边,同时更新左边界和右边界。循环结束的条件是exchange1变量为0,表示没有发生交换。

2.己知记录序列(k1,k2,k3,k4,k5)是堆,要求将记录序列(k1,k2,k3,k4,k5,k6)调整为堆

思想:先将新增的元素插入到堆尾,然后使用堆排序中构造初始堆的方法,从最后一个分支结点至根结点调整。

3.设待排序的记录序列用单链表作存储结构,写出直接插入排序算法。

147. 对链表进行插入排序 - 力扣(LeetCode)

思想:首先我们定义一个空链表dummy,然后遍历链表,在每次遍历时,都定义一个指针,指向dummy的表头(即每次都要从dummy头开始找),当指针所指的下一个元素的元素值小于该次遍历时head指向的元素值y时,指针移动,直到找到刚好比y大的元素x,然后将y插入到x前面即可。遍历完成之后,dummy就是排序完的链表。

4.将有序序列A[n]和有序序列B[m]归并为一个有序序列并存放在C[m十n]中。思想:双指针

5.写出快速排序的非递归算法。

我在快速排序中写的就是非递归方法,不再赘述。

6.折半插入排序。

插入排序是从无序区插到有序区,有序正符合二分查找的条件,所以用二分查找来确定插入的位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值