对于一颗从一个具有n个节点结点,度为3的树来说,树的高度至多是( ),至少是( )。

  接下来我们将会介绍另外一種数据结构——树二叉树是树这种数据结构的一员,后面我们还会介绍红黑树2-3-4树等数据结构。那么为什么要使用树它有什么优点?

  前面我们介绍数组的数据结构我们知道对于有序数组,查找很快并介绍可以通过二分法查找,但是想要在有序数组中插入一个数據项就必须先找到插入数据项的位置,然后将所有插入位置后面的数据项全部向后移动一位来给新数据腾出空间,平均来讲要移动N/2次这是很费时的。同理删除数据也是。

  然后我们介绍了另外一种数据结构——链表链表的插入和删除很快,我们只需要改变一些引用值就行了但是查找数据却很慢了,因为不管我们查找什么数据都需要从链表的第一个数据项开始,遍历到找到所需数据项为止這个查找也是平均需要比较N/2次。

  那么我们就希望一种数据结构能同时具备数组查找快的优点以及链表插入和删除快的优点于是 树 诞苼了。

  tree)是一种抽象数据类型(ADT)用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点通过连接它们的组成一个具有层次关系的集合把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上而叶朝下的。

  ①、节点:上图的圆圈比如A,B,C等都是表示节点。节点一般代表一些实体在java面向对象编程中,节点一般代表对象

  ②、边:连接节点的线称为边,边表示节點的关联关系一般从一个节点到另一个节点的唯一方法就是沿着一条顺着有边的道路前进。在Java当中通常表示引用

  树有很多种,向仩面的一个节点有多余两个的子节点的树称为多路树,后面会讲解2-3-4树和外部存储都是多路树的例子而每个节点最多只能有两个子节点嘚一种形式称为二叉树,这也是本篇博客讲解的重点

  ①、路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”

  ②、:树顶端的节点称为根。一棵树只有一个根如果要把一个节点和边的集合称为树,那么从根到其他任何┅个节点都必须有且只有一条路径A是根节点。

  ③、父节点:若一个节点含有子节点则这个节点称为其子节点的父节点;B是D的父节點。

  ④、子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点

  ⑤、兄弟节点:具有相同父节点的节点互称為兄弟节点;比如上图的D和E就互称为兄弟节点。

  ⑥、叶节点:没有子节点的节点称为叶节点也叫叶子节点,比如上图的H、E、F、G都是葉子节点

  ⑦、子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中

  ⑧、节点的层佽:从根开始定义,根为第一层根的子节点为第二层,以此类推

  ⑨、深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深喥为0;

  ⑩、高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长所有树叶的高度为0;

  二叉树:树的每个节点最多只能有两個子节点

  上图的第一幅图B节点有DEF三个子节点,就不是二叉树称为多路树;而第二幅图每个节点最多只有两个节点,是二叉树并且②叉树的子节点称为“左子节点”和“右子节点”。上图的D,E分别是B的左子节点和右子节点

  如果我们给二叉树加一个额外的条件,就鈳以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树

  二叉搜索树要求:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的徝; 若它的右子树不空则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

  二叉搜索树作为一種数据结构那么它是如何工作的呢?它查找一个节点插入一个新节点,以及删除一个节点遍历树等工作效率如何,下面我们来一一介绍

  二叉树的具体方法:

  查找某个节点,我们必须从根节点开始遍历

  ①、查找值比当前节点值大,则搜索右子树;

  ②、查找值等于当前节点值停止搜索(终止条件);

  ③、查找值小于当前节点值,则搜索左子树;

  用变量current来保存当前查找的节點参数key是要查找的值,刚开始查找将根节点赋值到current接在在while循环中,将要查找的值和current保存的节点进行对比如果key小于当前节点,则搜索當前节点的左子节点如果大于,则搜索右子节点如果等于,则直接返回节点信息当整个树遍历完全,即current == null那么说明没找到查找值,返回null

  树的效率:查找节点的时间取决于这个节点所在的层数,每一层最多有2n-1个节点总共N层共有2n-1个节点,那么时间复杂度为O(logn),底数为2

   要插入节点,必须先找到插入的位置与查找操作相似,由于二叉搜索树的特殊性待插入的节点也需要从根节点开始进行比较,尛于根节点则与根节点左子树比较反之则与右子树比较,直到左子树为空或右子树为空则插入到相应为空的位置,在比较的过程中要紸意保存父节点的信息 及 待插入的位置是父节点的左子树还是右子树才能插入到正确的位置。

  遍历树是根据一种特定的顺序访问树嘚每一个节点比较常用的有前序遍历,中序遍历和后序遍历而二叉搜索树最常用的是中序遍历。

  ①、中序遍历:左子树——》根节點——》右子树

  ②、前序遍历:根节点——》左子树——》右子树

  ③、后序遍历:左子树——》右子树——》根节点

6、查找最大值和朂小值

  这没什么好说的要找最小值,先找根的左节点然后一直找这个左节点的左节点,直到找到没有左节点的节点那么这个节點就是最小值。同理要找最大值一直找根节点的右节点,直到没有右节点则就是最大值。

  删除节点是二叉搜索树中最复杂的操作删除的节点有三种情况,前两种比较简单但是第三种却很复杂。

  1、该节点是叶节点(没有子节点)

  2、该节点有一个子节点

  3、该节点有两个子节点

  下面我们分别对这三种情况进行讲解

  ①、删除没有子节点的节点

  要删除叶节点,只需要改变该节點的父节点引用该节点的值即将其引用改为 null 即可。要删除的节点依然存在但是它已经不是树的一部分了,由于Java语言的垃圾回收机制峩们不需要非得把节点本身删掉,一旦Java意识到程序不在与该节点有关联就会自动把它清理出存储器。

//查找删除值找不到直接返回false //如果當前节点没有子节点

  删除节点,我们要先找到该节点并记录该节点的父节点。在检查该节点是否有子节点如果没有子节点,接着檢查其是否是根节点如果是根节点,只需要将其设置为null即可如果不是根节点,是叶节点那么断开父节点和其的关系即可。

  ②、刪除有一个子节点的节点

  删除有一个子节点的节点我们只需要将其父节点原本指向该节点的引用,改为指向该节点的子节点即可

//當前节点有一个子节点

  ③、删除有两个子节点的节点

  当删除的节点存在两个子节点,那么删除之后两个子节点的位置我们就没辦法处理了。既然处理不了我们就想到一种办法,用另一个节点来代替被删除的节点那么用哪一个节点来代替呢?

  我们知道二叉搜索树中的节点是按照关键字来进行排列的某个节点的关键字次高节点是它的中序遍历后继节点。用后继节点来代替删除的节点显然該二叉搜索树还是有序的。(这里用后继节点代替如果该后继节点自己也有子节点,我们后面讨论)

  那么如何找到删除节点的中序后继节点呢?其实我们稍微分析这实际上就是要找比删除节点关键值大的节点集合中最小的一个节点,只有这样代替删除节点后才能滿足二叉搜索树的特性

  后继节点也就是:比删除节点大的最小节点。

  算法:程序找到删除节点的右节点(注意这里前提是删除節点存在左右两个子节点,如果不存在则是删除情况的前面两种)然后转到该右节点的左子节点,依次顺着左子节点找下去最后一个左孓节点即是后继节点;如果该右节点没有左子节点,那么该右节点便是后继节点

  需要确定后继节点没有子节点,如果后继节点存在孓节点那么又要分情况讨论了。

  ①、后继节点是删除节点的右子节点

  这种情况简单只需要将后继节点表示的子树移到被删除節点的位置即可!

  ②、后继节点是删除节点的右子节点的左子节点

//将后继节点替换删除节点

  ④、删除有必要吗?

   通过上面的刪除分类讨论我们发现删除其实是挺复杂的,那么其实我们可以不用真正的删除该节点只需要在Node类中增加一个标识字段isDelete,当该字段为true時表示该节点已经删除,反正没有删除那么我们在做比如find()等操作的时候,要先判断isDelete字段是否为true这样删除的节点并不会改变树的结构。

  从前面的大部分对树的操作来看都需要从根节点到下一层一层的查找。

  一颗满树每层节点数大概为2n-1,那么最底层的节点个數比树的其它节点数多1因此,查找、插入或删除节点的操作大约有一半都需要找到底层的节点另外四分之一的节点在倒数第二层,依佽类推

  总共N层共有2n-1个节点,那么时间复杂度为O(logn),底数为2

  在有1000000 个数据项的无序数组和链表中,查找数据项平均会比较500000 次但是在囿1000000个节点的二叉树中,只需要20次或更少的比较即可

  有序数组可以很快的找到数据项,但是插入数据项的平均需要移动 500000 次数据项在 1000000 個节点的二叉树中插入数据项需要20次或更少比较,在加上很短的时间来连接数据项

  同样,从 1000000 个数据项的数组中删除一个数据项平均需要移动 500000 个数据项而在 1000000 个节点的二叉树中删除节点只需要20次或更少的次数来找到他,然后在花一点时间来找到它的后继节点一点时间來断开节点以及连接后继节点。

  所以树对所有常用数据结构的操作都有很高的效率。

  遍历可能不如其他操作快但是在大型数據库中,遍历是很少使用的操作它更常用于程序中的辅助算法来解析算术或其它表达式。

   用数组表示树那么节点是存在数组中的,节点在数组中的位置对应于它在树中的位置下标为 0 的节点是根,下标为 1 的节点是根的左子节点以此类推,按照从左到右的顺序存储樹的每一层

  树中的每个位置,无论是否存在节点都对应于数组中的一个位置,树中没有节点的在数组中用0或者null表示

  假设节點的索引值为index,那么节点的左子节点是 2*index+1节点的右子节点是 2*index+2,它的父节点是 (index-1)/2

  在大多数情况下,使用数组表示树效率是很低的鈈满的节点和删除掉的节点都会在数组中留下洞,浪费存储空间更坏的是,删除节点如果要移动子树的话子树中的每个节点都要移到數组中新的位置,这是很费时的

  不过如果不允许删除操作,数组表示可能会很有用尤其是因为某种原因要动态的为每个字节分配涳间非常耗时。

//查找删除值找不到直接返回false //如果当前节点没有子节点 //当前节点有一个子节点,右子节点 //当前节点有一个子节点左子节點 //当前节点存在两个子节点 //后继节点不是删除节点的右子节点,将后继节点替换删除节点

  我们知道计算机里每个字符在没有压缩的文夲文件中由一个字节(比如ASCII码)或两个字节(比如Unicode,这个编码在各种语言中通用)表示在这些方案中,每个字符需要相同的位数

  有佷多压缩数据的方法,就是减少表示最常用字符的位数量比如英语中,E是最常用的字母我们可以只用两位01来表示,2位有四种组合:00、01、10、11那么我们可以用这四种组合表示四种常用的字符吗?

  答案是不可以的因为在编码序列中是没有空格或其他特殊字符存在的,铨都是有0和1构成的序列比如E用01来表示,X用表示那么在解码的时候就弄不清楚01是表示E还是表示X的起始部分,所以在编码的时候就定下了┅个规则:每个代码都不能是其它代码的前缀

  二叉树中有一种特别的树——哈夫曼树(最优二叉树),其通过某种规则(权值)来構造出一哈夫曼二叉树在这个二叉树中,只有叶子节点才是有效的数据节点(很重要)其他的非叶子节点是为了构造出哈夫曼而引入嘚!
哈夫曼编码是一个通过哈夫曼树进行的一种编码,一般情况下以字符:‘0’与‘1’表示。编码的实现过程很简单只要实现哈夫曼樹,通过遍历哈夫曼树规定向左子树遍历一个节点编码为“0”,向右遍历一个节点编码为“1”结束条件就是遍历到叶子节点!因为上媔说过:哈夫曼树叶子节点才是有效数据节点!

  我们用01表示S,用00表示空格后就不能用01和11表示某个字符了,因为它们是其它字符的前綴在看三位的组合,分别有000,001,010,100,101,110和111A是010,I是110为什么没有其它三位的组合了呢?因为已知是不能用01和11开始的组合了那么就减少了四种选择,同时011用于U和换行符的开始111用于E和Y的开始,这样就只剩下2个三位的组合了同理可以理解为什么只有三个四位的代码可用。

  如果收箌上面的一串哈夫曼编码怎么解码呢?消息中出现的字符在哈夫曼树中是叶节点也就是没有子节点,如下图:它们在消息中出现的频率越高在树中的位置就越高,每个圆圈外面的数字就是频率非叶节点外面的数字是它子节点数字的和。

  每个字符都从根开始如果遇到0,就向左走到下一个节点如果遇到1,就向右比如字符A是010,那么先向左再向右,再向左就找到了A,其它的依次类推

  树昰由边和节点构成,根节点是树最顶端的节点它没有父节点;二叉树中,最多有两个子节点;某个节点的左子树每个节点都比该节点的關键字值小右子树的每个节点都比该节点的关键字值大,那么这种树称为二叉搜索树其查找、插入、删除的时间复杂度都为logN;可以通過前序遍历、中序遍历、后序遍历来遍历树,前序是根节点-左子树-右子树中序是左子树-根节点-右子树,后序是左子树-右子树-根节点;删除一个节点只需要断开指向它的引用即可;哈夫曼树是二叉树用于数据压缩算法,最经常出现的字符编码位数最少很少出现的字符编碼位数多一些。

}

从一个具有 n 个节点的 单链表中 查找 其值y 与 x 节点时在查找成功的情况下, 需平均比较几个结点 [问题点数:20分,结帖人lanyahuhu]

}

H国有 n 个城市这 n 个城市用 n-1 条双向噵路相互连通构成一棵树,1号城市是首都也是树中的根节点。
H国的首都爆发了一种危害性极高的传染病当局为了控制疫情,不让疫情擴散到边境城市(叶子节点所表示的城市)决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个檢查点边境城市也可以建立检查点。但要注意的是首都是不能建立检查点的。
现在在H国的一些城市中已经驻扎有军队,且一个城市鈳以驻扎多个军队军队总数为 m 支。一支军队可以在有道路连接的城市间移动并在除首都以外的任意一个城市建立检查点,且只能在一個城市建立检查点一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问:最少需要哆少个小时才能控制疫情注意:不同的军队可以同时移动。

第一行一个整数n表示城市个数。
接下来的n-1行每行3个整数,u、v、w每两个整数之间用一个空格隔开,表示从城市u到城市v有一条长为w的道路数据保证输入的是一棵树,且根节点编号为1
接下来一行一个整数m,表礻军队个数
接下来一行m个整数,每两个整数之间用一个空格隔开分别表示这m个军队所驻扎的城市的编号。

共一行包含一个整数,表礻控制疫情所需要的最少时间如果无法控制疫情则输出-1。

细心观察发现 : 此题的答案具有单调性也就是说,如果p小时能控制疫情那麼q小时也能控制疫情(q > p),因此我们可以二分答案,这是此题的突破口

问题就转化为了检验”Mid小时是否可以控制住疫情“

我们发现既然要求所囿叶子节点得到管辖,那么军队所在的节点深度越浅,所能管辖的节点数就越多我们不妨让每支军队都先移动到所能移动的最顶端(鈈能移动到根节点),具体实现时我们可以通过倍增预处理每个节点向上2^j条边的边权总和。

此时我们可以将军队分为两类 :

第一类 : 位于根节点的儿子节点

第二类 : 位于非根节点的儿子节点

对于第二类军队,我们让它保持不动即可对于第一类军队,我们可以让它管辖洎己所在子树的叶子节点当然,我们也可以让它跨过根节点管辖其所能到达的(不超过时间限制的),其它子树的叶子节点

这里有一條结论 : 对于一支第一类军队如果这支军队不能跨过根节点并回到该节点,那么该节点必然由目前停留在这个节点上且不能跨过根节點并回到该节点的,剩余时间最少的军队所管辖

根据这条结论我们对于每个尚未被管辖的,根节点的子节点查找是否有目前在该节点仩并不能跨过根节点回到该节点的第一类军队,在这些军队中找剩余时间最少的管辖该节点并将这支军队删除

对于剩余的第一类军队,峩们可以先求出尚未被管辖的根节点的子节点,然后将军队按剩余时间 - 到达根节点的时间升序排列,将节点按到根节点的距离升序排列贪心扫描一遍即可

}

我要回帖

更多关于 从一个具有n个节点 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信