看了左神的很多视频,感觉一些算法最好的复现方式应该是用一张张的图来细节刻画,个人感觉这种效果会比动态图要好。
故在此先将全部的笔记附到这里,后续在一点一点把过程图复原完整(暂时没研究手绘软件)。
本质上也是在求一个累加;
// 如图最差的情况,空间复杂度为 O( N )
因为左侧申请的空间递归结束后,可以提供给右侧使用;
//左图即为——二叉树展开;
//改成迭代的话,需要我们自己压栈;
和时间复杂度一样,求每种情况出现的概率累加, 数学上的长期期望为——O( log N )
//上述三张图片,都叫做完全二叉树~~~!!!
如果跳过左侧节点,直接在右侧生出枝干,这就不是完全二叉树;
【下图就不是完全二叉树】:
//跳过了左侧节点~~~!!!
//没有从左往右依次变满~~~!!!
你把数组从 0 出发的连续一段儿,可以对应成完全二叉树;
// size=7 ; 表示从0出发连续七个位置作为完全二叉树的节点;——即:连续的一段可以用size来描述。
有了这三个公式的话,我们就能够把数组中的 《 从0出发连续的一段 》 脑补成一颗完全二叉树~~~!!!
//堆在逻辑概念上是——完全二叉树。
堆是一个特殊的完全二叉树;堆在逻辑概念上,其实是一颗完全二叉树结构;
【左神原话】:(对堆这一数据结构的经典解释!!!)
首先堆必须是一棵完全二叉树,其次堆分为大根堆 和 小根堆。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUaTWT8N-1663993283147)(/Users/yuguangyao/Library/Application Support/typora-user-images/image-20220416173151054.png)]
每一个头节点都是其子树上的最大值;
// 左图即为一个大根堆~ ~ ~! ! !
// 6 是其两颗子树上的最大数;
// 5 是其两颗子树上的最大数;
// 4 是其两颗子树上的最大数;
。。。所有节点都是其两颗子树上的最大数——此即大根堆。
。。。所有节点都是其两颗子树上的最小数——此即小根堆。
//视频中演示的是——用户不断给我数字,得不停地调整堆的结构。
理解了完全二叉树的形状之后,实际的结构如何实现呢?——借助数组。(从0出发的一段儿,可以对应成完全二叉树。)
如何把数组连续出发的一段儿,搞成一个堆呢?
假设:
堆外面有一个接口,用户可以通过这个接口,依次给我一些数字,我把这些数字依次放到我准备好的干净的数组上,然后我怎么样让每一个用户进来的数字,整体形成一个大根堆呢?
新加进来的数会一直和父比较,如果一直比父节点的数要大,就一直交换,一路往上窜,直到比较失败为止。
来到了头节点位置 / 比较失败。
从一个位置出发,往上动的过程称之为heapInsert。
//某个数现在处在index位置 , 往上继续移动。
【用户的需求】:
问题(1)——直接返回堆的头节点即可~~~
//用户想让我提供一种操作~~~
从一个位置出发,往下动的过程称之为heapify ( 堆化 )。
这是最重要的两个操作 , 其余的操作都是从这两种操作演变而来~~~
如果将某堆中的数字a变成未知数X , 那么你还能保持此时的有效区域仍然还是堆数据结构呢???
//用户新加一个数字,调整代价是logN级别的;heapInsert只关心我的父的路径,所以只走一个高度,所以用户新加一个数字的时候,调整代价即是logN。
//用户删掉最大值 , 并将剩下的数调整成堆——代价也是LogN级别。
周而复始 , 当heapSize减至0时 , 这个数组便排好序了,即——不断地将最大的数往后放,并移出堆区。
之前是用户一个一个给你数字,现在要求——用户一口气提供一个数组,直接搞成大根堆。
O( N*logN )
O( 1 )
优先级队列结构,就是堆结构。
起名虽然叫做优先级队列 , 但是其底层就是堆结构。——通常堆顶是优先级最大的~~~!!!
堆排序的地位远远没有堆结构重要!!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npjy6meQ-1663993283148)(/Users/yuguangyao/Library/Application Support/typora-user-images/image-20220613220227745.png)]
//优先级队列的意思;
既然底层是用数组做的我堆结构的实际结构,我虽然可以用heapSize来规定堆的有效范围,但是如果一直添加数字的话,这个数组一定有耗尽的时候;
扩容时:需要将原数组所有内容全部都拷贝到新数组中;
单次扩容的代价是 O( N );
一个数组一共从头开始加了N个数的话,需要扩容 ( log N )次;
需要细腻调整堆结构时,必须手写堆;
//系统堆不是不能完成这种功能,而是不会做到高效;
//你给它一个,它给你一个(唯一交流方式)。
已经形成了堆结构,自己人为的变动某些节点,然后又想以很轻的代价重新调整成堆结构 , 这对于系统堆来说 , 是无法完成的。
如果某一个节点变了,我可以只针对这一个节点看是否需要——heapIfy / heapInsert .
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FSHubZVJ-1663993283148)(/Users/yuguangyao/Library/Application Support/typora-user-images/image-20220818151941943.png)]
【 正 】—— 二参置前
【 负 】—— 一参置前
new PriorityQueue<>(new IdAscendingComparator())
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsCiCike-1663993283148)(/Users/yuguangyao/Library/Application Support/typora-user-images/image-20220818155815241.png)]
词频排序——又称:计数排序。
数组里存放的是人的年龄,因为是年龄,所以可以认为数据状况是——( 0~200 )
如果数组中元素过大,上万位,那么数组的长度是不可想象的。2^ 31 长度的数组?——词频表直接爆炸~ ~ ~
不基于比较的排序 , 全是根据数据状况作的排序 ;
因为高位数字是晚排序的,所以其优先级最高~ ~ ~
桶就是容器的意思,桶的具体结构可以是队列,可以是数组,可以是栈,什么都行~
//在这里我们认为桶的结构是队列。
比数组好太多,比计数排序好太多。
如果是十进制,搞十个桶即可。三进制,搞三个桶即可。
但桶排序依然和数据状况挂钩:
数据必须要有进制这个玩意儿~ ~ ~
完全是由数组中最大值的位数决定~ ~ ~
最大值的位数为N。 <==> 所有数字进出桶的次数。
100 ----> 3次;
1000 ----> 4次;
10000 ----> 5次;
我们利用这个count数组,其实可以做到分片儿~~~
原数组从右往左,根据count[] 来输入到 bucket[] 中,实际上就相当于完成了一次入桶 / 出桶;
利用词频表来完成进出桶~~~