文章目录

  • 1.概念和性质
    • 1.1.树
    • 1.2.完全二叉树(complete binary tree)
  • 2.二叉树
    • 2.1.建树
    • 2.2.二叉树的静态表示
    • 2.3. 二叉树遍历
    • 2.4.几种遍历之间的转换
  • 3.一般意义的树的遍历
    • 3.1.深度优先搜索(DFS)与先根遍历
  • 3.2.广度优先搜索(BFS)与层序遍历
  • 3.3.相关题目
  • 4.二叉搜索树
    • 4.1.性质
    • 4.2.相关题目
  • 5.AVL树
    • 5.1 概念
    • 5.2调整方法
    • 5.3 树型
    • 5.5 计算平衡因子
    • 5.4 插入算法

1.概念和性质

1.1.树

(1)树可以没有结点,这种情况树称为空树。
(2)满足连通、边数等于顶点数减1的结构一定是一棵树。
(3)当树只有一个结点(即根节点)时,根结点也算叶子结点。
(4)根节点不是任何结点的子结点,在题目中经常会利用这个性质确定根节点。

1.2.完全二叉树(complete binary tree)

概念:除最下面一层,其余层都达到最大结点数,最下面一层从左到右存在若干结点。
特殊情况:满二叉树(full binary tree)——各层都达到最大结点数。

特殊的存储方式:对结点从上到下、从左到右编号(从1开始),对任一编号为x的结点,其左孩子的编号一定为2x,右孩子为2x+1。且这种存放顺序为其层序遍历序列。
(1) 判断某个结点是否为叶结点的标志:记该结点下标为x,其左子结点标号2x大于结点总个数n。
(2) 判断某个结点为空结点的标志:下标大于总个数。

2.二叉树

2.1.建树

1.存储结构——二叉链表

struct node {int val;node *left, *right;
};

2.结点的插入
思路:假定根据给定条件插入位置应该是确定的,即插入位置只有一个,因此插入位置就是数据域在二叉树中查找失败的位置。因此在递归查找的过程中,最后到达空树(死胡同)的地方就是插入的位置。
注:这里root是引用的指针类型,是由于调用函数需要改变传入实参的值,这样才能将结点插入

void insert(node *&root, int x) { //注意root为引用类型if (root == NULL) {root = new node;root->val = x;root->left = root->right = NULL; //务必令新结点的左右指针域为NULL}else if (由于给定条件,结点应该插入在左子树) {insert(root->left, x);}else insert(root->right, x);
}

3.二叉树的创建
注意这里初始化root为NULL

int data[maxn];  //建树的数据域 
node* root=NULL;  //新建空根节点
for(int i=0;i<n;i++){insert(root, data[i]);
}

4.DFS
例如要统计每层的结点数:

void dfs(node *root,int depth) {if (root == NULL) { //递归边界return;}num[depth]++;dfs(root->left, depth + 1);dfs(root->right, depth + 1);
}

5.例题
由于需要建树的题目不多,有一个经典题是根据给定序列建BST。
https://pintia.cn/problem-sets/994805342720868352/problems/994805355987451904

2.2.二叉树的静态表示

静态表示即不使用指针来存储子结点的位置,做题时会更简便。因此以下算法均使用静态表示。

struct node{int left,right;int val;
};

2.3. 二叉树遍历

先序、中序、后序均采用递归算法即可,比较简单。这里展示层序遍历代码,是BFS算法:

void layerorder(int root) {queue<int> q;q.push(root);while (!q.empty()) {int now = q.front(); //取出队首元素q.pop();  //出队cout << no[now].val<<" ";  //访问if (no[now].left != -1) q.push(no[now].left);if (no[now].right != -1) q.push(no[now].right);}
}

2.4.几种遍历之间的转换

https://blog.csdn.net/weixin_43590232/article/details/104002692

3.一般意义的树的遍历

与二叉树遍历算法相同,只是结构体中用vector< int > child来存储子结点

3.1.深度优先搜索(DFS)与先根遍历

(1)对所有DFS求解过程,都可以把它画成树的形式,此时搜索时的死胡同(递归边界)等价与树中叶子结点,而岔道口等价与树中的非叶子结点。这也是为什么DFS算法中有剪枝的概念,就是从树的角度理解才产生的概念,即发现子树不可能存在问题的解,则剪掉该子树。
(2)对树的DFS遍历过程就是树的先根遍历过程(先访问根节点,再从左至右依次访问所有子树)

3.2.广度优先搜索(BFS)与层序遍历

(1)所有的BFS求解过程,都可以转换为树的层序遍历求解过程;
(2)树的层序遍历过程也就是一个广度优先搜索的过程。

3.3.相关题目

https://blog.csdn.net/weixin_43590232/article/details/104068637

4.二叉搜索树

也叫二叉查找树,概念就不解释了,直奔主题。

4.1.性质

二叉树搜索树有一个重要的性质:
对二叉搜索树进行中序遍历,遍历的结果是有序的。

4.2.相关题目

https://blog.csdn.net/weixin_43590232/article/details/104083675
https://blog.csdn.net/weixin_43590232/article/details/104086605
https://blog.csdn.net/weixin_43590232/article/details/104213909

5.AVL树

5.1 概念

当二叉查找树结构不当,退化为链式时,查找的时间复杂度为O(n),那么为了使树的结构能使插入元素后仍能保持O(logn)的级别,产生了二叉平衡树(AVL树)。因此,AVL树仍是二叉查找树。平衡是指:对AVL数的任一结点,其左右子树的高度差的绝对值不超过1。其中左右子树的高度差称该结点的平衡因子

5.2调整方法

对于左旋、右旋,有两种不同的定义,某些书的解释是将根结点的左孩子作为根结点是左旋,有的则以逆时针的方向作为左旋的判断。为了方便理解,这里选择第二种。
(1)左旋
即逆时针旋转,B取代A作为根结点。A成为B的左孩子,B的左孩子成为A的右孩子。
树有关重要算法及结论-编程之家
调整步骤如下:
树有关重要算法及结论-编程之家

void L(node* &root) {node* temp = root->right;root->right = temp->left;temp->left = root;root = temp;
}

(2)右旋
右旋与左旋只是方向相反,思路相同。
树有关重要算法及结论-编程之家

void R(node* &root) {node* temp = root->left;root->left = temp->right;temp->right = root;root = temp;
}

5.3 树型

但是情况不止以上两种,为了方便理解,引入了树型的概念。
首先,结论1:只要把最靠近插入结点的失衡结点调整到正常,路径上的所有结点就都会平衡。
插入结点就是“破坏者”,最近的失衡结点是“被破坏者”。如下图,灰色结点表示“破坏者”,不论破坏者是B的左孩子还是左孩子,情况都相同。需要先对C子树进行左旋,再对A右旋。由于破坏者在被破坏者左子树的右子树上,称为LR树型。需要先L旋转,再R旋转。如果破坏者在被破坏者左子树的左子树上,称为LL型,只需要右旋。
树有关重要算法及结论-编程之家
类似的,对于RR,RL型亦是如此。
树有关重要算法及结论-编程之家

5.5 计算平衡因子

由于平衡因子不能由孩子结点的平衡因子直接求得,而高度可以由其孩子结点的高度求得,因此求平衡因子需要先求高度。

int getHeight(node *root) {if (root == NULL) return 0;int l = getHeight(root->left); //左孩子的高度int r = getHeight(root->right); //右孩子的高度return max(l, r) + 1;
}
int getBalanceFactor(node* root) {return getHeight(root->left) - getHeight(root->right);
}

5.4 插入算法

void insert(node* &root, int val) {if (root == NULL) {root = new node;root->val = val;root->left = root->right = NULL;return;}if (root->val > val) {insert(root->left, val);if (getBalanceFactor(root) == 2) {if (getBalanceFactor(root->left) == 1) R(root); //LL型else {							//LR型L(root->left);R(root);}}}else {insert(root->right, val);if (getBalanceFactor(root) == -2) {if (getBalanceFactor(root->right) == -1) L(root); //RR型else {							//RL型R(root->right);L(root);}}}
}