树是一种用于表达层级结构的数据结构
树的定义
树
树结构是一种数据结构。它由结点以及连接结点的边构成,如图所示:
其中圆点表示结点,实线表示连接结点的边
黑色的结点我们称为根,是一棵树的起始点,如果一棵树有根,我们称之为“有根树”
树有以下几个概念:
父结点、子结点、兄弟结点:
如图中的结点1、2、3,它们具有共同的父结点,也就是结点0;而结点6、7、8,它们的父结点是结点2.相应地,我们也说结点6、7、8是结点2的子结点,而结点6、7、8相互称为兄弟节点(具有相同的父节点)
叶结点:没有子结点的结点,如结点4,结点11
内部结点:除叶结点以外的结点
度:结点x的子结点数。如结点2的度为3,叶结点的度为0
深度:从根结点到结点x的路径长度。如结点8的深度为2
高:结点x到叶结点的最大路径长度。如结点2的高为2,结点3的高为1,
树的高:即根节点的高,如图中树高为3
二叉树
如果一棵树拥有1个根节点,而且所有结点的子结点数都不大于2,则称之为有根二叉树
其中,我们将左边的子结点称为左子结点,右边的子结点称为右子结点
左子树:
以左子结点作为根的树称为左子树
右子树:
以右子结点作为根的树称为右子树
完全二叉树
两种情况:
1.所有叶结点深度相同,且所有内部结点都有两个子结点的二叉树称为完全二叉树(满二叉树)
2.二叉树的叶结点深度最大差距为1,最下层叶节点都集中在该层最左边的若干位置
二叉搜索树
搜索树是一种可以进行插入,搜索,删除等操作的数据结构。二叉搜索树的各节点均拥有键值,且满足以下性质:
设结点x为二叉搜索树的结点,如果y为x结点左子树的结点,那么y的键值<=x的键值,如果z为x右子树的结点,那么z的键值>=的键值
树的实现
二叉树的实现
结构体实现
由二叉树的定义我们可知,对于任意一个结点,我们应当知道它的左子节点与右子节点,如果内存条件允许,我们还应知道他的父结点,从而更方便我们对树进行修改
因此,我们可以通过开创一个结构体作为树的结点,其中包含结点本身的值以及指向上述三个结点的指针
数组实现
我们可以开一个数组,若一个结点的下标为i(i>0),则它的左子结点的下标为2*i,右子结点的下标为2*i+1,父结点的下标为i/2(/表示整数除法)
对二叉搜索树的操作
结点的搜索
给定要搜索的键值,搜索树上结点,若存在该键值,返回true,否则返回false
结点的增删
插入结点:
给定要插入的键值,新建一个结点并将其插入到树上合适的位置
删除结点:
给定要删除的键值,搜索树上结点,若成功找到,将其删除
在删除过程中,我们需要保证二叉搜索树结点键值仍然遵循大小顺序规则
1.删除叶结点:直接删除即可
2.删除只有一棵子树的内部结点:称删除结点为p,将p的父结点的子结点指针指向p的子树的根节点,然后删除p即可
3.删除有两棵子树的内部结点:称删除结点为p,由于需要保证父节点大于左子结点,我们需要将p的右子结点的值赋给p,然后将p的右子结点删除(删除方式根据p的右子结点的子结点个数而定,采用上述的方式1、2或者3)
树的遍历
现在给定一棵二叉树如图:
树的遍历方式有三种,分别为前序遍历,中序遍历和后序遍历
前序遍历:按照根节点——左子树——右子树的顺序返回结点的值
即:25,12,6,3,2,8,1,9,5,4
中序遍历:按照左子树——根节点——右子树的顺序返回结点的值
即:3,6,2,12,1,8,25,5,9,4
后序遍历:按照左子树——右子树——根节点的顺序返回结点的值
即:3,2,6,1,8,12,5,4,9,25
结构体实现代码如下:
typedef struct Tree{
int val;
struct Tree* parent;//指向父结点
struct Tree* left;//指向左子结点
struct Tree* right;//指向右子节点
}TreeNode, *LinkNode;
//结点的搜索
bool Find(LinkNode root, int key) {
if (root) {
if (root->val > key) {//键值小于结点值时,向左子树查找
return Find(root->left, key);
}
else if (root->val < key) {//键值大于结点值时,向右子树查找
return Find(root->right, key);
}
else {
return true;
}
}
else {//找不到时,返回false
return false;
}
}
//结点的插入
void Insert(LinkNode* root, int key) {
if (!*root) {//当指向NULL时,正是要插入的位置
*root = (LinkNode)malloc(sizeof(TreeNode));
(*root)->val = key;
(*root)->left = (*root)->right = NULL;
}
else {
if ((*root)->key > num) {//键值小于结点的值时,向左子树寻找合适位置
Insert(&((*root)->left), key);
}
else {//键值大于结点的值时,向右子树寻找合适位置
Insert(&((*root)->right), key);
}
}
}
//结点的删除
void Delete(LinkNode root, int key) {
if (root) {
if(root->val==key){//结点值等于键值,是我们要删除的结点
if (!root->left && !root->right) {//叶结点
if (root->parent->left == root) {
root->parent->left = NULL;
}
else {
root->parent->right = NULL;
}//置空其父结点的指针
free(root);//释放内存
}
else if (!root->right) {//只有左子结点
if (root->parent->left == root) {
root->parent->left = root->left;
}
else {
root->parent->right = root->left;
}//使父结点的指针指向自己的左子结点
root->left->parent = root->parent;//左子节点的父指针指向自己的父结点
free(root);
}
else if (root->right) {
if (root->parent->right == root) {
root->parent->right = root->right;
}
else {
root->parent->left = root->right;
}
root->right->parent = root->parent;
free(root);
return;
}
else {//左右子结点均存在
root->val = root->right->val;//将右子结点的值赋给自己
root->right->val = key;//将自己的值(键值)赋给右子节点
Delete(root->right, key);//递归去判断右子结点的情况,进而删除右子结点
return;
}
}
else{//没有找到键值,继续递归往下查找
Delete(root->left,key);
Delete(root->right,key);
}
}
//树的前序遍历
void PreOrder(LinkNode root){
if (root) {
printf("前序遍历:%d\n", root->val);
PreOrder(root->left);
PreOrder(root->right);
}
}
//树的中序遍历
void InOrder(LinkNode root){
if (root) {
InOrder(root->left);
printf("中序遍历:%d\n", root->val);
InOrder(root->right);
}
}
//树的后序遍历
void PostOrder(LinkNode root){
if (root) {
PostOrder(root->left);
PostOrder(root->right);
printf("后序遍历:%d\n", root->val);
}
}
树遍历的应用——树的重建
现给定了一棵二叉树,并给出了两个结点编号序列,分别是对这棵二叉树的前序遍历结果和中序遍历结果,现要求编写程序输出对该棵二叉树后序遍历的结点编号序列
我们观察前面提到的前序遍历和中序遍历的数组结果:(该树没有重复的键值,可以勉强看作结点编号(偷懒不想再找一颗树))
前序遍历:25,12,6,3,2,8,1,9,5,4
中序遍历:3,6,2,12,1,8,25,5,9,4
由于前序遍历的顺序是根节点——左子树——右子树
而中序遍历的顺序是——左子树——根节点——右子树
因此,对于前序遍历的每一个根节点的编号(称之为a),我们都可以在中序遍历中找到,而且a之前的元素,就是a的左子树的全部结点,a之后的元素,就是a的右子树的全部节点
如对根节点25而言
在中序遍历中,其前面的元素为3,6,2,12,1,8,即它的左子树的元素
后面的元素为5,9,4即它的右子树的元素
再对根节点12而言,由前序遍历可知,它是25的左子树的根节点
在中序遍历中,其前面的元素为3,6,2,即它的左子树的元素
其后面的元素为1,8(到25就没了,因为这是25的左子树的全部元素了),即它的右子树的元素
再按照这样的顺序递归下去,就可以还原整棵树,或者说只需要按照左子树——右子树——根节点的顺序,即可输出该树的后序遍历
实现代码如下:
int n,pos;
vector<int> pre, in, post;
void reConstruction(int l,int r){
if(l>=r){
return;
}
int root=pre[index++];
int m=distance(in.begin(),find(in.begin(),in.end(),root));
reConstruction(l,m);
reConstruction(m+1,r);
post.push_back(root);
}