左神算法笔记-3 详解桶排序以及排序内容大总结

听了好久,晚上听可太困了
离散数学课上听了一半,算法果然很顶!

讲了快排的空间复杂度,详解堆排序,桶排序等
2021年9月28日
19点59分

1、快速排序的空间复杂度(0)
空间复杂度为O(logn)

分为最坏和最好情况:
Ⅰ.最坏情况
当每次划分值都是最大的数据,此时每次都要保存划分的位置,要调用N层,每层申请一个额外空间来存储划分值使得需要的额外空间复杂度就是O(N)即每个数字都要存起来 直到最后一层开始往回返。
Ⅱ.最好情况
当每次的划分值较为合适(接近中点值)时,就能出现较好的情况,此时空间复杂度是O(logN),因为每次先处理左侧的半截数组,申请一个空间,递归结束返回时就能释放左侧申请的空间,供右侧使用,所以总的空间复杂度就是O(logN)

提问:不用递归行不行,不递归就不需要额外申请空间来存储划分值了。
回答:不行,就算不递归同样需要自行申请变量空间而且code很繁琐不如直接递归来的简洁明了。(原因解释忘记了,记得复习)

所有情况按概率累加求数学期望得到最终空间复杂度是O(logN)

2、 堆结构(08:38)⭐比堆排序还重要
堆分为大根堆和小根堆,大根堆就是对于每个结点,以他为根节点的树上的所有值都要小于根。小根堆则相反。

堆说白了就是一颗完全二叉树结构(这个要是都不知道就快学习吧。)
对于结点 i 他的左孩子是 2 * i + 1,他的右孩子是 2 * i +2他的父亲结点是 (i - 1) / 2 ,注意 0 号结点的父亲结点就是自己也适用这个计算方法因为 (-1 / 2 == 0)。

堆结构的两个重要操作(18:50)
heapify在3处,为了让内容更连贯我选择把他放到这里。

此处两个流程都是按照大根堆来写的,小根堆是一样的,不再赘述:

heapInsert流程:
①插入的结点 i ,比较i结点与父亲结点的大小;
②如果i比他的父亲大,交换i和i的父亲结点,然后继续比较;
③如果i比他的父亲小或者相等,就此停止。


heapify流程:
①对于任意一个结点,不妨设为i结点,比较i结点的两个孩子结点;
②取较大的孩子结点与i结点比较大小,如果i没比过自己的孩子,那么就交换i和更大的孩子的位置,维护大顶堆的结构正确性;
③如果i结点比自己的孩子都大(就是调好了)那没事了。

code(25:25)

3、 堆的其他操作(28:22)

(1)去顶操作(30:25)

描述:
如果对于一个已经维护好的堆结构,有需求是将堆顶返回并从堆中删除,如何操作?

解决:
先申请变量将堆顶存储,然后用堆中最后一个结点覆盖根节点,同时heapSize- -;
紧接着调用heapify();堆维护好就可。

code(37:13)

4、 堆排序(50:50)

流程:
①按照heapInsert()将数组按照堆结构排好;
②堆顶和堆结构中最后一个结点交换位置,同时heapSize--,此时堆结构外的第一个数组元素就是最大的(小根就是最小的);
③处理完的堆重新根据heapify()维护好并循环;
④循环结束,数组就排好序了。

code(01:02:28)

5、 堆排序的优化(01:09:09)
在①中按照heapInsert()将数组按照堆结构排好;
直接可以优化成整个数组直接一起处理,heapify(),不用一个一个Insert;
计算 (利用了错位相减)时间复杂度O(N)比heapInsert()建堆要快但是对时间复杂度的影响不大,因为②③决定时间复杂度还是O(N * logN)
6、 堆扩展题目(01:19:47)
图片来自左神课件对这个题目的理解与分析:
k = 6的话,我们就把前七个数组元素放到小根堆里面去,此时堆顶一定就是整个数组里最小的数,也就是左神课上说的一定在0位置上(听的时候理解有问题,想着小根堆本来堆顶就是最小的啊啥叫跟k有关),因为k= 6所以7号位置上的元素(第八个数)一定不会是最小的,然后就能确定出第一个正确位置上的元素,按顺序继续始终保持heapSize不变,往右遍历,遍历结束后得到的数组就是正确顺序了。

7、比较器(01:37:03)
比较器 在 c++中叫做运算符重载,实质上就是告诉程序该如何比较两个不同寻常的数组元素比如自定义的类的数组。

返回值<0时,stu_1在前;
返回值>0时,stu_2在前;
返回值=0时,都一样。

class student
{
	private name;
	private id;
	private age;
}

student[] student = new student();
student.sort();//此时sort方法无法运行,需要我们自定义一个比较器

student.sort(com());

int com(student stu_1,student stu_2)
{
	return stu_1.id - stu_2.id;
}

比较器的作用就是帮助我们实现一些复杂类型的比较,可以节约很多代码。

---------------------------分割线--------------------------------------------------
之前学到的都是比较排序,下面是两种非比较排序;
8、 记数排序(01:51:30)
应用场景,一个数组存放着员工年龄,对这个数组进行排序。

分析:公司员工年龄的范围一般都在20-70之间,只需开辟一个长度为51的数组,以下标为年龄标志,下标对应元素为处于该年龄人数的标志,遍历数组,重写原数组即可。

优点:O(N)
缺点:实用性差,适用范围窄

9、基数排序(01:58:20)
N进制数,开辟N个队列,从最低位数字开始,按照数字入队,个位数为1就进1队,0就进0队,全部入队后按照从小到大把队列里的数据依次出队回到数组,在按照次低位入队出队,最后按照最高位(不足的补0)执行相同的操作。

为什么要从最低位开始呢,因为最后一轮的决定权重最高

再好好理解一下
code(02:06:20)
代码很新奇,自己要实现

加油!

亡羊补牢,为时不晚;时不我待,舍我其谁!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值