数据结构——树、二叉树、二叉搜索树

树是一种用于表达层级结构的数据结构

树的定义

树结构是一种数据结构。它由结点以及连接结点的边构成,如图所示:

其中圆点表示结点,实线表示连接结点的边

黑色的结点我们称为根,是一棵树的起始点,如果一棵树有根,我们称之为“有根树”

树有以下几个概念:

父结点、子结点、兄弟结点:

如图中的结点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);
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值