二叉树总结
来源:互联网 发布:油画 知乎 编辑:程序博客网 时间:2024/06/05 01:02
什么是二叉树?
引用自百度百科:
在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree),同样的左右子树也都是二叉树.
前言
本文代码实现为 C/C++,因为不是一个完整的讲解文章,只是个人思路,所以说思路讲解可能有不足之处,有错误请指出.
节点定义
使用单向链表的形式,只保存当前节点的子节点和权值,不保存父节点.
typedef struct Node{ int value;//权值,根据实际情况而定,这里用数字 Node *lChild;//左儿子节点 Node *rChild;//右儿子节点}BNode;
二叉树的遍历
二叉树的遍历分为三种:
- 前序遍历
先遍历当前节点(根节点),然后遍历左儿子节点,再遍历右儿子节点 - 中序遍历
先遍历左儿子节点,然后遍历当前节点(根节点),再遍历右儿子节点 - 后序遍历
先遍历左儿子节点,然后遍历右儿子节点,再遍历当前节点(根节点)
我们创建二叉树的时候一般用的是递归的方法,但是二叉树的遍历可以有递归和非递归两种方式.下面会分别给出二叉树遍历递归和非递归的思路和代码.
PS:建议看懂思路后再看代码实现,然后手动模拟一下,效果更佳.
前序遍历
递归版:
先遍历根节点,然后遍历左子树,再遍历右子树
非常经典的递归思想,理解二叉树的性质和递归思想即可得出.
void preorderTraversal(BNode *rootNode) { if(rootNode != NULL) { printf("%d ", rootNode->value); preorderTraversal(rootNode->lChild); preorderTraversal(rootNode->rChild); }}
非递归版:
使用栈来实现,因为 C++ 中有现成的库,所以说就不手动模拟栈了,当遍历到一个节点的时候,先将此节点输出,然后一直遍历其左儿子,依次循环,直到碰到叶子节点,然后开始弹,也就是递归过程中的回溯。
对于任一结点node:
- 访问结点node,并将结点node入栈;
- 判断结点node的左孩子是否为空
- 若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点node,循环至1
- 若不为空,则将node的左孩子置为当前的结点node;
- 直到node为NULL并且栈为空,则遍历结束。
void preorderTraversalNonrecursive(BNode *rootNode) { stack<BNode *>s; if (rootNode == NULL ) { return ; } BNode *tempNode = rootNode; while(!s.empty() || tempNode != NULL) { while(tempNode != NULL) { printf("%d ", tempNode->value); s.push(tempNode); tempNode = tempNode->lChild; } if (!s.empty()) { tempNode = s.top(); s.pop(); tempNode = tempNode->rChild; } }}
中序遍历
递归版:
先遍历左子树,然后遍历根节点,再遍历右子树
void inorderTraversal(BNode *rootNode) { if(rootNode != NULL) { inorderTraversal(rootNode->lChild); printf("%d ", rootNode->value); inorderTraversal(rootNode->rChild); }}
非递归版:
和先序遍历一样的思想,因为要先访问左子树,然后再访问根节点(当前节点),那么在栈弹出的时候进行输出即为所求.
对于任一结点node:
- 若其左孩子不为空,则将node入栈并将node的左孩子置为当前的node,然后对当前结点P再进行相同的处理;
- 若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的node置为栈顶结点的右孩子;
- 直到node为NULL并且栈为空则遍历结束
void inorderTraversalNonrecursive(BNode *rootNode) { stack<BNode *>s; if (rootNode == NULL ) { return ; } BNode *tempNode = rootNode; while(!s.empty() || tempNode != NULL) { while(tempNode != NULL) { s.push(tempNode); tempNode = tempNode->lChild; } if (!s.empty()) { tempNode = s.top(); s.pop(); printf("%d ", tempNode->value); tempNode = tempNode->rChild; } }}
后序遍历
递归版:
先遍历左子树,然后遍历右子树,再遍历根节点
void postorderTraversal(BNode *rootNode) { if(rootNode != NULL) { postorderTraversal(rootNode->lChild); postorderTraversal(rootNode->rChild); printf("%d ", rootNode->value); }}
非递归版:
后序遍历的非递归版和先序遍历、中序遍历不一样,这里我用两个栈来实现,一个栈来保存遍历节点并不断的进行弹出,一个栈来保存节点的遍历顺序,最后遍历第二个栈.
void postorderTraversalNonrecursive(BNode *rootNode) { stack<BNode *>s1; stack<BNode *>s2; if (rootNode == NULL ) { return ; } s1.push(rootNode); while(!s1.empty()) { BNode *tempNode = s1.top(); s1.pop(); s2.push(tempNode); if (tempNode->lChild) { s1.push(tempNode->lChild); } if (tempNode->rChild) { s1.push(tempNode->rChild); } } while(!s2.empty()) { printf("%d ", s2.top()->value); s2.pop(); }}
分层遍历二叉树
即每次输出二叉树的一层,例如:
实现思路:广度优先遍历(BFS),使用队列来实现,每次遍历当前节点的左右儿子节点,并将其加入队列中,然后进行队列的弹出直到队列为空.
void levelTraversal(BNode *rootNode) { queue<BNode *>q; q.push(rootNode); while(!q.empty()) { BNode *tempNode = q.front(); q.pop(); printf("%d ", tempNode->value); if (tempNode->lChild) { q.push(tempNode->lChild); } if (tempNode->rChild) { q.push(tempNode->rChild); } }}
S型打印二叉树
这里我使用的是队列 + 栈来实现的,队列存储当前遍历的节点的序列,栈中存储下一层遍历的节点顺序,使用一个 level 来判断遍历方向
void STraversal(BNode *rootNode) { queue<BNode *>q; stack<BNode *>s; int level = 1;//根节点为第一层 q.push(rootNode); while(!q.empty()){ BNode *temp; while(!q.empty()){ temp = q.front(); printf("%d ", temp->value); if (level % 2) {//下一层要从左到右遍历 if (temp->rChild != NULL) { s.push(temp->rChild); } if (temp->lChild != NULL) { s.push(temp->lChild); } } else {//下一层要从右到左遍历 if (temp->lChild != NULL) { s.push(temp->lChild); } if (temp->rChild != NULL) { s.push(temp->rChild); } } q.pop(); } while(!s.empty()){ temp = s.top(); //将下一层节点的按照遍历顺序加入队列中 q.push(temp); s.pop(); } level++; }}
二叉树深度
一棵空树的深度为0,只有根节点的数的深度为1,所以说一个数的深度 = max(左子树的深度,右子树的深度) + 1(当前节点),使用递归来求.
int findDeepOfTree(BNode *rootNode){ if(rootNode == NULL){ return 0; } return max(findDeepOfTree(rootNode->lChild), findDeepOfTree(rootNode->rChild)) + 1;}
树的宽度
树的宽度即是节点最多的一层的节点数,根据前面分层遍历二叉树的原理,每次从队列中取出一层的节点,将其子节点加入队列,然后查看节点数,即队列的大小.
int findWidthOfTree(BNode *rootNode){ queue<BNode *>q; q.push(rootNode); int ansWidth = 1; while(!q.empty()) { for(int i = 0; i < q.size(); i++) { BNode *tempNode = q.front(); q.pop(); if (tempNode->lChild) { q.push(tempNode->lChild); } if (tempNode->rChild) { q.push(tempNode->rChild); } } ansWidth = max(ansWidth, (int)q.size()); } return ansWidth;}
求叶子节点数
叶子节点,即不含子节点的节点,当一个节点的左右儿子皆为空的时候,此节点为叶子节点,所以可以得出:树的叶子节点数 = 左子树的叶子节点数 + 右子树的叶子节点数.
int findNumOfLeafNode(BNode *rootNode){ if(rootNode == NULL) { return 0; } if(rootNode->lChild == NULL && rootNode->rChild == NULL) { return 1; } return findNumOfLeafNode(rootNode->lChild) + findNumOfLeafNode(rootNode->rChild);}
求树的一层有多少节点
根节点某层的节点数 = 左子树中某层的节点数 + 右子树中某层的节点数
设置一个层数变量 level,在递归过程中,通过 level 的减小模拟下降的过程,当level = 1的时候说明到达了我们要找的那一层,那么返回当前节点数,也就是1.
int findNumOfNodeOnLevel(BNode *rootNode, int level) { if (level == 0 || rootNode == NULL) { return 0; } if(level == 1){ return 1; } return findNumOfNodeOnLevel(rootNode->lChild, level - 1) + findNumOfNodeOnLevel(rootNode->rChild, level - 1);}
求树的直径(树上两个节点间的最大距离)
这里有两种方案:
方案一:
直接遍历每个点,模拟当前点为两个节点路径的转折点时的最大距离,那么也就是当前节点的左子树深度 + 右子树深度,然后取最大值
时间复杂度: O(n^2)
int findDiameterOfTree1(BNode *rootNode) { if(rootNode == NULL) { return 0; } return max(max(findDiameterOfTree1(rootNode->lChild), findDiameterOfTree1(rootNode->rChild)), findDeepOfTree(rootNode->lChild) + findDeepOfTree(rootNode->rChild));}
方案二:
在求子树深度的时候就将最长距离求出
int findMaxDeep(BNode *rootNode, int &ans) { if (rootNode == NULL) { return 0; } int leftDeep = findMaxDeep(rootNode->lChild, ans); int rightDeep = findMaxDeep(rootNode->rChild, ans); ans = max(ans, leftDeep + rightDeep); return max(leftDeep, rightDeep) + 1;}int findDiameterOfTree2(BNode *rootNode) { int ans = 0; findMaxDeep(rootNode, ans); return ans;}
求一个节点到根节点的路径
定义一个从某节点到根节点的栈,这里使用 vector 来实现,具体步骤:
- 将当前节点压入栈中
- 寻找当前节点左子树是否含有要寻找节点(递归进行)
- 如果有,栈中存放的就是路径
- 如果没有,再寻找右子树是否含有要寻找节点
- 如果有,栈中存放的就是路径
- 如果都没有,弹出当前节点,在遍历栈中的上一个节点
bool findPathFromNodetoRoot(BNode *rootNode, BNode *node, vector<BNode *> &path) { if (rootNode == NULL || node == NULL) { return false; } path.push_back(rootNode); if (rootNode == node) { return true; } bool isFind = findPathFromNodetoRoot(rootNode->lChild, node, path); if (isFind == false) { isFind = findPathFromNodetoRoot(rootNode->rChild, node, path); } if (isFind == false) { path.pop_back(); } return isFind;}
求两个节点的最近公共祖先
如果当前遍历到了一个根节点,那么检查我们要查找的两个节点在当前节点的哪个子树中,会有三种情况:
- 都在左子树
- 都在右子树
- 一个在左子树,一个在右子树
情况一和情况二的时候我们需要返回节点所在子树的遍历结果,情况三的时候说明我们已经找到了最近公共祖先(不明白的最好手动模拟一下)。
BNode * findCommonNodeOfTwoNode(BNode *rootNode, BNode *node1, BNode *node2) { if (rootNode == NULL || node1 == NULL || node2 == NULL) { return NULL; } if (node1 == rootNode || node2 == rootNode) { return rootNode; } BNode *leftLCANode = findCommonNodeOfTwoNode(rootNode->lChild, node1, node2);//检查左子树 BNode *rightLCANode = findCommonNodeOfTwoNode(rootNode->rChild, node1, node2);//检查右子树 if (leftLCANode != NULL && rightLCANode != NULL) {//一个在左一个在右,找到目标 return rootNode; } if (leftLCANode == NULL) {//全在右子树 return rightLCANode; } else {//全在左子树 return leftLCANode; }}
还有一种方案就是将两个节点到根节点的路径都求出,然后开始对比,如果遇到第一个相同的节点即为所求,这个留给读者自己实现吧.
求两个节点间的路径
分别将两个节点到根节点的路径求出,然后对根节点到两个节点的最近公共祖先节点的路径进行去重即可.
void findPathBetweenTwoNode(BNode *rootNode, BNode *node1, BNode *node2, vector<BNode *> &path){ vector<BNode *>path1; findPathFromNodetoRoot(rootNode, node1, path1); vector<BNode *>path2; findPathFromNodetoRoot(rootNode, node2, path2); vector<BNode *>::iterator it1 = path1.begin(); vector<BNode *>::iterator it2 = path2.begin(); BNode *LCANode; int cnt = 0; //去重 while(it1 != path1.end() && it2 != path2.end()) { if (*it1 == *it2) { LCANode = *it1; cnt++; } it1++; it2++; } for(int i = cnt - 1; i < path1.size(); i++) { path.push_back(path1[i]); } reverse(path.begin(), path.end()); for(int i = cnt; i < path2.size(); i++) { path.push_back(path2[i]); }}
翻转二叉树
这里穿插一个小故事,2015 年 6 月 10 日,Homebrew 的作者 Max Howell 在 twitter 上发表了如下一内容:
Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so fuck off.
大概意思就是:他在应聘谷歌的时候被面试官说:虽然有90%的人在用你写的软件,但是你不会翻转二叉树,所以说我们不会录用你.
因为 Max Howell 的影响力,所以这件事情一下子广为流传.
回到正题:翻转二叉树,即是二叉树的镜像,也就是将二叉树的左右子树对调,这里有两种方案;
递归版:
void swapTree(BNode *&root){ BNode *tmp = root->lChild; root->lChild = root->rChild; root->rChild = tmp;}void turnBTree(BNode *rootNode) { if (rootNode == NULL) { return ; } turnBTree(rootNode->lChild); turnBTree(rootNode->rChild); swapTree(rootNode);}
非递归版:
void invertBinaryTreeNonrecursive2(BNode *root) { if(root == NULL){ return ; } stack<BNode *>s; s.push(root); while(!s.empty()) { BNode *tmp = s.top(); s.pop(); swapTree(tmp); if(tmp->lChild) s.push(tmp->lChild); if(tmp->rChild) s.push(tmp->rChild); }}
判断完全二叉树
完全二叉树即叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树
所以可得出判断的两个条件:
- 如果某个节点的右子树不为空,则它的左子树必须不为空
- 如果某个节点的右子树为空,则排在它后面的节点必须没有孩子节点
所以我们可以设置一个标志位flag,当子树满足完全二叉树时,设置flag=YES。当flag=YES而节点又破坏了完全二叉树的条件,那么它就不是完全二叉树。
bool checkIsCompleteBTree(BNode *rootNode) { if (rootNode == NULL) { return true; } queue<BNode *>q; q.push(rootNode); bool flag = false; while(!q.empty()){ BNode *tempNode = q.front(); q.pop(); if (tempNode->lChild == NULL && tempNode->rChild != NULL) { return false; } if (tempNode->lChild == NULL && tempNode->rChild == NULL) { flag = true; } if (flag == true && tempNode->lChild != NULL && tempNode->rChild != NULL) { return false; } if (tempNode->lChild != NULL) { q.push(tempNode->lChild); } if (tempNode->rChild != NULL) { q.push(tempNode->rChild); } } return flag;}
判断平衡二叉树
又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.
递归检查每个节点的左右子树的高度之差是否符合要求,返回即可.
bool checkLR(BNode *rootNode, int &height) { if (rootNode == NULL) { return true; } if (rootNode->value > rootNode->rChild->value || rootNode->value < rootNode->lChild->value) { return false; } bool lAns = checkLR(rootNode->lChild, height); int lHeight = height; bool rAns = checkLR(rootNode->rChild, height); int rHeight = height; height = max(lHeight, rHeight) + 1; if (lAns == true && rAns == true && abs(lHeight - rHeight) <= 1) { return true; } else { return false; }}bool checkBalanceBTree(BNode *rootNode) { int height = 0; return checkLR(rootNode, height);}
- 二叉树<总结一>
- 二叉树<总结二>
- 二叉树的总结
- 二叉树算法总结
- 二叉树总结
- 二叉树题总结
- 二叉树学习总结
- 二叉树<总结一>
- 二叉树<总结二>
- 二叉树总结
- 二叉树性质总结
- 二叉树的总结
- 二叉树应用总结
- 数据结构---二叉树总结
- 二叉树总结
- 二叉树总结_legend
- 二叉树题目总结
- 二叉树总结
- iOS多线程——Dispatch Source 工程广告页有使用以及Iphone X 广告页启动变形
- 动态SQL中使用Open for语句
- ViewPager之setOffscreenPageLimit()解析
- 多项式求和
- 高可用+高并发+负载均衡架构设计
- 二叉树总结
- I
- HDU
- unhandle exception at 0X75c6c54F........exception at memory location 0x0039f628
- mkdocs安装
- 我用Python爬了12万条影评,告诉你《战狼Ⅱ》都在说些啥
- 代码整洁_类
- 求最小公倍数 Java模板
- 问题小结