二叉树
Binary Tree
概念来源:部分来源于参考[1] (这本书知名度很高就不用我多说啦~)
代码来源:部分来源于参考[2](UP真的讲得真的非常详细,像我这种笨蛋都能搞懂,非常推荐~)
1 二叉树的基本概念
1.1 基本概念
「二叉树 binary tree」是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。
二叉树的常用术语如图所示
- 「根节点 root node」:位于二叉树顶层的节点,没有父节点。
- 「叶节点 leaf node」:没有子节点的节点,其两个指针均指向
None
。 - 「边 edge」:连接两个节点的线段,即节点引用(指针)。
- 节点所在的「层 level」:从顶至底递增,根节点所在层为 1 。
- 节点的「度 degree」:节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。
- 二叉树的「高度 height」:从根节点到最远叶节点所经过的边的数量。
- 节点的「深度 depth」:从根节点到该节点所经过的边的数量。
- 节点的「高度 height」:从距离该节点最远的叶节点到该节点所经过的边的数量。
1.2 常见二叉树类型
(1)完美二叉树 [满二叉数]
「完美二叉树 perfect binary tree」所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 0 ,其余所有节点的度都为 2 ;若树的高度为 ℎ ,则节点总数为 2ℎ+1−1 ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。
(2)完全二叉树
「完全二叉树 complete binary tree」只有最底层的节点未被填满,且最底层节点尽量靠左填充。
(3)完满二叉树
「完满二叉树 full binary tree」除了叶节点之外,其余所有节点都有两个子节点。
(4)平衡二叉树
「平衡二叉树 balanced binary tree」中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。
1.3 二叉树退化
当二叉树的每层节点都被填满时,达到“完美二叉树”;而当所有节点都偏向一侧时,二叉树退化为“链表”。
- 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 O(n)。
2 二叉树遍历
2.1 层序遍历
层序遍历 level-order traversal」从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。
层序遍历本质上属于「广度优先遍历 breadth-first traversal」,也称「广度优先搜索 breadth-first search, BFS」,它体现了一种“一圈一圈向外扩展”的逐层遍历方式。
2.2 前序遍历、中序遍历、后序遍历
前序、中序和后序遍历都属于「深度优先遍历 depth-first traversal」,也称「深度优先搜索 depth-first search, DFS」,它体现了一种“先走到尽头,再回溯继续”的遍历方式。
图 7-10 展示了对二叉树进行深度优先遍历的工作原理。深度优先遍历就像是绕着整棵二叉树的外围“走”一圈,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。
2.3 代码实现
#include<bits/stdc++.h>
using namespace std;
struct biTree{
int val;
biTree* left;
biTree* right;
};
//先序创建二叉树
void creatTree(biTree** T){
int n;
cout<<"请输入当前节点的val值,如果输入0表示当前节点为NULL:";
cin>>n;
if(n == NULL){
*T = NULL;
return;
}else{
biTree* node = new biTree;
node->val = n;
*T = node;
creatTree(&((*T)->left));
creatTree(&((*T)->right));
}
}
//先序遍历
void preTree(biTree* T){
if(T == NULL){
return;
}else{
cout<< T->val <<" ";
preTree(T->left);
preTree(T->right);
}
}
//中序遍历
void midTree(biTree* T){
if(T == NULL){
return;
}else{
//中序遍历左子树
midTree(T->left);
//中序遍历中间节点
cout<< T->val << " ";
//右
midTree(T->right);
}
}
//后续遍历
void postTree(biTree* T){
if(T == NULL){
return;
}else{
//
midTree(T->left);
//
midTree(T->right);
//
cout<< T->val << " ";
}
}
int main()
{
biTree* T = NULL;
creatTree(&T);
//pre
cout<<"pre:"<<endl;
preTree(T);
cout<<endl;
//mid
cout<<"mid:"<<endl;
midTree(T);
cout<<endl;
//post
cout<<"post:"<<endl;
postTree(T);
cout<<endl;
return 0;
}
3 二叉搜索树
3.1 基本概念
「二叉搜索树 binary search tree」满足以下条件。
- 对于根节点,左子树中所有节点的值 < 根节点的值 < 右子树中所有节点的值。
- 任意节点的左、右子树也是二叉搜索树,即同样满足条件
1.
。
3.2 代码实现
#include<iostream>
using namespace std;
struct bstTree{
int val;
bstTree* right;
bstTree* left;
};
void insert(bstTree** T, int data){
if(*T == NULL){
bstTree* node = new bstTree;
node->val = data;
node->left = NULL;
node->right = NULL;
*T = node;
}else{
if((*T)->val > data)insert(&((*T)->left), data);
else insert(&((*T)->right), data);
}
}
void midTree(bstTree* T){
if(T == NULL){
return;
}else{
midTree(T->left);
cout<< T->val << " ";
midTree(T->right);
}
}
//3.1递归
bstTree* search(bstTree* T, int data){
//如果是空树
if(T == NULL) return NULL;
//不是空树
//小于当前节点--->左
if(T->val > data) return search(T->left, data);
else if(T->val < data) return search(T->right, data);
else if(T->val == data) return T;
return NULL;
}
//3.2迭代
bstTree* search2(bstTree* T, int data){
bstTree* cur = T;
while(cur != NULL){
if(cur->val < data) cur = cur->right;
else if(cur->val > data) cur = cur->left;
else if(cur->val == data) return cur;
}
return cur; // return NULL;
}
int main()
{
bstTree* T = NULL;
int data;
for(int i = 0; i<5; i++){
cout<<"Please enter ur number:";
cin>>data;
insert(&T, data);
}
midTree(T);
cout<<endl;
cout<<"Please enter the number u want to find:";
cin>>data;
bstTree* node = search(T, data);
if(node == NULL){
cout<<"Not Find."<<endl;
}else{
cout<<"Find:";
cout<< node->val <<endl;
}
return 0;
}
4 删除节点
4.1 怎么样删除一个节点
- 在二叉搜索树的基础上先找到我们需要删除的节点
通过我们传入的值(data)和 节点所指向的值(T->val)进行比较
data > T->val; //往右子树的方向继续查找: T->right = deleteNode(T->right, data);
data < T->val; //往左子树的方向继续查找: T->left = deleteNode(T->left, data);
- 当我们找到了我们需要删除的节点之后 (data == T->val)
我们首先需要去检查该删除节点的左右子树是否为NULL (一共存在四种情况~)
(1)左右都为NULL
(2)仅左为NULL
(3)仅右为NULL
(4)左右都不为NULL
4.1 右子树放到左子树的情况
4.2左子树放到右子树的情况
4.2 代码实现
#include<iostream>
#include<vector>
using namespace std;
struct biTree{
int val;
biTree* right;
biTree* left;
};
void insert(biTree** T, int data){
if(*T == NULL){
biTree* node = new biTree;
node->left = NULL;
node->right = NULL;
node->val = data;
*T = node;
}else{
if((*T)->val < data) insert(&((*T)->right), data);
else insert(&((*T)->left), data);
}
}
void midTree(biTree* T){
if(T == NULL){
return;
}else{
midTree(T->left);
cout<< T->val << " ";
midTree(T->right);
}
}
biTree* deleteNode(biTree* T, int data){
if(T == NULL) return NULL;
//寻找要删除的节点
if(T->val > data){
//小 --> 左
T->left = deleteNode(T->left, data);
return T;
}else if(T->val < data){
// ~
T->right = deleteNode(T->right, data);
return T;
}else if(T->val == data){
//找到要删除的节点啦 ---> 有四种情况
if(T->left == NULL && T->right == NULL){
//1.要删除的节点 左右为 NULL
biTree* temp = T;
delete T;
temp = NULL;
return temp;
} else if(T->left != NULL && T->right == NULL){
//2.要删除的节点 只有右为 NULL ---> 让左边的 val 代替删除的节点
biTree* temp = T->left;
delete T;
return temp;
} else if(T->left == NULL && T->right != NULL){
//3. ~
biTree* temp = T->right;
delete T;
return temp;
} else if(T->left != NULL && T->right != NULL){
//4 左右两边都能取到 val 的情况 ---> 画图理解
//4.1 找到左子树最大的 val 把(删掉节点)右边的值往左子树最大的 val 的右子树上接
// //先找到左子树最大的值
// biTree* cur = T->left;
// while(cur->right != NULL){
// cur = cur->right;
// }
// //把要删除的节点的右子树放在 cur 的右子树上
// cur->right = T->right;
// //释放 T
// biTree* temp = T->left;
// delete T;
// return temp;
//4.2 ~
biTree* cur = T->right;
while(cur->left != NULL){
cur = cur->left;
}
cur->left = T->left;
biTree* temp = T->right;
delete T;
return temp;
}
}
//没有找到要删除的节点
return T;
}
int main()
{
biTree* T = NULL;
int data,num;
for(int i = 0; i<5; i++){
cout << "Please enter ur number:";
cin >> data;
insert(&T, data);
}
cout<< "Before: ";
midTree(T);
cout<<endl;
cout << "Please enter the number u want to delete:";
cin >> num;
biTree* D = deleteNode(T, num);
cout<< "After: ";
midTree(D);
return 0;
}
5 AVL树
平衡因子绝对值在 <= 1
5.1 左旋和右旋
需要满足两个条件:
- 是否为二叉搜索树?
- 每个节点的平衡因子是不是绝对值都小于等于1
- 左旋
左旋代码示例(右旋反之即可):
//定义左旋函数
Node* leftRoate(Node* root){
//1.当前节点的右子树会作为新树的根节点
Node* newroot = root->right;
//T2保存新树根原来的左子树
Node* T2 = newroot->left;
//2.当前节点(root)会作为新树的根节点的左子树(newroot->left)
newroot->left = root;
//如果新的树根原来有左子树,原来的左子树,就作为旧根结点的右子树
root->right = T2; //即使是NULL也没有关系
//更新树高 root newroot
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newroot->height = 1 + max(getHeight(newroot->left), getHeight(newroot->right));
return newroot;
}
- 右旋
5.2 失衡
-
LL型失衡 —>右旋
RR型失衡反之
-
LR型失衡—>先左旋,再右旋
RL型失衡反之
-
举个栗子
5.3 代码实现
#include<stdio.h>
#include<stdlib.h>
//定义树结构
typedef struct Node{
int val; //数据域
int height; //树高
struct Node* right;
struct Node* left;
}Node;
//定义函数生成新的节点,返回值是这个节点的指针
Node* newNode(int val){
Node* node = (Node*)malloc(sizeof(Node));
node->val = val;
node->height = 1;
node->left = NULL;
node->right = NULL;
return node;
}
//获取树的高度
int getHeight(Node* node){
if(node == NULL)
return 0;
return node->height;
}
int max(int a, int b){
return (a>b)? a:b;
}
//定义左旋函数
Node* leftRoate(Node* root){
//1.当前节点的右子树会作为新树的根节点
Node* newroot = root->right;
//T2保存新树根原来的左子树
Node* T2 = newroot->left;
//2.当前节点(root)会作为新树的根节点的左子树(newroot->left)
newroot->left = root;
//如果新的树根原来有左子树,原来的左子树,就作为旧根结点的右子树
root->right = T2; //即使是NULL也没有关系
//更新树高 root newroot
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newroot->height = 1 + max(getHeight(newroot->left), getHeight(newroot->right));
return newroot;
}
//自定义右旋函数
Node* rightRoate(Node* root){
Node* newroot = root->left;
Node* T2 = newroot->right;
newroot->right = root;
root->left = T2;
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newroot->height = 1 + max(getHeight(newroot->left), getHeight(newroot->right));
return newroot;
}
//定义函数获取平衡因子
int getBalance(Node* node){
return getHeight(node->left) - getHeight(node->right);
}
//定义插入节点的函数
Node* insertNode(Node* node, int key){
//传入节点为空创建新节点
if(node == NULL)
return newNode(key);
if(key < node->val)
node->left = insertNode(node->left, key);
else if(key > node->val)
node->right = insertNode(node->right, key);
else
return node;
//更新树高
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
//获取当前节点的平衡因子
int balance = getBalance(node);
//是否需要调整这个数 平衡因子绝对值是否大于 1
//LL型失衡
if(balance > 1 && getBalance(node->left) > 0)
return rightRoate(node);
//LR型失衡
if(balance > 1 && getBalance(node->left) < 0){
node->left = leftRoate(node->left);
return rightRoate(node);
}
//RR型失衡
if(balance < -1 && getBalance(node->right) < 0)
return leftRoate(node);
//RL型失衡
if(balance < -1 && getBalance(node->right) > 0){
node->right = rightRoate(node->right);
return leftRoate(node);
}
return node;
}
//定义先序遍历
void preOrder(Node* root){
if(root == NULL)
return;
printf("%d ", root->val);
preOrder(root->left);
preOrder(root->right);
}
//定义中序遍历
void midOrder(Node* root){
if(root == NULL)
return;
midOrder(root->left);
printf("%d ", root->val);
midOrder(root->right);
}
//定义函数查找 --- counter:记录查找的次数
Node* find(Node* root, int key, int* counter){
Node* cur = root;
while(cur != NULL){
if(key < cur->val){
cur = cur->left;
(*counter)++;
}else if(key > cur->val){
cur = cur->right;
(*counter)++;
}else{
return cur;
}
}
//没找到
return NULL;
}
//删除节点恢复平衡
/*
前半部分:二叉搜索树删除
后半部分:插入节点导致失衡的修改,稍作调整
*/
Node* deleteNode(Node* node, int key){
if(node == NULL)
return node;
if(key < node->val){
node->left = deleteNode(node->left, key);
}else if(key > node->val){
node->right = deleteNode(node->right, key);
}else{
if(node->left == NULL && node->right == NULL){
Node* temp = node;
node = NULL;
free(temp);
}else if(node->left == NULL && node->right != NULL){
Node* temp = node;
node = node->right;
free(temp);
}else if(node->left != NULL && node->right == NULL){
Node* temp = node;
node = node->left;
free(temp);
}else if(node->left != NULL && node->right != NULL){
Node* cur = node->right;
while(cur->left != NULL){
cur = cur->left;
}
node->val = cur->val;
node->right = deleteNode(node->right, cur->val);
}
}
//删除完成,下面开始调整
if(node == NULL)
return node;
//更新树高
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
//获取当前节点的平衡因子
int balance = getBalance(node);
//是否需要调整这个数 平衡因子绝对值是否大于 1
//LL型失衡
if(balance > 1 && getBalance(node->left) >= 0) //特殊情况
return rightRoate(node);
//LR型失衡
if(balance > 1 && getBalance(node->left) < 0){
node->left = leftRoate(node->left);
return rightRoate(node);
}
//RR型失衡
if(balance < -1 && getBalance(node->right) <= 0)
return leftRoate(node);
//RL型失衡
if(balance < -1 && getBalance(node->right) > 0){
node->right = rightRoate(node->right);
return leftRoate(node);
}
return node;
}
//测试用例
void test(){
Node* root = NULL;
root = insertNode(root, 10);
root = insertNode(root, 20);
root = insertNode(root, 30);
root = insertNode(root, 40);
root = insertNode(root, 50);
root = insertNode(root, 60);
root = insertNode(root, 70);
int counter = 0;
Node* result = find(root, 70, &counter);
printf("查找次数:%d\n", counter);
printf("----------先序遍历结果-----------\n");
preOrder(root);
printf("\n----------中序遍历结果-----------\n");
midOrder(root);
printf("\n");
counter = 0;
root = deleteNode(root, 10);
root = deleteNode(root, 20);
root = deleteNode(root, 30);
result = find(root, 40, &counter);
printf("\n删除节点(10 20 30)后 \n查找次数:%d\n", counter);
printf("----------先序遍历结果-----------\n");
preOrder(root);
printf("\n----------中序遍历结果-----------\n");
midOrder(root);
printf("\n");
}
int main()
{
test();
return 0;
}
参考来源
[1] hello-algo.com/chapter_tree/
[2] space.bilibili.com/168886653