目录
题目二 如何完成二叉树的宽度(也叫层序)遍历(常见题目:求一棵二叉树的宽度)
题目七 给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点
关于二叉树的一些笔记。
题目一 二叉树递归和非递归遍历
递归序
void RecurTree(Node *head){
if (head == nullptr){
return;
}
preOrderRecur(head -> left);
preOrderRecur(head -> right);
}
递归序定义:上面的递归代码运行时,如果什么都不做,每个节点被访问的顺序。
以上二叉树的递归序为:1, 2, 4, 4, 4, 2, 5, 5, 5, 2, 1, 3, 6, 6, 6, 3, 7, 7, 7, 3, 1
基于递归序,对二叉树的遍历分为先序遍历,中序遍历和后续遍历。
先序遍历:1, 2, 4, 5, 3, 6, 7(选取第一次出现的节点值,即第一次到达节点的时候打印,二、三次到达节点时什么也不做。)
中序遍历:4, 2, 5, 1, 6, 3, 7(选取第二次出现的节点值,即第二次到达节点的时候打印,一、三次到达节点时什么也不做。)
后序遍历:4, 5, 2, 6, 7, 3, 1(选取第三次出现的节点值,即第三次到达节点的时候打印,一、二次到达节点时什么也不做。)
以下为先序遍历、中序遍历和后序遍历的非递归实现原理。方法:借助栈。
先序遍历:
① 把头节点压入栈中;
② 从栈中弹出一个节点cur;
③ 打印(处理)cur;
④ 先后压右、左节点入栈(如果有的话);
⑤ 重复② - ④步。
中序遍历:
① 每棵子树,整棵树左边界进栈;
② 依次弹出节点的过程中打印,然后对弹出节点的右树重复以上过程;
中序遍历能使用上面方法遍历的原因,任何一棵树都可以被左边界分解掉。
后序遍历:一个栈和一个收集栈
① 把头节点压入栈中;
② 从栈中弹出一个节点cur;
③ cur压入收集栈;
④ 先后压左、右节点入栈(如果有的话);
⑤ 重复② - ④步;
⑥ 打印收集栈。
具体代码如下:
#include <iostream>
#include <stack>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
//递归
//先序遍历
void preOrderRecur(Node *head){
if (head == nullptr){
return;
}
cout << head -> val << " ";
preOrderRecur(head -> left);
preOrderRecur(head -> right);
}
//中序遍历
void inOrderRecur(Node *head){
if (head == nullptr){
return;
}
inOrderRecur(head -> left);
cout << head -> val << " ";
inOrderRecur(head -> right);
}
//后序遍历
void posOrderRecur(Node *head){
if (head == nullptr){
return;
}
posOrderRecur(head -> left);
posOrderRecur(head -> right);
cout << head -> val << " ";
}
//非递归,借助栈
//先序遍历
void preOrderUnRecur(Node *head){
if (head != nullptr){
stack<Node*> helper;
helper.push(head);
while(!helper.empty()){
head = helper.top();
cout << head -> val << " ";
helper.pop();
if (head -> right != nullptr){
helper.push(head -> right);
}
if (head -> left != nullptr){
helper.push(head -> left);
}
}
}
}
//中序遍历
void inOrderUnRecur(Node *head){
if (head != nullptr){
stack<Node*> helper;
while(!helper.empty() || head != nullptr){
if (head != nullptr){
helper.push(head);
head = head -> left;
}else {
head = helper.top();
cout << head -> val << " ";
helper.pop();
head = head -> right;
}
}
}
}
//后序遍历
void posOrderUnRecur(Node *head){
if (head != nullptr){
stack<Node*> helper1;
stack<Node*> helper2;
helper1.push(head);
while(!helper1.empty()){
head = helper1.top();
helper2.push(head);
helper1.pop();
if (head -> left != nullptr){
helper1.push(head -> left);
}
if (head -> right != nullptr){
helper1.push(head -> right);
}
}
while (!helper2.empty()){
cout << helper2.top() -> val << " ";
helper2.pop();
}
}
}
int main(){
Node *head = new Node(5);
head -> left = new Node(3);
head -> right = new Node(8);
head -> left -> left = new Node(2);
head -> left -> right = new Node(4);
head -> left -> left -> left = new Node(1);
head -> right -> left = new Node(5);
head -> right -> right = new Node(9);
head -> right -> right -> right = new Node(7);
preOrderRecur(head);
cout << endl;
preOrderUnRecur(head);
cout << endl;
inOrderRecur(head);
cout << endl;
inOrderUnRecur(head);
cout << endl;
posOrderRecur(head);
cout << endl;
posOrderUnRecur(head);
cout << endl;
system("pause");
}
题目二 如何完成二叉树的宽度(也叫层序)遍历(常见题目:求一棵二叉树的宽度)
层序遍历借助队列,头部进,尾部出。
① 先把头节点放入队列;
② 然后弹出就打印;
③ 弹出时先后放入该节点的左、右节点。
④ 循环② -> ③
代码如下:
#include <iostream>
#include <queue>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
void levelTraversal(Node *head){
if (head != nullptr){
priority_queue<Node*> helper;
helper.push(head);
while(!helper.empty()){
head = helper.top();
cout << head -> val << " ";
helper.pop();
if (head -> left != nullptr){
helper.push(head -> left);
}
if (head -> right != nullptr){
helper.push(head -> right);
}
}
}
}
int main(){
Node *head = new Node(5);
head -> left = new Node(3);
head -> right = new Node(8);
head -> left -> left = new Node(2);
head -> left -> right = new Node(4);
head -> right -> left = new Node(5);
head -> right -> right = new Node(9);
levelTraversal(head);
cout << endl;
system("pause");
}
现在来看求二叉树的宽度
借助哈希表,记录每个节点所在的层数,然后统计层数最大的,代码如下:
#include <iostream>
#include <map>
#include <queue>
#include <algorithm>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
int getMaxwidth(Node *head){
if (head == nullptr){
return 0;
}
int maxWidth = 0;
int curWidth = 0;
int curLevel = 0;
//记录每个节点对应层数的哈希表
map<Node*, int> levelMap;
levelMap.insert({head, 1});
//借助队列实现层序遍历
queue<Node*> helper;
helper.push(head);
while (!helper.empty()){
head = helper.front();
helper.pop();
if (head -> left != nullptr){
helper.push(head -> left);
levelMap.insert({head -> left, levelMap[head] + 1});
}
if (head -> right != nullptr){
helper.push(head -> right);
levelMap.insert({head -> right, levelMap[head] + 1});
}
if (levelMap[head] > curLevel){
curWidth = 1;
curLevel = levelMap[head];
}else {
curWidth++;
}
maxWidth = max(maxWidth, curWidth);
}
return maxWidth;
}
int main(){
Node *head = new Node(5);
head -> left = new Node(3);
head -> right = new Node(8);
head -> left -> left = new Node(2);
head -> left -> right = new Node(4);
head -> left -> left -> left = new Node(1);
head -> left -> left -> right = new Node(1);
head -> left -> right -> left = new Node(4);
head -> left -> right -> right = new Node(4);
head -> right -> left = new Node(5);
head -> right -> right = new Node(9);
head -> right -> right -> right = new Node(7);
int max = getMaxwidth(head);
cout << max << endl;
system("pause");
}
题目四 如何判断一棵二叉树是搜索二叉树(BST)?
搜索二叉树定义:如果一棵树不为空,并且如果它的根节点左子树不为空,那么它左子树上面的所有节点的值都小于它的根节点的值,如果它的右子树不为空,那么它右子树任意节点的值都大于他的根节点的值,它的左右子树也是搜索二叉树。
解法一:中序遍历后是升序的就是搜索二叉树,将中序遍历的值存入到队列中,在判断是不是升序。对应代码中的方法1;
解法二:使用递归:
① 左子树是搜索二叉树;
② 左子树是搜索二叉树;
③ 左树max < x;
④ 右树min > x;
左树需要的信息:是不是搜索二叉树?max值?
右树需要的信息:是不是搜索二叉树?min值?
左右树均需返回的信息:是不是搜索二叉树?max值?min值?
对应代码中的方法2。
#include <iostream>
#include <vector>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
//中序遍历
void inOrderTravesal(Node *root, vector<Node*> &helper){
if (root == nullptr){
return;
}
inOrderTravesal(root -> left, helper);
helper.insert(helper.end(), root);
inOrderTravesal(root -> right, helper);
}
bool isBST(Node *root){
if (root == nullptr){
return true;
}
vector<Node*> helper;
inOrderTravesal(root, helper);
int pre = INT_MIN;
for (auto c : helper){
if (pre >= c -> val){
return false;
}
pre = c -> val;
}
return true;
}
int preValue = INT_MIN;
bool isBST2(Node *root){
if (root == nullptr){
return true;
}
bool isLeftBST = isBST2(root -> left);
if (!isLeftBST){
return false;
}
if (root -> val <= preValue){
return false;
}else {
preValue = root -> val;
}
return isBST2(root -> right);
}
int main(){
Node *root = new Node(6);
root -> left = new Node(4);
root -> right = new Node(9);
root -> left -> left = new Node(3);
root -> left -> right = new Node(5);
root -> right -> left = new Node(8);
root -> right -> right = new Node(10);
cout << boolalpha << isBST(root) << endl;
cout << boolalpha << isBST2(root) << endl;
system("pause");
}
树型DP代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
//需要知道左右子树是不是BST,左右子树的最大,最小值
struct ReturnData{
bool isBST;
int min;
int max;
ReturnData(bool is, int mi, int ma) : isBST(is), min(mi), max(ma) {}
};
ReturnData* process(Node *x){
if (x == nullptr){
return nullptr;
}
ReturnData *leftData = process(x -> left);
ReturnData *rightData = process(x -> right);
int minData = x -> val;
int maxData = x -> val;
if (leftData != nullptr){
minData = min(minData, leftData -> min);
maxData = max(maxData, leftData -> max);
}
if (rightData != nullptr){
minData = min(minData, rightData -> min);
maxData = max(maxData, rightData -> max);
}
bool isBST = true;
if (leftData != nullptr && (!leftData -> isBST || leftData -> max >= x -> val)){
isBST = false;
}
if (rightData != nullptr && (!rightData -> isBST || rightData -> min <= x -> val)){
isBST = false;
}
return new ReturnData(isBST, minData, maxData);
}
int main(){
Node *root = new Node(6);
root -> left = new Node(4);
root -> right = new Node(9);
root -> left -> left = new Node(3);
root -> left -> right = new Node(5);
root -> right -> left = new Node(8);
root -> right -> right = new Node(10);
cout << boolalpha << process(root) -> isBST << endl;
cout << process(root) -> min << endl;
cout << process(root) -> max << endl;
system("pause");
}
题目四 如何判断一棵二叉树是平衡二叉树(AVL)?
首先了解一下平衡二叉树的定义:平衡二叉树(AVL树),它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
套用树型DP模板,需要知道的信息为:
① 左树是平衡二叉树;
② 右树是平衡二叉树;
③ 丨左高 - 右高丨 1
左树需要的信息:是不是平衡二叉树?高度?
右树需要的信息:是不是平衡二叉树?高度?
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
struct ReturnData{
bool isAVL;
int height;
ReturnData(bool is, int h) : isAVL(is), height(h){}
};
ReturnData* process(Node *root){
if (root == nullptr){
return new ReturnData(true, 0);
}
ReturnData *leftData = process(root -> left);
ReturnData *rightData = process(root -> right);
int height = max(leftData -> height, rightData -> height) + 1;
bool isAVL = leftData -> isAVL && rightData -> isAVL
&& abs(leftData -> height - rightData -> height) < 2;
return new ReturnData(isAVL, height);
}
bool isAVL(Node *root){
return process(root) -> isAVL;
}
int main(){
Node *root = new Node(6);
root -> left = new Node(4);
root -> right = new Node(9);
root -> left -> left = new Node(3);
root -> left -> right = new Node(5);
// root -> right -> left = new Node(8);
// root -> right -> right = new Node(10);
cout << boolalpha << isAVL(root) << endl;
system("pause");
}
题目五 如何判断一棵二叉树是满二叉树(FBT)?
满二叉树定义:树中除了叶子节点,每个节点都有两个子节点。
解题方法: 先统计这棵树的最大深度L,然后统计这棵树的节点个数N。若是满二叉树需满足关系。
① 左树是满二叉树;
② 右树是满二叉树;
③ 左树的信息:是不是满二叉树?深度?节点数?
④ 右树的信息:是不是满二叉树?深度?节点数?
#include <iostream>
#include <algorithm>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
struct ReturnData{
int deep;
int nodes;
ReturnData(int d, int n) : deep(d), nodes(n) {}
};
ReturnData* process(Node *root){
if (root == nullptr){
return new ReturnData(0, 0);
}
ReturnData *leftData = process(root -> left);
ReturnData *rightData = process(root -> right);
int dee = max(leftData -> deep, rightData -> deep) + 1;
int nod = leftData -> nodes + rightData -> nodes + 1;
return new ReturnData(dee, nod);
}
bool isCBT(Node *root){
ReturnData *data = process(root);
return data -> nodes == (1 << data -> deep) - 1;
}
int main(){
Node *root = new Node(6);
root -> left = new Node(4);
root -> right = new Node(9);
root -> left -> left = new Node(3);
root -> left -> right = new Node(5);
root -> right -> left = new Node(8);
// root -> right -> right = new Node(10);
cout << boolalpha << isCBT(root) << endl;
system("pause");
}
题目六 如何判断一棵二叉树是完全二叉树(CBT)?
完全二叉树定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
解决:二叉树层序遍历
① 遇到的任何一个节点有右节点没有左节点,直接返回false;
② 在①不违规的条件下,如果遇到第一个左右节点不双全的节点,后续节点必须是都是叶节点,则为完全二叉树,否则该二叉树不是完全二叉树。
#include <iostream>
#include <queue>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
bool isCBT(Node *root){
if (root == nullptr){
return true;
}
queue<Node*> helper;
bool leafNotFull = false;//是否遇到子节点不双全的情况,是为true,不是为false
helper.push(root);
while(!helper.empty()){
Node *cur = helper.front();
helper.pop();
Node *l = cur -> left;
Node *r = cur -> right;
//有右节点,无左节点 => false;遇到了不双全的节点之后,又发现当前节点不是叶节点 => false
if ((l == nullptr && r != nullptr) ||(leafNotFull && (l != nullptr || r != nullptr))){
return false;
}
if (l != nullptr){
helper.push(l);
}
if (r != nullptr){
helper.push(r);
}else {
leafNotFull = true;
}
}
return true;
}
int main(){
Node *root = new Node(6);
root -> left = new Node(4);
root -> right = new Node(9);
//root -> left -> left = new Node(3);
//root -> left -> right = new Node(5);
root -> right -> left = new Node(8);
root -> right -> right = new Node(10);
cout << boolalpha << isCBT(root) << endl;
system("pause");
}
题目七 给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点
方法一:借助哈希表。
① 在哈希表中存储每个节点和节点的父节点;
② 查找node1节点的所有父节点,存储在set结构的set1中;
③ 依次查找node2节点的父节点并判断查找到的节点是否在set1中,若存在,当前节点为最低公共祖先,不存在返回根节点。
如代码中方法1所示。
方法2: 递归
o1和o2的关系只有下面两类:
① o1是o2的LCA,或者o2是o1的LCA;
② o1与o2不互为LCA,向上遍历树找到;
如代码中方法2所示。
#include <iostream>
#include <map>
#include <set>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node() : val(0), left(nullptr), right(nullptr) {}
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
void process(Node *root, map<Node*, Node*> &fatherMap){
if (root == nullptr){
return;
}
fatherMap.insert({root -> left, root});
fatherMap.insert({root -> right, root});
process(root -> left, fatherMap);
process(root -> right, fatherMap);
}
//方法1
Node* lowestCommonAncestor1(Node *root, Node *o1, Node *o2){
map<Node*, Node*> fatherMap;
fatherMap.insert({root, root});
process(root, fatherMap);
set<Node*> set1;
Node *cur = o1;
while (cur != fatherMap[cur]){
set1.insert(cur);
cur = fatherMap[cur];
}
set1.insert(root);
cur = o2;
while(cur != fatherMap[cur]){
if (set1.count(cur)){
return cur;
}
cur = fatherMap[cur];
}
return root;
}
//方法2
Node* lowestCommonAncestor2(Node *root, Node *o1, Node *o2){
if (root == nullptr || root == o1 || root == o2){
return root;
}
Node *left = lowestCommonAncestor2(root -> left, o1, o2);
Node *right = lowestCommonAncestor2(root -> right, o1, o2);
if (left != nullptr && right != nullptr){
return root;
}
//左右两棵树,并不都有返回值,谁不空返回谁
return left != nullptr? left : right;
}
//test
int main(){
Node *root = new Node(6);
root -> left = new Node(4);
root -> right = new Node(9);
root -> left -> left = new Node(3);
root -> left -> right = new Node(5);
root -> right -> left = new Node(8);
root -> right -> right = new Node(10);
Node *o1 = root -> left -> left;
Node *o2 = root -> left -> right;
cout << o1 -> val << ", " << o2 -> val <<": " << endl;
cout << lowestCommonAncestor1(root, o1, o2) -> val << endl;
cout << lowestCommonAncestor2(root, o1, o2) -> val << endl;
system("pause");
}
题目八 在二叉树中找到一个节点的后继节点。
后继节点的定义: 在二叉树的中序遍历序列中,node的下一个节点叫做node的后继节点。
分以下几种情况:
① x有右树的时候:后继节点为右树上的最左节点;
② x无右树的时候:依次向上遍历父节点,直到找到的父节点是其父节点y的左节点,因为这时x是y左树上最后一个打印的节点,下一个必定打印y;
③ x为整棵树最右的节点,此时x的后继节点为nullptr。
代码如下:
#include <iostream>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node *parent;
Node(int x) : val(x), left(nullptr), right(nullptr), parent(nullptr) {}
};
//获取node节点的最左节点
Node* getMostLeft(Node * node){
if (node == nullptr){
return node;
}
Node *cur = nullptr;
while (node != nullptr){
cur = node;
node = node -> left;
}
return cur;
}
Node* getSuccessorNode(Node* node){
if (node == nullptr){
return node;
}
if (node -> right != nullptr){
return getMostLeft(node -> right);
}else {
Node *nodeParent = node -> parent;
while(nodeParent != nullptr && nodeParent -> left != node){
node = nodeParent;
nodeParent = node -> parent;
}return nodeParent;
}
return nullptr;
}
//test
int main(){
Node *root = new Node(1);
root -> parent = nullptr;
root -> left = new Node(2);
root -> left -> parent = root;
root -> right = new Node(3);
root -> right -> parent = root;
root -> right -> left = new Node(6);
root -> right -> left -> parent = root -> right;
root -> right -> right = new Node(7);
root -> right -> right -> parent = root -> right;
cout << getSuccessorNode(root) -> val << endl;
system("pause");
}
题目九 二叉树的序列化和反序列化
使用先序遍历序列化和反序列化的代码如下:
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
struct Node{
int val;
Node *left;
Node *right;
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
string serializeByPre(Node *root){
if (root == nullptr){
return "# ";
}
string res = to_string(root -> val) + " ";
res += serializeByPre(root -> left);
res += serializeByPre(root -> right);
return res;
}
Node* myDeserialize(istringstream &ans){
string tmp;
ans >> tmp;
if (tmp == "#"){
return nullptr;
}
Node *root = new Node(stoi(tmp));
root -> left = myDeserialize(ans);
root -> right = myDeserialize(ans);
return root;
}
Node* deserialize(string preStr){
istringstream tmp(preStr);
return myDeserialize(tmp);
}
题目十 折纸问题
解题思路:每折一次都会在上一次的折痕两边添加两道新的折痕,上边的折痕为凹(down)折痕,下边的折痕为凸(up)折痕。因此基于中序遍历即可解决问题。
代码如下:
#include <iostream>
#include <string>
using namespace std;
//递归过程,来到了某一个节点
//i是当前节点的层数,N一共的层数,down == true ==> 凹折痕, down == false ==> 凸折痕
void printProcess(int i, int N, bool down){
if (i > N){
return;
}
printProcess(i + 1, N, true);
string fold = down ? "down" : "up";
cout << fold <<" ";
printProcess(i + 1, N, false);
}
void printAllFolds(int N){
printProcess(1, N, true);
}
int main(){
cout << "Please Enter N: ";
int N;
cin >> N;
printAllFolds(N);
cout << endl;
system("pause");
}