数据结构-排序

这是我最后一次整理排序,我说的。

排序算法千千万,考试只考那几种。
本块知识点的整理由于涉及到一整个关于排序的章节所以我们按照王道书上进行知识点的顺序过一遍。
在整理排序算法前我们普及一下基本常识

算法的稳定性

什么是算法的稳定性,通俗一点说就是,我俩一样,我本来在你前面,排序过后,我还在你前面。那你如果排完序到我前面,那就是不稳定的。

算法分类

插入排序

插入排序主要分为三类:直接插入排序、折半插入排序、希尔排序

直接插入排序

算法思想:每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成
例如:(为了区别第一个49与第二个49 第一个后加个^)
第一轮 49^ 38 65 97 76 13 27 49
第二轮 38 49^ 65 97 76 13 27 49(38 插入到已排好序列)
第三轮 38 49^ 65 97 76 13 27 49
第四轮 38 49^ 65 97 76 13 27 49
第五轮 38 49^ 65 76 97 13 27 49
第六轮 13 38 49^ 65 76 97 27 49
第七轮 13 27 38 49^ 65 76 97 49
第八轮 13 27 38 49^ 49 65 76 97
我们通过模拟可以知道,直接插入排序是稳定的

当然我们可以根据这个算法思想,对一些单链表进行操作。这个代码如果时间允许的话,我也会尝试着用数据结构写一写,这里先挖个坑,我们主要是对这几种算法进行一个区分。

折半插入排序

折半插入排序比起直接插入排序最直观的一个特点就是,我不是从头或尾进行比较了,每次从已经排好的序列的中点处开始查找,由于已经是排好的序列,所以小于就比较左半,大于就比较右半,这样来的要比直接插入排序比较次数小
通常什么情况用到折半排序呢:对于一些数据量不是很大的排序表,表现就会非常出色。
当然,折半插入排序也是稳定的

希尔排序

(高亮一下,因为我在写题的时候,完全忘记了希尔排序是什么,所以关于希尔排序要仔细地进行梳理)
算法思想:将待排序表分割成若干形如L[i,i+d,i+2d…i+kd]的特殊子表,再在个子表中分别进行直接插入排序,然后缩小增量d,不断重复操作。
在取增量的时候我们怎么取呢?
如果题目没有给,那么一般常用的增量就是d1=n/2(n为个数)
上题:
解释希尔排序的算法思想。对以下的数据序列,给出希尔排序过程的示意图。
(46,8,36,50,6,24,18,78,12,10)
分析:首先十个数那么
D1=10/2=5
那么我们取(1,6)(2,7)(3,8)(4,9)(5,10)
在这里插入图片描述第二轮我们将缩小增量,取5/2上整=3,再进行直接插入排序
在这里插入图片描述真的是舒服我今天升级了!!!我把我的笔带上了写起来真的方便
第三轮我们再缩小增量,此时由于可以直接取1进行直接插入排序
最终直接可以得到结果:
6 8 10 12 18 24 36 46 50 78
PS:由于我们会划分不同的子表,所以很可能相同的元素会被划分到不同子表中,所以这是一种不稳定算法。

交换排序

交换排序也非常的重要,分别是冒泡算法和快排,一定要掌握

冒泡排序

关于冒泡排序没啥好说的,就是从头开始把最大或最小的沉底,再在前n-1个数迭代,直到整个序列排好序。
PS:在这里注意一个地方,当在排序的过程中出现一趟,任何数都没有发生交换,那就意味着,表已经有序了,我们就可以停止排序了。

快排(基于分治法)

啊这个也是我个人非常头疼的一种,每次都能理解,也每次都能忘记,高亮一下,跟前面的希尔一起。。。
算法思想:在待排序表L[1…n]中取第一个元素作为枢纽flag,通过一趟排序将待排序表划分为独立的两部分L[1…k-1]和L[k+1…n],使得L[1…k-1]中的所有元素小于flag,L[k+1…n]中所有元素大于等于flag,使flag放在L(k)上,然后递归对两个子表重复上述过程,直到每个部分都只有一个元素或者为空。
我们以 49 38 65 97 76 13 27 49*作为例进行快排
取两个指针i和j分别表示从头遍历和从尾遍历的遍历指针
首先第一步,j先动!!!j先动!!!!j先动
j找到第一个小于flag的数然后把它交换到i上(与其说是交换,我个人认为覆盖到i上似乎更为妥当)
在这里插入图片描述第二步,i动,找到一个大于flag的值再覆盖给j
在这里插入图片描述以此类推,13换到空位上,97换到13的空位上。。。
但是,到了i和j交汇的时候,我总是非常的迷,也不是不理解,但就是记忆不深刻。。。好吧就是没有完全懂,为此我看了一下王道上的代码循环条件意思是i<j时候进行循环,那意思是当i=j时已经不满足了就要跳出。
然后我们再将49插入最终的L[k]中
即得到 27 38 13 49 76 97 65 49
然后我们就得到了一个左右子序列,再分别在左右子序列中进行快排
接下来的过程由于我已经在草稿纸上演算了一遍所以我就不放上来了,如果有什么问题欢迎留言,我看到也会一起讨论。
PS:关于快排还有一点是408会考到的吧,快排是众多排序算法中最快的一种–故名快速排序(狗头保命)

选择排序

关于选择排序我倒是有一点想跟自己说的是,希望要跟直接插入排序分开,二者从基本思想上就有着本质区别
关于选择排序分为简单选择排序和堆排序,其中堆排序可谓是重中之重,那么简单选择排序呢我主要是想说一题代码题,是829中的真题,应该就是算法跟单链表结合着考的例题吧,我个人觉得虽然不是很难,但毕竟是代码,我们也应该好好的分析。

简单选择排序

算法思想:在第i趟排序中从L[i…n]中选择一个关键字最小的与L[i]交换,即每次可以确定一个元素的最终位置,经过n-1趟排序可有序

代码题:设一个带头结点的单链表 L,数据元素为整数,编写函数,通过调整该链表的结点指针,对该链表进行简单选择排序(元素值从小到大)。先给出算法思想,再写相应代码。
我们知道了简单选择排序的原理之后就可以开始构思,代码的内核实际上是通过不断地遍历每次选出一个最值存起来,不断遍历至所有结点都被遍历结束后即为有序列。
我刚开始的时候想法为直接构造一个新的单链表,然后通过不断遍历每次选出一个最大的结点摘出来,头插到空的单链表里,最后再把初始的链表头(此时已经为空)链接到我们排好序的单链表中,释放我们构造的新单链表结点。
但是后来由于我对于简单选择排序的定理抠得太死,我以为只能在一个链表中操作,并且只能每次选一个最小的接在已经排好序的链表后面,所以放弃了第一种写法改成了:设置一个遍历指针,一个f指针表示有序列的最后一个,每次把最小的尾插到有序列的最后,再把有序列向后移一位。
最后我看了答案才知道第一种也可以
我就把两种的写一下总结一下供各位参考一下:
跑不跑得通我不负责啊。。。我是理论研究者我的数据结构一般没怎么上机,还需要具体调试

void simplesort(Linklist L){
B=(Linklist)malloc(sizeof(LNode));
B->next=NULL;
LNode max=L->next,p=max->next,pre=L;
while(L!=NULL){
       while(p!=NULL){
              if(p->data>max->data){
                   max=p;
                   p=p->next;}//如果找到更大的,就把max更新
              else
                   p=p->next;//否则接着向后面遍历
                      }//每次选出一个最大值
      pre->next=max;
      pre->next=max->next;
      max->next=B->next;
      B->next=max;
      max=L->next;
      p=max->next;//头插到B中再把max和p归位
               }
L->next=B->next;
free(B);
}

那么我的第二种设置有序结点最后一位flag然后尾插法的代码如下,欢迎提出问题

void simplesort(Linklist &L){
LNode m=L->next;//min
LNode a=L;//有序列最后一个位置
LNode p=m->next;
LNode pre;
while(a->next!=NULL){
     while(p!=NULL){
     if(m->data<p->data){
          m=p;
          p=p->next;}
      else
          p=p->next;}//找出最小结点
     pre->next=m;
     pre->next=m->next;
     m->next=a->next;
     a->next=m;
     a=m;
     m=a->next;
     p=m->next        
              }
}

以上就是关于这道题我个人的一些想法。

堆排序

其实对于我个人来说堆排序没有很难,主要就是需要自己模拟一遍,我更愿意花时间深入了解一下堆排序的算法,当然如果有可能也会在稍后的帖子里专门对这些代码进行总结。
还是有几个点可以稍微说的,捋几个过程。以大根堆为例

调整大根堆
从第一个非叶结点开始
如果不符合条件与更大的孩子结点交换
注意在调整到高层非叶结点如果调整后的孩子结点也不符合还要往下调整到符合为止
大根堆排序
顶与最后一个结点交换,然后进行调整
每次能得到一个最大的数

则大根堆排序最后会得到从小到大的序列
小根堆则是从大到小

归并排序和基数排序

我提一嘴吧,这两个比较抽象的排序方式就放一起说其实也不难理解

归并排序

归并排序实际上就是将序列分成长度1的序列表,然后12归并,34归并…(两两归并);再第一次归并结束的基础上再对已经归并成长度为2的序列组再进行两两归并…直至最后归并成一个总序列

基数排序

这个就更没啥好说的了…那么奇葩却有趣的排序方法,真的不会记不住。

总结

关于这一大块知识我个人的总结就是,我在写题的时候容易把各种排序的方式弄混,还有对原理不是很有底气,有时候明明自己其实是理解的,但是总觉得自己不理解,希望这次总结可以让我真正的记住这些方法,并且熟记于心。尤其是直接插入排序、简单选择排序;基数排序和希尔排序…我真的是服了为什么这两个会弄混。

希望我有朝一日可以成为真正的大牛!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值