数据结构之树

数据结构之树

树是由N个(N>=0)个结点组成的一个有限集合,在任意一颗非空树中

  • 有且仅有一个结点被称为根结点
  • N>=1时,其余的结点可以分成M个互不相交的有限集合

其中每个集合本身又是一颗树,叫做根结点的“子树”

树的基本概念

  • 结点的度:该结点拥有的子树个数

  • 叶子结点:度为0的结点

  • 分支结点:度不为0的结点

  • 结点的层次:从根节点开始,根结点为第一层,根结点的孩子为第二层,孩子的孩子为第三层……(以此类推)

    • 最大结点层次就是树的层次
  • 树的宽度:结点数目最多的那一层

二叉树

二叉树的概念

  • 二叉树是一种特殊的树,每个结点至多只有两棵子树

  • 即任意一个结点的度 <=2,并且子树有左右之分,其顺序不能颠倒

二叉树的性质

  • 二叉树的第 i 层 ∗ ∗ 最多 ∗ ∗ 有多少个结点? 2 i − 1 二叉树的第 i 层**最多**有多少个结点?\\ 2{i-1} 二叉树的第i最多有多少个结点?2i1

  • 深度为 n 的二叉树,最多有多少个结点? 2 n − 1 深度为n的二叉树,最多有多少个结点?\\ 2^n-1 深度为n的二叉树,最多有多少个结点?2n1

  • 具有 n 个结点的二叉树的深度范围: [ l o g 2 n ( 向下取整 ) + 1 , n ] 具有n个结点的二叉树的深度范围:\\ [log_2n(向下取整)+1,n] 具有n个结点的二叉树的深度范围:[log2n(向下取整)+1n]

  • 对于任意一颗二叉树 T ,如果叶子结点的数目是 n 0 , 度为 2 的结点是 n 2 可以得出一个结论 n 0 = n 2 + 1 对于任意一颗二叉树T,如果叶子结点的数目是n_0,\\ 度为2的结点是n_2\\ 可以得出一个结论\\ n_0=n_2+1 对于任意一颗二叉树T,如果叶子结点的数目是n0,度为2的结点是n2可以得出一个结论n0=n2+1

证明,第四点
从结点的角度分析 : 假设度为 1 的结点为 n 1 , 总结点为 N n 1 + n 2 + n 0 = N 从分支的角度分析 : 分支数: n 1 + 2 × n 2 ( 数杠 ) 总结点数: n 1 + 2 × n 2 + 1 ( 根结点没有杠指向它,故 + 1 算上根结点 ) 两式联立 n 1 + n 2 + n 0 = n 1 + 2 × n 2 + 1 = = > n 0 = n 2 + 1 从结点的角度分析:\\假设度为1的结点为n_1,总结点为N\\ n_1+n_2+n_0=N 从分支的角度分析:\\ 分支数:n_1+2×n_2(数杠)\\ 总结点数:n_1+2×n_2+1(根结点没有杠指向它,故+1算上根结点)\\ 两式联立\\ n_1+n_2+n_0=n_1+2×n_2+1\\ ==>n_0=n_2+1 从结点的角度分析:假设度为1的结点为n1,总结点为Nn1+n2+n0=N从分支的角度分析:分支数:n1+2×n2(数杠)总结点数:n1+2×n2+1(根结点没有杠指向它,故+1算上根结点)两式联立n1+n2+n0=n1+2×n2+1==>n0=n2+1

顺序结构

对于一颗二叉树而言,

假设父亲的下标为 i(根结点的下标一般从1开始),

那么它的左孩子的下标为 2 * i,右孩子的下标为2 * i + 1,

如果不是完全二叉树的情况会造成比较大的空间浪费

故一般不用顺序结构

链式结构

构建二叉树的结点类型
typedef int Elemtype;	//结点的数据的类型

typedef struct tnode
{
	Elemtype data;
	struct tnode * lchild;	//指向左孩子的指针
	struct tnode * rchild;	//指向右孩子的指针
}Tnode;

在这里插入图片描述

结点初始化
/*
* 初始化结点
*/
Tnode* tnode_init(elemtype d) 
{
	Tnode* p = (Tnode*)malloc(sizeof(Tnode));
	p->lchild = NULL;
	p->rchild = NULL;
	p->data = d;
	return p;
}
构建一颗排序二叉树

排序二叉树:

  1. 每个节点都包含一个键值,且键值具有唯一性。
  2. 左子树中所有节点的键值小于当前节点的键值。
  3. 右子树中所有节点的键值大于当前节点的键值。
  4. 左子树和右子树也都是排序二叉树。
/*
* 创建排序二叉树
*/
Tnode* create_sort_tree(void)
{
	elemtype d;
	Tnode * root = NULL;
	while (1)
	{
		scanf_s("%d", &d);
		if (d == -1)
		{
			break;
		}
		Tnode* New = tnode_init(d);
		if (root == NULL)//从无到有
			root = New;
		else//从少到多
		{
			Tnode* q = root;
			while (q)
			{
				if (New->data < q->data)//小于q的data往左边走下去
				{
					if (q->lchild == NULL)
					{
						q->lchild = New;
						break;
					}
					q = q->lchild;
				}
				else if (New->data >= q->data)//大于q的data往右边走下去
				{
					if (q->rchild == NULL)
					{
						q->rchild = New;
						break;
					}
					q = q->rchild;
				}
			}
		}
	}
	return root;
}
遍历
前序遍历(先序遍历)
/*
* 先序遍历
*/
void Pre_Tree(Tnode* T)
{
	if (T)
	{
		printf("%d ", T->data);
		Pre_Tree(T->lchild);
		Pre_Tree(T->rchild);
	}
}
中序遍历
/*
* 中序遍历
*/
void Mid_Tree(Tnode* T)
{
	if (T)
	{
		Mid_Tree(T->lchild);
		printf("%d ", T->data);
		Mid_Tree(T->rchild);
	}
}
后序遍历
/*
* 后序遍历
*/
void Post_Tree(Tnode* T)
{
	if (T)
	{
		Post_Tree(T->lchild);
		Post_Tree(T->rchild);
		printf("%d ", T->data);
	}
}

层次遍历

层次遍历需要用到队列

首先令根结点入队①

打印后出队②

再以它的角度入队它的左右孩子③

再不断循环队列的首元素,直至全部结点遍历完出队

如下图:
在这里插入图片描述

/*
* 层次遍历
* int Elemt_Enqueue(Queue * q, Elemtype d);//元素入队
* Elemtype Elemt_Dequeue(Queue * q);	//元素出队
* 参数:第一个参数:Tnode * 类型,传入一颗树
* 第二个参数:Queue * 类型,传入一个队列
*/
void levle_traversal(Tnode* T)
{
	if (!T)
		return;
	Queue* q = queue_init();
	if (q->front==NULL)//入队根结点的情况
	{
		Elemt_Enqueue(q, T);
	}
	while(q->front)
	{
		T = Elemt_Dequeue(q);
		printf("%d ", T->data);//打印队列中第一个结点,并且出队
		if(T->lchild)//入队刚刚出队的左孩子
			Elemt_Enqueue(q, T->lchild);
		if(T->rchild)//入队刚刚出队的右孩子
			Elemt_Enqueue(q, T->rchild);
	}
	Destory_queue(q);	//销毁队列
}

该函数中所要用到的队列函数都可以在下面这个博客里找到哦

数据结构之队列

判断一棵树是否为完全二叉树
/*
* 判断该树是否为完全二叉树
* 是返回1,不是返回0
*/
int Is_complete_binary_tree(Tnode* T)
{
	int Flag_Empty = 0;	//空位标志位
	int Flag_Judge = 1;	//判断标志位
	if (T == NULL)
	{
		return Flag_Judge;
	}
	//出现空位就不能再入队
	//如果出现空位还有入队,就不是完全二叉树

	Queue* q = queue_init();	//初始化一个队列

	Elemt_Enqueue(q, T);	//根结点入队
	while (q->front)
	{
		T =Elemt_Dequeue(q);//出队队列第一个结点
		if (T->lchild)
		{
			if (Flag_Empty == 1)
			{
				Flag_Judge = 0;
			}
			Elemt_Enqueue(q, T->lchild);	//左孩子存在,左孩子入队
		}
		else 
		{
			Flag_Empty = 1;
		}
		if (T->rchild)//入队刚刚出队的右孩子
		{
			if (Flag_Empty == 1)
			{
				Flag_Judge = 0;
			}
			Elemt_Enqueue(q, T->rchild);	//右孩子存在,右孩子入队
		}
		else
		{
			Flag_Empty = 1;
		}
	}
	Destory_queue(q);	//销毁队列
	return Flag_Judge;
}

该函数中所要用到的队列函数都可以在下面这个博客里找到哦

数据结构之队列

求一颗树的高度
递归
/*
* 求一棵排序二叉树的深度(递归)
*/
int get_height(Tnode* T)
{
	if (T == NULL)
	{
		return 0;
	}
	int l = get_height(T->lchild);
	int r = get_height(T->rchild);
	return l > r ? l + 1 : r + 1;
}
非递归
/*
* 求一棵排序二叉树的深度
*/
int Get_height(Tnode* T)
{
	int height = 0;
	if (!T)
		return height;
	Queue* q1 = queue_init();
	Queue* q2 = queue_init();
	Elemt_Enqueue(q1, T);
	height++;
	//若q1不为空,将q1的元素的左右孩子入q2,再退出为空,height++
	//若q2不为空,将q2的元素的左右孩子入q1,再退出为空,height++
	while (q1->front || q2->front)
	{
		if(q1->front)
		{
			while(q1->front)
			{
				T = Elemt_Dequeue(q1);//出队队列第一个结点
				if (T->lchild)
				{
					Elemt_Enqueue(q2, T->lchild);	//左孩子存在,左孩子入队
				}
				if (T->rchild)//入队刚刚出队的右孩子
				{
					Elemt_Enqueue(q2, T->rchild);	//右孩子存在,右孩子入队
				}
			}
			height++;
		}
		if (q2->front)
		{
			while (q2->front)
			{
				T = Elemt_Dequeue(q2);//出队队列第一个结点
				if (T->lchild)
				{
					Elemt_Enqueue(q1, T->lchild);	//左孩子存在,左孩子入队
				}
				if (T->rchild)//入队刚刚出队的右孩子
				{
					Elemt_Enqueue(q1, T->rchild);	//右孩子存在,右孩子入队
				}
			}
			height++;
		}
	}
	Destory_queue(q1);	//销毁队列
	Destory_queue(q2);	//销毁队列
	return height-1;
}
删除一个结点,保持排序二叉树不变

首先我们要找到该结点的位置

若是该结点不存在,直接返回原来的树

若是该结点存在,则对其进行操作

分为删除根结点,与非根结点的情况

再细分为左子树存不存在的情况

/*
* 删除一颗排序二叉树中值为x的第一个结点并保持排序性不变
*/

Tnode* delete_x(Tnode* T, elemtype x)
{
	Tnode* root = T;//树的根结点
	Tnode* q = T;//q删除结点的上一个结点
	//找位置
	while(T)
	{
		if (x > T->data)
		{
			q = T;
			T = T->rchild;
		}
		else if (x < T->data)
		{
			q = T;
			T = T->lchild;
		}
		else
		{
			break;//找到了
		}
	}
	if (T)//存在,即找到了
	{
		if (T==root)//删除根结点的情况
		{
			if (T->lchild)//若根结点左子树存在,令右子树接到左子树的右孩子的右孩子的右孩子……
			{
				Tnode* l = T->lchild;//该结点的左孩子
				Tnode* r = T->rchild;//该结点的右孩子
				//找左子树最大的位置即(左子树的右孩子的右孩子的右孩子……)
				while (l->rchild)
				{
					l = l->rchild;
				}
				l->rchild = r;
				root = T->lchild;
			}
			else//左子树为空则令右子树为根结点
			{
				root = T->rchild;
			}
		}
		else//删除其他结点的情况
		{
			if (T->lchild)//左子树存在,令右子树接到左子树的右孩子的右孩子的右孩子……
			{
				Tnode* l = T->lchild;//该结点的左孩子
				Tnode* r = T->rchild;//该结点的右孩子
				//找左子树最大的位置即(左子树的右孩子的右孩子的右孩子……)
				while (l->rchild)
				{
					l = l->rchild;
				}
				l->rchild = r;
				//判断T是q的左孩子还是右孩子
				if (q->lchild == T)
				{
					q->lchild = l;
				}
				if (q->rchild == T)
				{
					q->rchild = l;
				}
			}
			else//左子树不存在
			{
				Tnode* r = T->rchild;
				//判断T是q的左孩子还是右孩子
				if (q->lchild == T)
				{
					q->lchild = r;
				}
				if (q->rchild == T)
				{
					q->rchild = r;
				}
			}
		}
		//释放删除结点
		T->lchild = NULL;
		T->rchild = NULL;
		free(T);
	}
	return root;
}

感谢各位的观看,如有问题请指出,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值