树有关重要算法及结论

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);
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值