二叉树顺序表实现的堆 堆排序TopK问题 及二叉树链表实现

树一些基本概念

一个根的子树的个数

叶子节点 没有子节点的节点  即度为0的节点

父节点与子节点是相对的 在不同的所属中有不同的身份

完全二叉树 最后一层之前的层数所有节点都是满的 最后一层的节点也从左向右 没有空隙

满二叉树 最后一层及之前都是满的  是一种特殊的完全二叉树 

二叉树的概念及性质

概念: 二叉树是树中度不大于2的数据结构 可以用链表顺序表实现

性质:

1.若根节点为第一层 则第i层非空二叉树有2^(i-1)个节点

2.若根节点为第一层 则有h层二叉树的节点数最多有2^h-1个节点

3.对任何一颗二叉树 度为0的叶节点个数比度为2的节点个数多1个

4.按层序遍历标序号根节点为0   那么一个节点的根结点就等于该节点-1再/2  一个根的左子节点就等于其*2再+1 右节点就等于其*2再+2

 

二叉树的顺序表实现

二叉树用顺序表实现的意义在于可以实现堆的结构 

堆的介绍

堆是用顺序表实现二叉树的一种结构 前提需要为完全二叉树

堆分为小堆和大堆 

小堆的根节点是最小的 在结构中任一子节点都小于它的根节点

大堆的根节点是最大的 在结构中任一子节点都大于它的根节点


 

二叉树的结构图只是我们想象出来的 真正的存储结构其实就是数据在数组中 通过在数组中的关系实现我们想象的结构 

按图中的序号表示   一个节点的根结点就等于该节点-1再/2  一个根的左子节点就等于其*2再+1 右节点就等于其*2再+2

 

堆数据结构的实现

堆功能实现的问题主要在于插入和删除

堆的插入

首先解决类型顺序表实现的空间问题

 之后插入数据后需要进行一次向上调整 (调整逻辑根据建的堆是大堆还是小堆来决定) 这样每次插入数据之前这个数组就是堆的结构 只需对插入的这个数据进行调整

向上调整:调整这个数据需要找到其的父节点 然后比较child和parent位置的数据 根据所要建小堆还是大堆选择交换的逻辑 这里是建小堆 

然后让child更新为此时的parent parent再更新为此时child的父节点为了进行下一次的比较 在这个过程中只要有一次不满足if的情况就可以直接跳出循环(因为上面的结构已经是建好的堆了 child大于此时的parent 那一定大于其更上层的parent节点) 

 如果一直小于parent节点 一直到chid==0的时候 那此时的child已经是根节点的位置了 结束循环

堆的删除

这里删除是删除堆顶的元素 如果直接删除堆顶元素的话堆的结构就被破坏了

可以先交换堆顶和最后一个元素的位置 然后删除最后一个元素 然后再对第一个元素就行向下调整

 

类似于向上调整 先有一个child为parent*2+1得到 这个得到的child是parent的左节点 parent和child比较之前先让child为左右节点中小的那一个 若果右边的更小则child++   child也就为右节点了 此时进行parent和child位置的比较并进行相应的交换操作

这里要注意的就是最后一次child是一个左节点其右边可能仍有同一个父亲的右节点  这不影响循环结束的条件 但是在找孩子中最小的时候可能发生越界访问的情况

当child为最后一层while循环时候 此时child是最后一个元素或者倒数第二个元素

如果是最后一个元素时候(上图二) 此时进行找最小孩子操作a[child+1]就进行了越界访问了 所以在进行找最小孩子比较之前可以加上child<n-1的判断 这样刚刚的越界情况就不会发生了  另一个左右节点都有的情况(上图一)也不会受到影响

 

在堆的删除操作中我们是把最后一个元素和顶元素进行了交换并删除(这的删除不是真的删掉了这是size-- 不会访问这个元素了)  每次会把最小元素放到当前最后一个位置 那么一直这样下去就是把最小的数不断放到了后面的位置 就完成了从大到小的排序

基于此我们可以对一个数组中的数据进行堆排序 

堆排序

首先我们要把一个数组中的数据建堆

除了类似于堆数据结构在插入数据时对每个数据进行向上调整来建堆之外 我们还可以用向下调整建堆的方式来完成 

向下调整 从最后一个不为叶子节点的位置开始不断向下调整

len-1就是最后一个数据的下标 对此-1再/2就是最后一个节点的父亲节点了 此节点就是第一个不为叶子节点的位置  (前提得是完全二叉树 此处堆的结构就是完全二叉树)

 

 

且两种建堆方式的时间复杂度有很大的差别 向下调整要快很多

证明需要进行详细数学的推导 这里简单来说就是向上建堆 上层的数据少 调整次数也少因为是向上调整 而下层的数据多调整的次数也多乘起来就很大了 而向下排序下层数据多但太调整次数少 上层数据少调整次数多

建堆完成后就可以实现排序的操作了

类似于堆数据结构中实现的排序 每次把堆顶的元素和最后一个元素进行交换 这样堆顶最大或者最小的元素就到最后一个位置了 然后每次len--就不会访问最后这个最值的数据了 再将交换到堆顶的值进行向下调整使其重新为堆的结构 堆顶的就又是最值的元素了 这样不断进行最值元素就不断往尾放实现了排序

基于这样的排序方式 如果我们要让排的序为正序建堆时候就应该建大堆 要让逆序建堆的时候就应该建小堆  而改变建堆方式只需要改变向下排序的逻辑就可以了

堆排序时间复杂度O(N*logN) 建堆O(N))  排序O(N*logN) 

基于堆排序的TopK的问题

 也就是生活中常见的找的前几大或者小的数据的问题

例如给了我们十万个数据让我们找前十大的数据 

正常的简单思路就是两层循环但这样需要很多空间  如果只给我们很小的一个空间怎么实现呢  这用到堆排序就可以做到 且我们可以只用十个int大小的空间就足够了

 思路: 首先开十个int大小的空间 先放入从文件中读取的10个数据建小堆

之后每次读一个数据将其与堆顶的元素进行比较 如果其大于堆顶的数据 那么堆顶的数据就变为该数然后向下调整使这个堆仍为小堆

后面读取的每一个数都进行该过程 所有数据都读取结束之后 这个堆中的数据就是前十大的数据了  在这个过程中我们仅仅只用到了十个int大小的空间就解决了

以下为代码实现 

 

二叉树链表的实现

先定义基本结构 

每一个节点包含该处的数据和指向左右两个节点的指针

 

我们要实现以上功能

 

 

先完成了创建树节点并初始化的函数 然后首先我们先手动创建一些树的节点 并把他们连起来 如下图的结构

二叉树的各功能实现中经常用到递归解决

二叉树的销毁 

对根来说先销毁根的左右节点再销毁根节点 对这颗树来说就会先销毁H   D的左右就销毁完了 然后销毁D  D的这层递归完了就相当于B的左边结束了 然后B的右边然后再B这样一直到A的左右都释放了 最后释放A

计算二叉树的总节点个数

1.首先可以使用static静态变量  每次的递归中该节点不为空就++ 但是有个缺陷 该函数在结束前只能使用一次   static只能初始化一次 和程序同步结束

2.全局变量的方式 和1类似  但是需要在该函数外定义一个全局变量

3.递归方式 对于每一个根来说 它的节点数就等于左树的节点数加上右树的节点数再加1  这样递归下去 就会先算最下层叶子节点等于0+0+1就会为1 有两个叶子节点的根就是1+1+1为3这样一直归到最根处的位置就算出来了   

计算叶子节点的个数

类似于算总节点的个数 仍用递归方式 根的叶子节点数就等于左右两棵树的叶子节点数之和

当左右都为空时候该节点就是叶子节点 返回1   一个节点左边右边都有一个这样的节点其最终就返回2了 就是返回了该节点做为根其有的叶子节点个数 最终一直归到根就算出总叶子节点个数

计算某一层节点的个数

递归逻辑  对于某一层k层节点的个数对于下一层的节点来说就是其的k-1层节点的个数这样一直递归下去到了第k层此时对其来说就是该层 例如 对于第一层来说第五层有多少节点  进第二层 对该层来说把其看成第一层 第五层的节点数就变成了第四层 一直到第五层的 最开始的第五层对其来说就是第一层

这的实现是按节点理解 对根来说第k层的节点数就等于其下一层左右节点对其来说k-1层节点个数之和  一直到k层的节点其返回1 这样其上一层其的根就得到了1 再归回到根 最上面的根就得到了k层节点的个数

查找值为x的节点

先对根的值进行判断 不是的话就判断左右节点是不是  这样一直递归下去 

这里要注意得到左节点是不是的结果(是的话就返回个指针 不是就是NULL) 如果是NULL就不返回了 不然不管左节点什么结果都会返回 就不会进行右节点的判断了

三种序列的遍历

前序遍历即 根 左子树 右子树

中序遍历即 左子树 根 右子树

后序遍历即 左子树 右子树 根

对应上面创建树的结果如下图 (空的时候打印N)  

这三个遍历还是要理解递归  拿中序遍历来说 对于第一次进函数 会先进左节点的递归 到左节点的这层函数仍然是访问其左节点 这样一直到为NULL 才会访问这层的值然后其右

计算二叉树的高度(深度)

对于每一个根来说 它的高度就等于 左右树中更高的那一方再+1  当左右节点都为空此时就是叶子节点直接返回1  

这里注意 算出左右树的高度后 用变量来接收后再比较决定取哪一个  否则后面选择的结果仍然需要递归 而且这不只是加几次递归那么简单 类似于之前是2*2*2*2这样 后面变成3*3*3*3  会多递归很多很多次 尤其是对最下层的来说

层序遍历 

对于自己创建的树来说结果如下图

层序遍历的实现需要依靠队列来实现   依靠队列先进先出的原则   

首先先入一个根节点  然后每次判断队列不为空就取出一个数据然后入它的两个非空左右子节点

判断该树是不是完全二叉树

类似于层序遍历用到队列 不过这里空的也要入队列 

思路: 用层序遍历的方式 当遇到第一个不为空的停下 此时判断队列中有没有不为空的数据 如果有那么该树就不是完全二叉树 

当出现第一个不为空的节点 此时其上层的节点及其左边的节点一定已经全部出队列了   而其同层的右边及同层左边的下一层一定已经入队列了如果此时有不为空的那这棵树自然不是完全二叉树了 

 其同层的右边的下一次及同层左边下一层的再下一层可能有数据没有入队列  这种情况存在的话 那其队列中仍有数据 他们进不进也无所谓 不影响判断

传数组来构建二叉树

每次从数组中读取一个值 根据前序遍历的原则 依次先根再左子树再右子树

从数组中挨着一个一个值取出来 我们就需要依次读取数组的下标  但是不是++就完事了 比如第一层如果传给左右子树的是一个值 左子树中下标改变了不会影响右子树下标的改变  这个过程中会用到递归 我们要让这个下标共用一个 互相能受到影响 除静态变量之外 我们可以使用指针 开始进函数就传了一个变量值为0的地址 每次传的就是该地址 这样不管哪层递归中*pi改变了其他的也会改变

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值