概念

1、结点的度:结点拥有的子树个数
2、树的度:树内各结点度的最大值
3、叶结点(终端结点):度为0的结点
4、分支结点:度大于0的结点
5、满二叉树:所有的分支结点都存在左右孩子,左右的叶子结点都在同一层的二叉树
6、完全二叉树:对一棵具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点中位置完全相同,那此树就是一棵满二叉树(结点只能从左到右,从上往下生长)
7、平衡二叉树:如果某二叉树中任意结点的左右子树的深度相差不超过1 ,那么它就是一棵平衡二叉树

性质

1、在二叉树的第i层最多有2^(i-1)个结点
2、深度为k的二叉树最多有2^k-1个结点
3、对任何一颗二叉树,若有n0个叶结点,度为2的结点数位n2,则n0 = n2 + 1
(树枝数 = n0 + n1 + n2 -1 = n1 + 2*n2)
4、具有n个结点的完全二叉树的深度为[log2n] + 1(2为底,n的对数)
5、对一棵有n个结点的完全二叉树按层编号(从0开始):
A.若 i = 0,则结点 i 为二叉树的根;若 i > 0, 则其双亲为[(i-1) / 2]
B.若 2 * i +1 > n,则结点 i 无左孩子,否则其左孩子是结点 2 * i + 1
C.若 2 * i +2 > n,则结点 i 无右孩子,否则其右孩子是结点 2 * i + 2

遍历二叉树

先序遍历:根左右

二叉树及其线索化_二叉树


先序代码:

void PreOrderTraverse(Node* node){
if(node == NULL) return ;
cout<<node->data<<" ";
PreOrderTraverse(node->lchild);
PreOrderTraverse(node->rchild);
}

中序遍历:左根右

二叉树及其线索化_结点_02


中序代码:

void InOrderTraverse(Node* node){
if(node == NULL) return ;
InOrderTraverse(node->lchild);
cout<<node->data;
InOrderTraverse(node->rchild);
}

后序遍历:左右根

二叉树及其线索化_结点_03


后序代码:

void PostOrderTraverse(Node* node){
if(node == NULL) return ;
PostOrderTraverse(node->lchild);
PostOrderTraverse(node->rchild);
cout<<node->data;
}

层次遍历:从上到下,从左到右

二叉树及其线索化_子树_04

1、把根节点A放入队列,此时队列为:A,队列头指针指向A,也就是队列第一个元素

2、把当前队列头指针所指元素的左右儿子放入队列,即将B C放入队列,此时队列为A B C ,队列头指针向后移一格,此时指向B

3、不断重复2步骤。此时把B的左右儿子取出来放入队尾,队列变为A B C D,
队列头指针后移,指向C。把C的左右儿子取出来放入队尾,队列变为A B C D E F
队列头指针后移,指向D。把D的左右儿子取出来放入队尾,队列变为A B C D E F G H
队列头指针后移,指向E。把E的左右儿子取出来放入队尾,队列变为A B C D E F G H I
4、结束条件,队列头指针和为指针重合时,输出最后一个元素,算法结束!

也就是说,把这个队列从头到尾输出一遍,就是按层遍历,这个队列是动态的,只要有子节点,子节点就会不停的加入队尾,但总有子节点没有的时候,所以,队列尾指针肯定有不再移动的时候,而头指针一直在一步一步向下移,总会有首尾指针重合的时候,即标志着算法结束。

层次遍历代码:

void LevelOrderTraverse(Node* root){
if(root == NULL) return ;
queue<Node*> q;
q.push(root);
while(!q.empty()){
cout<<q.front()->data<<" ";
if(q.front()->lchild != NULL){
q.push(q.front()->lchild);
}
if(q.front()->rchild != NULL){
q.push(q.front()->rchild);
}
q.pop();
}
cout<<endl;
}

注:已知中序遍历和先序遍历结果,或已知中序遍历和后序遍历结果可以还原二叉树

二叉树深度

最简单的方式就是使用递归的方式来遍历二叉树,在递归的处理函数中逐渐增加二叉树的深度

int getDeepth(Node* node){
if(node == NULL) return 0;
int left = getDeepth(node->lchild);
int right = getDeepth(node->rchild);
return left>right ? left+1 : right+1;
}

图解(深度为3):

二叉树及其线索化_二叉树_05

注:‘#’ 表示此结点为空,向调用处返回0,实际的二叉树如下

二叉树及其线索化_子树_06

判断平衡二叉树

递归地检查二叉树中所有结点的左右子树的深度之差,若都相差不超过1 ,那么它就是一棵平衡二叉树

bool isBalanced(Node* node){
if(node == NULL) return true;
int left = getDeepth(node->lchild);
int right = getDeepth(node->rchild);
int gap = abs(left - right);
//对于当前结点而言,左右子树的深度之差大于1,不平衡
if(gap > 1)
return false;
//对于当前结点而言,左右子树的深度只差不大于1,平衡
//接着检查其左右子树是否平衡
return isBalanced(node->lchild) && isBalanced(node->rchild);
}

构造二叉搜索树的insert操作

Node* insert(Node* node, int num){
if(node == nullptr){
node = new Node(num);//当前结点为空,直接构造结点挂上
}else if(node->val > num){
node->left = insert(node->left, num);
}else if(node->val < num){
node->right = insert(node->right, num);
}
//相等直接返回
return node;
}

将有序数组转为二叉搜索树

class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return getBST(nums, 0, nums.size() - 1);
}
TreeNode* getBST(vector<int>& nums, int l, int r){
if(l > r){
return nullptr;
}
int mid = (l + r) / 2;
TreeNode* node = new TreeNode(nums[mid]);
node->left = getBST(nums, l, mid - 1);
node->right = getBST(nums, mid + 1, r);
return node;
}
};

面向过程完整代码:

#include<iostream>
#include<queue>

using namespace std;

struct Node{
char data;
Node* lchild;
Node* rchild;
}Node;

//按先序输入二叉树结点的值
//根据输入序列创建二叉树
//用双指针是为了将根结点传入,执行函数完成后,还能通过此根结点找到树
void CreateTree(Node** node){
char temp;
cin>>temp;
if(temp == '#')
*node = NULL;
else{
*node = (Node*)malloc(sizeof(Node));
(*node)->data = temp;
CreateTree(&((*node)->lchild));
CreateTree(&((*node)->rchild));
}
}

//先序遍历
void PreOrderTraverse(Node* node){
if(node == NULL) return ;
cout<<node->data<<" ";
PreOrderTraverse(node->lchild);
PreOrderTraverse(node->rchild);
}

//中序遍历
void InOrderTraverse(Node* node){
if(node == NULL) return ;
InOrderTraverse(node->lchild);
cout<<node->data;
InOrderTraverse(node->rchild);
}

//后序遍历
void PostOrderTraverse(Node* node){
if(node == NULL) return ;
PostOrderTraverse(node->lchild);
PostOrderTraverse(node->rchild);
cout<<node->data;
}

void LevelOrderTraverse(Node* root){
if(root == NULL) return ;
queue<Node*> q;
q.push(root);
while(!q.empty()){
cout<<q.front()->data<<" ";
if(q.front()->lchild != NULL){
q.push(q.front()->lchild);
}
if(q.front()->rchild != NULL){
q.push(q.front()->rchild);
}
q.pop();
}
cout<<endl;
}

int getDeepth(Node* node){
if(node == NULL) return 0;
int left = getDeepth(node->lchild);
int right = getDeepth(node->rchild);
return left>right ? left+1 : right+1;
}

bool isBalanced(Node* node){
if(node == NULL) return true;
int left = getDeepth(node->lchild);
int right = getDeepth(node->rchild);
int gap = abs(left - right);
//对于当前结点而言,左右子树的深度之差大于1,不平衡
if(gap > 1)
return false;
//对于当前结点而言,左右子树的深度只差不大于1,平衡
//接着检查其左右子树是否平衡
return isBalanced(node->lchild) && isBalanced(node->rchild);
}

int main(){
Node* root = (Node*)malloc(sizeof(Node));
//测试数据AB#D##C##
cout<<"输入数据:";
CreateTree(&root);
cout<<"先序遍历结果:";
PreOrderTraverse(root);
cout<<endl;
cout<<"中序遍历结果:";
InOrderTraverse(root);
cout<<endl;
cout<<"后序遍历结果:";
PostOrderTraverse(root);
cout<<endl;
cout<<"层次遍历结果:";
LevelOrderTraverse(root);
cout<<"二叉树的深度:";
cout<<getDeepth(root)<<endl;
cout<<"是平衡二叉树吗?"<<isBalanced(root)<<endl;
return 0;
}

运行结果:

二叉树及其线索化_结点_07

注:创建结果如上图(二叉树深度部分的图)

面向对象版完整代码

#include<iostream>
#include<queue>

using namespace std;

typedef struct Node{
char data;
struct Node* lchild;
struct Node* rchild;
}Node;

class Tree{
private:
Node* root;
int tree_deepth;
//先序
void Pre(Node* node){
if(node == NULL) return ;
cout<<node->data<<" ";
Pre(node->lchild);
Pre(node->rchild);
}
//中序
void In(Node* node){
if(node == NULL) return ;
In(node->lchild);
cout<<node->data;
In(node->rchild);
}
//后序
void Post(Node* node){
if(node == NULL) return ;
In(node->lchild);
In(node->rchild);
cout<<node->data;
}
//层次
void Level(Node* node){
if(root == NULL) return ;
queue<Node*> q;
q.push(root);
while(!q.empty()){
cout<<q.front()->data<<" ";
if(q.front()->lchild != NULL){
q.push(q.front()->lchild);
}
if(q.front()->rchild != NULL){
q.push(q.front()->rchild);
}
q.pop();
}
cout<<endl;

}

void CreateTree(Node** node){
char temp;
cin>>temp;
if(temp == '#')
*node = NULL;
else{
*node = (Node*)malloc(sizeof(Node));
(*node)->data = temp;
CreateTree(&((*node)->lchild));
CreateTree(&((*node)->rchild));
}
}

int Deepth(Node* node){
if(node == NULL) return 0;
int left = Deepth(node->lchild);
int right = Deepth(node->rchild);
return left>right ? left+1 : right+1;
}

bool Balanced(Node* node){
if(node == NULL) return true;
int left = Deepth(node->lchild);
int right = Deepth(node->rchild);
int gap = abs(left - right);
//对于当前结点而言,左右子树的深度之差大于1,不平衡
if(gap > 1)
return false;
//对于当前结点而言,左右子树的深度只差不大于1,平衡
//接着检查其左右子树是否平衡
return Balanced(node->lchild) && Balanced(node->rchild);
}
public:
Tree(){
root = (Node*)malloc(sizeof(Node*));
cout<<"输入数据:";
CreateTree(&root);
}
void PreOrderTraverse(){
Pre(root);
}

void InOrderTraverse(){
In(root);
}

void PostOrderTraverse(){
Post(root);
}

void LevelOrderTraverse(){
Level(root);
}

void CountDeepth(){
tree_deepth = Deepth(root);
}

int getDeepth(){
CountDeepth();
return tree_deepth;
}

bool isBalanced(){
return Balanced(root);
}
};

int main(){
//AB#D##C##
Tree tree;
cout<<"先序遍历结果:";
tree.PreOrderTraverse();
cout<<"中序遍历结果:";
tree.InOrderTraverse();
cout<<"后序遍历结果:";
tree.PostOrderTraverse();
cout<<endl;
cout<<"层次遍历结果:";
tree.LevelOrderTraverse();
cout<<"树的深度为:"<<tree.getDeepth()<<endl;
cout<<"是平衡二叉树吗?"<<tree.isBalanced()<<endl;
return 0;
}

线索二叉树

将普通二叉树线索化成中序线索二叉树:

二叉树及其线索化_子树_08


线索二叉树的数据结构:

二叉树及其线索化_结点_09


中序线索二叉树的存储结构:

二叉树及其线索化_子树_10

将普通二叉树线索化成先序线索二叉树:

二叉树及其线索化_子树_11


先序线索二叉树的存储结构:

二叉树及其线索化_子树_12


将普通二叉树线索化成后序线索二叉树:

二叉树及其线索化_结点_13


后序线索二叉树的存储结构:

二叉树及其线索化_结点_14

用两个指针,在中序遍历的同时,将二叉树中序线索化:

二叉树及其线索化_二叉树_15


中序线索化实现代码:

二叉树及其线索化_二叉树_16

二叉树及其线索化_结点_17


先序线索化实现代码:

二叉树及其线索化_结点_18

后序线索化实现代码:

二叉树及其线索化_二叉树_19


注: 只有构造先序线索二叉树时,才需要判断左指针是否是真实的结点。因为先序遍历的顺序是【根,左,右】,访问根结点后可能会线索化左指针。改变左指针后,马上再接着访问当前根结点的左孩子,就会访问到当前根结点的父结点,然后又访问回来…,这样死循环下去。

别的顺序线索化二叉树,不需要判断左指针是否是真实的结点。中序线索化时,先访问左孩子,由于左孩子线索化后,会接着访问根结点,不会再往下层访问,所以线索化对中序遍历没有影响。后序线索化同样的分析。

中序线索化二叉树完整代码:

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>

using namespace std;

struct TreeNode {
char val;
TreeNode *left;
TreeNode *right;
int ltag;
int rtag;
TreeNode(char x) : val(x), left(nullptr), right(nullptr), ltag(0), rtag(0) {

}
};

TreeNode* buildTree(vector<char>& preorder, vector<char>& inorder) {
if(preorder.size() == 0){
return nullptr;
}
TreeNode* root = new TreeNode(preorder[0]);
auto mid = find(inorder.begin(), inorder.end(), preorder[0]); //指向根结点值的迭代器
char left_nodes = mid - inorder.begin(); //左子树结点数目
vector<char> left_preorder(preorder.begin()+1, preorder.begin()+1+left_nodes);
vector<char> right_preorder(preorder.begin()+1+left_nodes, preorder.end());
vector<char> left_inorder(inorder.begin(), mid);
vector<char> right_inorder(mid+1, inorder.end());
root->left = buildTree(left_preorder, left_inorder);
root->right = buildTree(right_preorder, right_inorder);
return root;
}

void inVisit(TreeNode* cur, TreeNode* &pre){
if(cur->left == nullptr){
cur->left = pre;
cur->ltag = 1;
}
if(pre != nullptr && pre->right == nullptr){
pre->right = cur;
pre->rtag = 1;
}
pre = cur;
}

void inThread(TreeNode* root, TreeNode* &pre){
if(root != nullptr){
inThread(root->left, pre);
inVisit(root, pre);
inThread(root->right, pre);
}
}

TreeNode* getInThreadTree(TreeNode* root){
TreeNode* pre = nullptr;
if(root != nullptr){
inThread(root, pre);
if(pre->right == nullptr){
pre->rtag = 1; //处理遍历的最后一个结点
}
}
return root;
}

int main(){
vector<char> preorder = {'A', 'B', 'D', 'G', 'E', 'C', 'F'};
vector<char> inorder = {'D', 'G', 'B', 'E', 'A', 'F', 'C'};
TreeNode* root = buildTree(preorder, inorder); //构造上图的二叉树
TreeNode* inThreadTree = getInThreadTree(root);
cout<<inThreadTree->right->left->right->val<<endl; //验证线索化是否正确
return 0;
}

树的孩子兄弟表示法:

二叉树及其线索化_二叉树_20


二叉树及其线索化_子树_21

森林与二叉树相互转换:

二叉树及其线索化_二叉树_22


先将每棵树按照孩子兄弟表示法转换为二叉树,然后把C、D当作B的兄弟结点,挂到B的后子树上,就像这样:

二叉树及其线索化_子树_23

将二叉树按照孩子兄弟表示法还原为森林:

二叉树及其线索化_二叉树_24

树的先根遍历

先访问根,在从左往后访问子树

二叉树及其线索化_子树_25

树的后根遍历

先从左往后访问子树,最后访问根

二叉树及其线索化_二叉树_26


因为树中,不管是左孩子还是右孩子对应到孩子兄弟表示法的二叉树中,都是左孩子。所以树中的【左、右、根】对应成了二叉树中的【左、根】,换到下一颗树遍历时,二叉树就变成了右孩子,故 树的后序遍历 = 对应二叉树的中序遍历

树的层次遍历

二叉树及其线索化_结点_27

森林的遍历
  1. 森林的先序遍历 = 从左到右对每棵树先序遍历 = 对应二叉树的先序遍历
  2. 森林的中序遍历 = 从左到右对每棵树后序遍历 = 对应二叉树的中序遍历