树的高度从零还是一开始数_数据结构与算法之1——树与二叉树

数据结构一直是让人头疼,面试遇到手撕算法题时真是慌得不行,从啥也不会刷题刷到游刃有余,路漫漫其修远兮~~。本人还是个算法菜鸟,而且还是想转行互联网的半吊子(好想拿大厂offer啊,幻想中。。),希望能和大佬共同学习,一起进步,理解不当之处请多多指教。(扯远了,hhh)。咳咳,咱言归正传~

树形结构是数据结构与算法当中最让人头疼的了吧,有没有?(图结构我已经自动放弃了,撸完图结构我可能就没头发了。。,顶多研究点路径搜索啥的吧。。)

备注:以下所有代码块都是基于python实现,大部分代码是自己设计和手打,错误之处请大家指正。

树形结构一些重要特性:

  • 树形结构有唯一的起始结点,树根(root)
  • 一个结点只有一个父节点,但是可以有多个子结点。
  • 从树根出发,经过若干次后继关系可以达到结构中任意一个子结点
  • 树结构中各个结点之间不会形成循环关系,因此会存在某种需的关系,但一般不会像线性表可以形成全序。
  • 两个任意结点,各自通过后继关系到达的所有结点的集合要么互不相交,要么其中一个是另一个的子集

二叉树的概念与性质:

一个结点至多关联两个子结点(左、右结点),即可以关联子结点数为0,1,2。二叉树是树结构中比较常用的,在讨论子结点的时候一定要明确说明是左结点还是右结点

基本概念就不介绍了,这里提几个比较重要的概念:

  • 结点的度数:某结点的子结点个数,0,1,2。
  • 路径,树的高度:从祖先结点到子孙结点的一系列边,形成前后者的联系,这样的一系列边形成路径,边的条数为路径的长度。从树根到树中任一结点路径长度即该结点的层数,而数的高度是树中最大的层数。(最长路径的长度)
  • 层数与结点数的关系:非空二叉树第i 层至多有
    个结点。
  • 高度为h的数至多有
    个结点。
  • 任何非空二叉树,其叶子结点个数为
    ,度数为2的分支结点个数为n,
  • 满二叉树:所有分支结点度数都为2。
  • 扩充二叉树:加入足够多的叶结点,使原来所有分支结点度数都为2,新增结点为外部结点,原来的结点为内部结点。外部结点的个数比内部结点始终多1。
  • 完全二叉树:高度为h的二叉树,从第0层到h-1层结点都满,如果最后一层结点不满,所有结点最左边连续排列,空位都在右边,则是一棵完全二叉树。
  • n个结点的树高度h不超过
    。(很重要,一般考验时间复杂度)
  • 对n个结点按层次从左到右顺序从0开始编号,对任意结点i (
    ):序号为0的结点为根结点;对于i>0,父结点编号为(i-1)/2;若
    ,左子结点编号为
    ,否则为None;若
    ,右子结点编号为
    ,否则为None。(线性表写树结构或优先队列,这个性质很重要)

二叉树的类实现:

二叉树的结点类:

class 

包含三个结点的二叉树t可表示为:

t

二叉树类:

class  

二叉树遍历

深度优先遍历:顺着一条路径尽可能向前搜索,必要时回溯。对于二叉树,最基本的回溯方式为检查完一个结点,由于无路可走,只能回头。

深度优先遍历又分为三种顺序的遍历,具体如何实现看图体会吧,这里每种遍历附上我用python实现的代码。

递归实现二叉树的遍历

def 

下面介绍三种遍历方式的非递归实现:

先根序遍历:根结点—左子树—右子树。

c61e51084808960f748d10b9772799ca.png
def 

时间复杂度:以上将树中每个结点访问一次,另外部分结点(右结点)压入弹出栈各一次,因此时间复杂度为o(n)。

空间复杂度:部分结点(右结点)压入栈中,平均空间复杂度为o(log(n))。

中根序遍历:左子树—根结点—右子树。

9f38ea7c9c2a8c3e5da8c6f868970bbe.png
def 

时间复杂度:所有元素遍历一遍,并且所有结点出入栈各一次,复杂度为o(n)。

空间复杂度:最多存入高度为h的根结点和左子结点,o(log(n))

后根序遍历:左子树—右子树—根结点。

abbc4bdc84e379ffceee83529042a189.png

后根序遍历是最麻烦的,利用非递归的方式,先遍历左子结点,如果有右结点则遍历右子结点,最后在遍历父结点然后强制退栈。

def 

内层循环为找出当前最左最下的结点,将其入栈后停止,如果被访问的结点是其父的左子结点,则直接转到右兄弟结点继续,如果被访问的是右子结点,设t为None,迫使外层循环下次迭代弹出并访问更上一层的结点。

宽度优先遍历

宽度优先遍历,需要用到队列SQueue(先进先出):

def 

优先队列

优先队列应该保证,在任何时候访问和弹出的总是当时在此结构里保存的所有元素优先级最高的。比如每次压入或取出一个数,都能始终保持队尾的元素为最小值或最大值。

基于list实现优先队列

先给优先队列建立一个类:

class 

注意这里的用法,以可变对象作为默认值是一种危险动作(可以自己设置一个函数感受一下,函数重复执行2次看看),应该特别注意,这里用了list转换有两个作用:1.对实参表(包括默认值空表)做一个拷贝,避免共享。2.可以使构造函数的实参可以是任何可迭代对象例如迭代器或元祖。

现在要在PrioQue类里插入元素:

def 

关于优先队列的一些其他操作:

def 

采用线性表技术实现优先队列,在插入元素或取出元素时,总有一种具有线性复杂度的操作。

堆,一种只需要o(log(n))复杂度的优先队列实现

  • 从根结点到任意一个叶结点的路径上,各结点所存数值一定是按优先关系(比如大小)严格递减
  • 堆中最优先的元素必定是根结点,o(1)时间拿到
  • 位于树中不同路径上的元素,不关心其顺序关系
  • 如果小元素优先(根结点元素最小),则称为小顶堆;大元素优先(根结点元素最大),则称为大顶堆。
  • 从堆中加入元素后,必须经过操作得到包含原有结点和新结点的堆,如果弹出根元素,必须把剩余元素重新做成堆,两个操作均是o(log(n))的时间复杂度。

这里我们先用连续表建立一个堆的类:

class 

插入元素和向上筛选(以小顶堆为例)

如果对节点i进行向上筛选,就是比较i节点的值与其父节点值的大小,如果该节点值比其父节点的值大,则这两个节点交换,再拿交换后的这个节点与其父节点比较,依次往上,直到该节点的值比其父节点的值小时或者该节点成为了根节点时结束。则插入元素的步骤为:

  • 把新加入的元素放在所有元素之后(连续表)并执行一次向上筛选操作。
  • 向上筛选操作交换次数不会超过路径长度,即加入元素操作时间复杂度为o(log(n))
def 

注意这里并不是先将e插入堆中,而是先建立一个空值,然后拿着e去找适合的位置,最后再填入。

弹出元素与向下筛选法(以小顶堆为例)

如果对节点i进行向下筛选,就是比较i节点的值与其左右孩子节点值的大小,如果三者中最大的值不是i节点,则i节点与其中值最大的节点进行位置交换(如果只有一个孩子节点,则二者比较再交换),再拿交换后的该节点与其左右孩子节点比较,依次往下,直到该节点的值比起左右孩子的值都大时或者该节点成为叶子节点时结束。

def 

初始堆的建立

还记得之前所提到了buildheap()函数吗?这个函数就是用来建立初始堆的,这个方法是建立在已有一个list(无序)基础之上的,把初始的表看成一个完全二叉树,从下标end//2的位置开始,后面的表元素都是叶结点,很明显,叶结点本身就是堆。那么可以从最右最下的分支结点开始,向左一个个建堆(经历一次向下筛选法),然后再到上一层去建立更大的堆,直至整个表都建立成堆。

def 

创建操作的时间复杂度是o(n),只需要做一次。插入和弹出元素操作均为o(log(n)),所有元素只用了一个简单变量,没用其他结构,因此空间复杂度为o(1)。

堆排序

def 

初始建堆复杂度o(n),第二个循环做了n次,每次取出堆顶元素并做一次向下筛选,堆顶距离不超过log(n),因此时间复杂度为o(nlog(n)),因为只用了几个变量,所以空间复杂度为o(1)。

哈夫曼树

前面介绍过,扩充二叉树的外部路径长度是根结点到其外部结点的路径长度之和。

。那么这里介绍一种带权值的扩充二叉树,即每个外部结点都有一个权值
,定义带权值的扩充二叉树外部路径长度为

哈夫曼树的定义:设有实数集

,T是一棵扩充二叉树,m个结点分别以实数集中的元素为权值,其带权外部路径
在所有的扩充二叉树中达到最小,则称T为该实数集
最优二叉树或者 哈夫曼树

构建哈夫曼树的算法过程:

  • 构造一棵新的二叉树,其左右子树为集合W中挑选的权值最小的两个子树,根结点权值设置为两棵子树之和。
  • 将所选的两棵二叉树从F中剔除,把新构造的二叉树加入W

假设右实数集W

1396db1fad71407c22cc1ff456eef089.png
先选其中最小的2个形成二叉树,然后剔除这2个结点,加入新的根结点进去,以此类推。

哈夫曼树的组合不唯一,若遇到相同最小值具有不同组合,最后仍然都是该实数集的哈夫曼树。如图中步骤(3)有2个4,无论哪个4和3组合最后都能得到哈夫曼树,只不过结构不同。

class 

第一个循环建立m棵二叉树(单点),每假如一次优先队列就要进行一次筛选,时间复杂度为o(log(m)),总共时间复杂度为o(mlog(m)),第二个循环需要做m-1次,每次减少一棵树,优先队列的弹出操作为o(1)复杂度,每次构建一个新的二叉树加入队列,需要o(log(m)),因此总的时间复杂度为o(mlog(m))。算法执行时构造出一棵包含2m-1个结点的树,空间复杂度为o(m)。


参考书籍:《数据结构与算法—python语言描述》—裘宗燕

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值