b站视频:路飞IT学城
https://www.bilibili.com/video/BV1mp4y1D7UP
文章目录
个人博客
https://blog.csdn.net/cPen_web
#21 堆排序前传堆和堆的向下调整
### 堆排序——什么是堆
# 堆:一种特殊的完全二叉树结构 # 注:完全二叉树:满的,最后一排可以少
# 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
# 小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
#注:完全二叉树:满的,最后一排可以按顺序少
#注:满二叉树:一个都不少的
#注:9比8、7 大……大根堆 父亲节点都比孩子节点大 整个二叉树叫大根堆
#注:1比2、6 小……小根堆 父亲节点都比孩子节点小 整个二叉树叫小根堆
#堆排序使用大根堆 排出来是增序的
#9看成省长,8、7看成县长,9管着2个县长
#8管着6、5两个村长
#6村长下面管着2个村民
#-----------------------------------------------------------
### 堆排序̶̶堆的向下调整性质
# 假设根节点的左右子树都是堆,但根节点不满足堆的性质
# 可以通过一次向下的调整来将其变成一个堆。
#注:这不是大根堆,因为2不比9和7大,省长领导不了县长
#注:但是2个县秩序都没问题
#注:怎么来还,这个过程就就做向下调整
#注:2换下来,让左边的9上去(因为右边的7上去,还是领导不了9)
#注:2不能做左边的县长,把8找上去(8和5里更大的那个村长上去)
#注:2不能做左边的村长,选6上去(6和4里最大的村民上去)
#注:最后 到了叶子节点了,2就做1个村民
#注:这一番折腾完后,整个树是1个大根堆了,这个过程叫做堆的向下调整过程
#当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换成1个堆
#注:把最上面的省长移动到下面,不一定是到最后
#22 堆排序的过程演示
### 堆排序过程
# 1.建立堆。 # 注:建立堆的过程后面再说
# 2.得到堆顶元素,为最大元素
# 3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。
# 4.堆顶元素为第二大元素。
# 5.重复步骤3,直到堆变空。
9
8 7
6 5 0 1
2 4 3
#注:这个堆建立好了,堆顶(根节点) 一定是最大的数(大根堆)
#注:把9拿下来。8不能上去,否则8原来的位置回空,6来补会空,4来补也会空,最后会出现空,就不是完全二叉树了
#注:所以不这么做
#注:把3放上去,满足了向下调整的条件,2个县都没有问题
#注:通过1个向下调整把这个堆变为合法的堆。这个过程把现在堆里面最大的数选出来,如下
8
6 7
4 5 0 1
2 3
#注:这时候把8拿下来(退休),再把3放上去(因为3是最后一个,每次找 都找堆的最后1个),如下
7
6 3
4 5 0 1
2
#注:7退休,拿下来。找个棋子2上来,调整,如下 [注:每次出1个数,整个堆都在变小]
6
5 3
4 2 0 1
#注:6下去,棋子是1……………………
# 最后 棋子0上去 0再下去。整个序就排好了
#挨个出数
#------------------------------------------
#构造堆
#如何建造堆?
#注:要建造1个堆,首先保证下面的有序,先村有序,再县有序
#注:看最后1个非叶子节点3 (因为0、7、2、4、5都是叶子节点)
6
8 1
9 3 0 7
2 4 5
#注:3 5 村无序,但是村民有序。换村长 3下来 5 上去
# 再看前1个村 ,9 2 4 是好的,不用调整
# 接着看右边的县长 1 0 7 ,调整 7 0 1
# 接下来看8 9(2 4) 5(3) 满足2个村都是好的,但是县不好,调整.如下
6
9 7
8 5 0 1
2 4 3
# 接下来省长6 调整
9
8 7
6 5 0 1
2 4 3
#这个时候 ,堆构造好了,所有都有序了,构造堆的过程就结束了 (过程是农村包围城市,先从最后1级 有孩子的节点开始看起,先调整小的,再调整大的,最后调整整个的)
# 当整个都调整好了,堆就构造好了
#构造堆以后,挨个出数,一个一个退休,一个一个调整 ,这就是堆排序的过程
#23 向下调整函数的实现
### 堆排序过程
# 1.建立堆。 # 注:农村包围城市,先县后省
# 2.得到堆顶元素,为最大元素 # 注:得到最大的元素后拿下来
# 3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。 # 注:找棋子上去,向下调整,重新有序
# 4.堆顶元素为第二大元素。
# 5.重复步骤3,直到堆变空。 # 注:接着重复拿出来放上去,直到堆空
#注:算法,内存能省则省,时间能快就快。堆排序没必要占一个新的内存
#怎么省?答:把省长9拿下来,不丢掉,放到上去的元素原来的那个末尾地方3,那个位置不是堆了,在左边的位置弄一个标志,记录那是堆最后的位置
#上去的元素一开始下面的位置,用来存原来的省长9,标记末尾9位置前一个的位置 是堆的最后的位置………………
#最后得到的是1个升序的,因为最后面从最大的开始放的
#怎样写出退排序? 在堆排序里反复用到的性质是调整
#先写调整的小函数
# def sift(li, low, high): # 堆的最后1个元素的下标是high (4下标是high)
# low是堆的第一个元素,因为构造堆的时候可能不是看整个的列表,而是小部分
#li:列表
#low:堆的根节点位置,用途:刚开始要看堆顶
#high:堆的最后一个元素的位置,用途:一直往下调整,为了下标不越界,是父亲找孩子
#2i+1跟high比,如果大于了,说明越界了,就赶快停止了放最后1层了
def sift(li, low, high): # 这是调整函数
#先把省长拿下来,接着一个循环一直看拿下来的这个 能干哪一层(省长肯定不能干了)
# 刚开始i 指父亲 这一层,j指孩子这一层 一共2层对比(i 跟左右2个比,如果i大于左右2个最大的还大,如果不是 就把j位置上的数放到i这个位置上)
# 然后i、j在往下移一层
i = low # 刚开始i 是堆顶
j = 2*i + 1 #从父亲找孩子,先看左孩子 ;j开始是左孩子
tmp = li[low] # 把堆顶存起来
#i指的是现在节点要去的位置
#j是看它的孩子能不能顶上去
while j <= high: # 只要j位置没有越界,就一直循环
if j + 1 <= high and li[j+1] > li[j]: # 如果右孩子 > 左孩子 且右孩子有
j = j + 1 # 那就把j指向右孩子
if li[j] > tmp: # 如果j位置大,j上去补候补tmp 当省长
li[i] = li[j]
i = j # 往下看1层
j = 2 * i + 1
# (3) i -->空位
# j--> 8 7 # 注:j指向8,8>3,j=8上去,补了i的位置即li[i]=li[j]
#然后 i到 原来j的位置 ,j指向i的左孩子,即更新i、j。
#代码是 i = j , j = 2 * i + 1
#总结起来是 下边的县长比候选的tmp更大,让县长上去,i指向县的位置(i = j),j指向县长的左孩子
else: # tmp比县长大的情况,把tmp放到i的位置上,i就不用往下看了
li[i]= tmp # 把tmp放到某一级领导的位置上
break # 结束循环
#这个函数相当于把省长拿下来放到合适的位子,左右2个已经是个堆了
else: # 这是到最下面的情况,j指向不存的的下面
li[i] = tmp # 把tmp放到叶子节点上
#最后的语句可以优化,删除倒数第4句的 li[i]= tmp
def sift(li, low, high):
# li:列表
# low: 堆的根节点位置
# high: 堆的最后一个元素的位置
i = low # i最开始指向根节点
j = 2 * i + 1 # j开始是左孩子
tmp = li[low] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j + 1 <= high and li[j+1] > li[j