二叉树的一些概念:
![](https://i-blog.csdnimg.cn/blog_migrate/b6994959c2311da12e957edd08e9b91f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6d53b81aedc0340b7fae506feaf441b6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/bcd4967338c1ff4ac1cec7b6c52567c9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/37c93f4319be9d90a76d4b81fff71fce.png)
![](https://i-blog.csdnimg.cn/blog_migrate/272c195299d3f77e5581334d40bf707b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d670f50dab951cdf23a943e2c03c4fc9.png)
//二叉树有严格的左右子树之分,度数为2的树则没有对此进行要求。
//有序树:如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
//度数为2的有序树不能称为二叉树,因为当某个结点度数为1的时候,没有左右之分。
二叉树的性质:
非空二叉树上的叶结点等于度为 2的结点数+1
//也是由叶子结点+非叶子结点= n = 孩子结点 + 1推导出;
孩子结点:结点的度数为1度数为2度数为0的情况之和(如果是完全二叉树就没有度数为1的情况,就直接是2 * m(m是非叶子结点的个数)),相当是没有根结点的层序遍历。
设度数为012的结点个数分别为n1, n2, n3
则有n1+n2+n3 = n;
由上面孩子结点知:n = n2*1 + n3*2 + 1;//不算度数为0得情况,因为度数为0是叶子结点没有孩子
两式联立得,n1 = n3 + 1;即叶结点等于度为 2的结点数+1
常见的两种二叉树:
![](https://i-blog.csdnimg.cn/blog_migrate/76091642c725978378d028755d2c8113.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9f9ff99aad188bb5a73e7a44e2ae91a3.png)
//如上E为满二叉树,每一层的结点个数都达到了当层能达到的最大结点数
![](https://i-blog.csdnimg.cn/blog_migrate/0963436bd507ce40383b7ef2ef399e51.png)
![](https://i-blog.csdnimg.cn/blog_migrate/cf7e7d6a57f90e93d8aa9aa93c65198e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/691620f5b9aa105dbce226e539a87b23.png)
//满二叉树自上而下,从左到右依次进行编号,当且仅当二叉树中的每一个结点的编号都与该满二叉树的结点的编号一一对应,称之为完全二叉树
![](https://i-blog.csdnimg.cn/blog_migrate/14a23fcbc9d3fc514a9e3a33fa071fae.png)
//理论上可以使用数组来存储完全二叉树,用数组下标作为结点编号,则该结点的左子树就为2*x, 2*x+1,可以找到它的左右结点
![](https://i-blog.csdnimg.cn/blog_migrate/aad73a20de2f3d5a58d5ba76006ea94a.png)
//不能通过该结点的编号来进行判断是否为叶子结点,因为只有最右边的结点的编号等于结点个数,最后一层的其他结点的编号都是小于结点数的,因此无法判断,一般通过判断其左子树的编号是否大于结点个数即可,为什么不用判断右子树,因为没有必要,如下图编号为4的结点,左子树的编号即为8,大于7,最小的编号都大于了,右子树肯定也大于了,没必要判断
//换句话来说,也就是int id, if(id > n) return ;当当前结点的编号大于元素个数,则说明达到了空结点
![](https://i-blog.csdnimg.cn/blog_migrate/bf99cc275d9f656b0730fc690256fe64.png)
完全二叉树:
①完全二叉树的定义:完全二叉树是指完全填充的树,但底层可能除外,底层是从左到右填充的。
②完全二叉树的存储除了用二叉链表存储以外还可以用更方便的数组进行存储,从1开始(根节点下标,1号位存放的必须是根节点),到n,下标为其结点的编号,值为权重。
这样就有:完全二叉树中任意一个结点编号为x,则左节点一定是2x,右节点的编号一定是2x+1
除以以外,该数组中元素的存放顺序恰好是该完全二叉树的层序遍历序列。
//一定要记住,二叉树有0,1,2这三种度,结点个数= 这三种度数结点个数之和,千万不要忘记度数为1的情况,也就是完全二叉树和二叉树一定要区分开!!!
对二叉树进行操作
二叉树的查找、修改、插入与删除
![](https://i-blog.csdnimg.cn/blog_migrate/cea3fe32208796dfae6829668a0eb6e5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/85fedac5985427203d430c5d1cac73df.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b8e785d259a6fd7f8946c8122c3994db.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8ee64e8b42b78d344e34b4c05afbadf3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f8965027d5e5f28c0d73a72d7a49f91c.png)
//
![](https://i-blog.csdnimg.cn/blog_migrate/ab064a9beff27a3cd5250c22d21cafb2.png)
//修改指针就需要加引用修改指针本身,如果是修改指针所指向的内容就没必要,按值传参使用指针的副本即可。
![](https://i-blog.csdnimg.cn/blog_migrate/db8b10e7d3b0678178adfb5871cee8ed.png)
//
![](https://i-blog.csdnimg.cn/blog_migrate/d4d97e9cd05bccd48840ab054ca48763.png)
//
二叉树的遍历
(11条消息) 二叉树的性质和遍历_柘木木的博客-CSDN博客
![](https://i-blog.csdnimg.cn/blog_migrate/cda360b61c36b87d6554c09d01ca86e5.png)
//遍历顺序中,左子树一定先于右子树,所谓“先中后”苏配置的是跟接待你在遍历中的位置
先序遍历:(根左右)
![](https://i-blog.csdnimg.cn/blog_migrate/aa4178892cde84b92d11afd7bd1e71bd.png)
//得到的先序遍历的序列为ABDECF
![](https://i-blog.csdnimg.cn/blog_migrate/a1bf6ad464cda9574750fc8663302aa6.png)
//由图我们可以知道A的子树有哪些结点,右子树有哪些结点,如果只给出一个序列,则我们不能知道左子树的序列多有长,也就不知道右子树的根节点是多少,因此不能确定一棵树
中序遍历:(左根右)
![](https://i-blog.csdnimg.cn/blog_migrate/aa4178892cde84b92d11afd7bd1e71bd.png)
//同样使用该图,得到的中序遍历DBEACF
![](https://i-blog.csdnimg.cn/blog_migrate/19d4ce0767fa0542318b4e76962cdc5d.png)
//通过中序遍历序列区分左右子树序列,通过先序遍历或者后序遍历序列或者层序遍历序列确定根节点
后序遍历:(左右根)
![](https://i-blog.csdnimg.cn/blog_migrate/7a7e008e28a3f6470eae994a20f74428.png)
//后序遍历得到的序列为:DEBFCA
![](https://i-blog.csdnimg.cn/blog_migrate/65783088439fa894eed3b06e0564333c.png)
//利用层序遍历找到根节点,中序遍历分出左右子树,知道左右子树序列长度放到层序遍历序列中又能找出,左右子树的根节点,这样子一个个根节点找出来,从上到下确定一颗二叉树。
//要保证所有元素不相同,否则可能会找错根节点从而分错左右子树
层序遍历:(按层次的顺序,从上到下,从左到右)
![](https://i-blog.csdnimg.cn/blog_migrate/196aafe8356f46dcc6a6cbf49e4d455f.png)
层次遍历就相当于从根结点开始广度优先搜索
基本思路如下:
![](https://i-blog.csdnimg.cn/blog_migrate/1a7f460aaa5992d18cfc8ccccf6f0966.png)
![](https://i-blog.csdnimg.cn/blog_migrate/642077bd58f11ead7560c8c90917dbdd.png)
//定制队列的时候是queue<node* > q而不是 queue<node >q;
![](https://i-blog.csdnimg.cn/blog_migrate/87fa7db5dfa5a338e09cd222918c3673.png)
//入队的时候,孩子结点的层次是父亲结点的层次+1即可,记得根节点的层次是1;
最后一个关于二叉树遍历的重要问题
![](https://i-blog.csdnimg.cn/blog_migrate/80e9663b2f4e14573cc6e1fae1c1008c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/09ea9ffbf523728474fd3e735c29101f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7804d10e81aae0dd0c22b3bcd77b8a17.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f418e99e6eb260e6abf8a3a9e1d4636e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c4502093ba47c90cf4940096b697e025.png)
//如先序遍历,根据中序遍历序列得出左右子树的长度,A/BC/DEF,B和D又是一个根结点,且为A的左右孩子,即一次遍历能确定两个左右孩子,从而确定出一颗二叉树,如下图
![](https://i-blog.csdnimg.cn/blog_migrate/e8fa10efd767f24dabd50e4eb50d02d6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/891debb046b5cd8f2676aaec7eaf964b.png)
//最后一次序列的长度应该是1,即该子树只有一个根节点没有左孩子右孩子,再往下递归先序序列的长度就小于或者等于0了
//建树的过程有点像先序遍历(根左右);
![](https://i-blog.csdnimg.cn/blog_migrate/985fc806cc569c5c2b7d39b16d244d91.png)
![](https://i-blog.csdnimg.cn/blog_migrate/419eab7ab194686f02f563ec74725d54.png)
遍历二叉树需要掌握:
1.如何进行先序,中序,后序遍历一颗二叉树
2.如何利用中序遍历序列和任何一个遍历序列去确定一棵二叉树
3.如何反转一颗二叉树
树
树的遍历:
树的遍历的方式只有两种,分别是先根遍历和层序遍历
(11条消息) 笔记-树的遍历_柘木木的博客-CSDN博客//代码和总结
先根遍历 : 总是先访问根结点,再去访问所有子树
![](https://i-blog.csdnimg.cn/blog_migrate/62713909ba3e6976c29f261adf818504.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a7ce7491dc0ecd17b3dcb9841025e551.png)
![](https://i-blog.csdnimg.cn/blog_migrate/088142008ac33c232e53bc5170d813d0.png)
和二叉树层序遍历的思想是一样的,总是从树根开始,自上到下,从左到右一层层地向下遍历
![](https://i-blog.csdnimg.cn/blog_migrate/859597ba5acf678cf6b6a19d26e6e9c6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a04c947c6f53ae6563bf80d9c0c1f942.png)
树的遍历和DFS和BFS的关系
![](https://i-blog.csdnimg.cn/blog_migrate/063701bce830254aa0cf90534256eeb5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/77444ac0fa698c9906e015e561d196c5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/98ffcf513cba78cd9321b5f6fe8058d7.png)
//树的先根遍历就是DFS,深度优先搜索以"深度"作为第一关键词进行对结点遍历
![](https://i-blog.csdnimg.cn/blog_migrate/eb0a7f30ee431b5a33576a79bff02c69.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b53bc2ef5e290e3e99ca99c4d3c66053.png)
//“剪枝”是DFS从树的角度和理解产生出来的概念,将不可能或者不确定的解剪掉,从而减少遍历的计算量,但要注意不要剪错了导致解缺失或错误
![](https://i-blog.csdnimg.cn/blog_migrate/486cedb9f18c5783d9a23d33cc8a0191.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0c7c39d4644c2c12d3a68cedd3ba4b01.png)
没有权值的树可以使用vector来表示,而不需要特意建立一个结构体,因为我们只是对树进行遍历而已
树的遍历需要掌握的大题://机试复试也可以看着复习;
遍历树,求出一条或多条权值和等于值W的路径
//void DFS(int newNodeId, vector<int > path, int W)
求树的深度或者叶子结点的深度
//void DFS(int newNodeId, int depth);//递归来增加深度,son.depth == father + 1;
求结点的所在层次或者统计每层有多少个结点,最深的是那一层//BFS或者DFS都可以
// void DFS(int nowNodeId, int depth)
//
我们可以发现树的遍历DFS都是要当前的结点作为参数,且要用for去递归其孩子结点,这两步都是可以无脑写的,然后再看求什么再放什么参数,比如求深度就放个depth