2024.3.21
芝士wa
参考视频: 数据结构-树
“种一棵树,最好的时间是十年前,其次是现在”
树的定义
树是由 n (n ≥ 0) 个结点组成的有限集合。如果 n = 0,称为空树;如果 n > 0,则有一个特定的称之为根(root)的结点,它只有直接后继,但没有直接前驱;除根以外的其他结点划分为 m (m ≥ 0) 个 互不相交的有限集合T0, T1, …, Tm-1,每个集合又是一棵树,并且称之为根的子树。
上述定义过于抽象。
树是一种非线性的数据结构,可用来存储层次机构的数据。
树的结构
一些繁杂的概念:
-
考虑结点K。根A到结点K的唯一路径上的任意结点,称为结点K的祖先。如结点B是结点K的祖先,而结点K是结点B的子孙。路径上最接近结点K的结点E称为K的双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。有相同双亲的结点称为兄弟,如结点K和结点L有相同的双亲E,即K和L为兄弟。
-
树中一个结点的孩子个数称为该结点的度,树中结点的最大度数称为树的度。如结点B的度为2,结点D的度为3,树的度为3。
-
度大于0的结点称为分支结点(又称非终端结点);度为0(没有子女结点)的结点称为叶子结点(又称终端结点)。在分支结点中,每个结点的分支数就是该结点的度。
-
结点的深度、高度和层次。
-
结点的层次从树根开始定义,根结点为第1层,它的子结点为第2层,以此类推。双亲在同一层的结点互为堂兄弟,图中结点G与E,F,H,I,J互为堂兄弟。
-
结点的深度是从根结点开始自顶向下逐层累加的。
-
结点的高度是从叶结点开始自底向上逐层累加的。
-
树的高度(或深度)是树中结点的最大层数。图中树的高度为4。
-
有序树和无序树。树中结点的各子树从左到右是有次序的,不能互换,称该树为有序树,否则称为无序树。假设图为有序树,若将子结点位置互换,则变成一棵不同的树。
-
路径和路径长度。树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
-
注意:由于树中的分支是有向的,即从双亲指向孩子,所以树中的路径是从上向下的,同一双亲的两个孩子之间不存在路径。
-
森林。森林是m (m≥0)棵互不相交的树的集合。森林的概念与树的概念十分相近,因为只要把树的根结点删去就成了森林。反之,只要给m棵独立的树加上一个结点,并把这m棵树作为该结点的子树,则森林就变成了树。
二叉树 Binary Search Tree
定义
每一个节点最多有两个子节点的树。
- 只有一个根,也是二叉树。
- 如果一个树除了最底层之外全被填充,并且所有节点都向左对齐,我们称之为完全二叉树。
- 二叉树的实现有两种方式,一是动态创建节点,二是采用数组的方式
二叉搜索树
-
二叉搜索树是一种二叉树,对其中的每一个节点,它的所有左子树上的节点值都比该节点小。
-
二叉搜索树基于二分查找的原理,在平均情况下,查找、插入或者删除的时间复杂度为O(logn)。
-
二叉搜索树的平衡十分重要,应该尽可能保持平衡
-
两种遍历方式:深度优先,广度优先
广度优先:按层访问,先访问同层的所有元素,再跳转到下一层
深度优先:有三种方式
- 前序遍历:<root><left><right>
- 中序遍历:<left><root><right>
- 后序遍历:<left><right><root>
例如,对于下面这样一棵二叉树
前序遍历:F D B A C E J G I H K中序遍历:A B C D E F G H I J K
后序遍历:A C B E D H I G K J F
二叉搜索树的层级访问
按层访问,可以使用队列,首先将root节点放入队列中,然后在出队之前,将root节点的左右子节点存入队列中,出队,此时队列中有root-left和root-right,在root-left出队之前,把root-left的左右子节点存入队列中,依次操作,即可实现按层遍历。
二叉搜索树的前中后序访问
采用递归的思想。代码如下
void BST::PreOrder(BstNode* root)
{
if (root == NULL) {
return;
}
cout << root->data<<"\t";
PreOrder(root->left);
PreOrder(root->right);
}
void BST::InOrder(BstNode* root)
{
if (root == NULL) {
return;
}
InOrder(root->left);
cout << root->data << "\t";
InOrder(root->right);
}
void BST::PostOrder(BstNode* root)
{
if (root == NULL) {
return;
}
PostOrder(root->left);
PostOrder(root->right);
cout << root->data << "\t";
}
判断一个树是否为二叉搜索树
所有方法均采用递归的思想。
方法一:
对每个节点,检测其值是否大于左子树的最大值,是否小于右子树的最小值。
该方法十分繁琐,重复查找的操作大大增加了时间复杂度,不建议使用。
代码如下:
int maxValue(BstNode* root)
{
int max = root->data;
if(root->left != NULL)
{
int maxLeft = maxValue(root->left);
max = max > maxLeft ? max : maxLeft;
}
if(root->right != NULL)
{
int maxRight = maxValue(root->right);
max = max > maxRight ? max : maxRight;
}
return max;
}
int minValue(BstNode* root)
{
int min = root->data;
if(root->left != NULL)
{
int minLeft = maxValue(root->left);
min = min < minLeft ? min : minLeft;
}
if(root->right != NULL)
{
int minRight = maxValue(root->right);
min = min < minRight ? min : minRight;
}
return min;
}
bool isBST(BstNode* root)
{
if(root == NULL)
return true;
if(root->left != NULL && maxValue(root->left) > root->data)
return false;
if(root->right != NULL && minValue(root->right) < root->data)
return false;
return isBST(root->left) && isBST(root->right);
}
方法二:中序遍历
二叉搜索树的中序遍历是一个递增序列,所以我们只需要把这个中序遍历保存下来,然后判断这是个递增序列即可。
代码如下:
void BST::InOrder(BstNode* root, vector<int>& v)
{
if (root == NULL) {
return;
}
InOrder(root->left,v);
v.push_back(root->data);
InOrder(root->right,v);
}
bool BST::isBST(BstNode* root)
{
vector<int> inorder;
InOrder(root,inorder);
for (int i = 1; i < inorder.size(); ++i)
if (inorder[i - 1] >= inorder[i])
return false;
return true;
}
使用静态变量可以简化上述代码,省略栈的空间
bool isBST(BstNode* root)
{
static BstNode* prev;
if(root != NULL)
{
if(!isBST(root->left))
return false;
if(prev != NULL && root->data < prev->data)
return false;
prev = root;
if(!isBST(root->right))
return false;
}
return true;
}
删除特定节点
采用递归的思想
删除节点一共有三种情况:
- 该节点没有子节点:直接对其进行删除即可,并将其指针归零
- 该节点有一个子节点:保存其子节点,然后修改祖先的指向,最后删除该节点
- 该节点有两个子节点:找到其中序后继,将其copy到该节点的位置,然后删除中序后继所在的节点,将问题转换成第二张情况
代码如下:
void BST::deleteNode(BstNode*& node, int data)
{
if (node == nullptr) {
return; // 如果树为空,直接返回
}
if (data < node->data) {
deleteNode(node->left, data); // 在左子树中寻找要删除的节点
}
else if (data > node->data) {
deleteNode(node->right, data); // 在右子树中寻找要删除的节点
}
else {
// 找到了要删除的节点
if (node->left == nullptr && node->right == nullptr) {
delete node;
node = nullptr; // 删除叶子节点
}
else if (node->left == nullptr) {
BstNode* temp = node;
node = node->right;
delete temp; // 删除只有右子节点的节点
}
else if (node->right == nullptr) {
BstNode* temp = node;
node = node->left;
delete temp; // 删除只有左子节点的节点
}
else {
// 删除有两个子节点的节点
BstNode* successor = node->right;
//找到右子树中最小的值
while (successor->left != nullptr) {
successor = successor->left;
}
node->data = successor->data; // 将后继节点的值复制到要删除的节点
deleteNode(node->right, successor->data); // 递归删除后继节点
}
}
}
中序后继
要找到一个节点的中序后继,可以执行中序遍历,将所有节点打印出来,进而找到中序后继,时间复杂度为O(n),因为要访问所有节点。
下面采用更简单的方式查找,即利用二叉搜索树查找元素的时间复杂度为O(h)的特点,h为高度
BstNode* findSuccessor(BstNode* root, BstNode* node)
{
if (node->right != nullptr) {
// 如果节点有右子树,后继节点为右子树中的最小节点
return findMin(node->right);
}
BstNode* successor = nullptr;
while (root != nullptr) {
if (node->data < root->data) {
successor = root;
root = root->left;
} else if (node->data > root->data) {
root = root->right;
} else {
break;
}
}
return successor;
}
树的难度较高,需要加深理解。
标签:node,结点,right,data,Tree,root,节点 From: https://www.cnblogs.com/cheese-wa/p/18088071