系统掌握数据结构9树与二叉树第三节

1.树与森林

1.1 树的存储结构

我们在第一节已经学习过树的逻辑结构,而且对于二叉树的存储结构和抽象数据类型已经很熟悉了,下面考虑如何存储一棵普通的树。(三种存储结构,重点是孩子兄弟表示法)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkpaUSCc-1649824356851)(树与二叉树 第三节.assets/p1.png)]

1.1.1 双亲表示法(顺序存储)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z1eqLCX9-1649824356853)(树与二叉树 第三节.assets/p2-1649307708596.png)]

存储方式:每个节点包括数据域data和指针域parent,指针域指向该节点的双亲索引,parent是int型,用数组来存储这些节点,根节点的parent=-1。

区别于二叉树的顺序存储,二叉树顺序存储的数组索引能表达出节点之间的逻辑关系,而树的双亲表示法索引不能表达逻辑关系,各个节点存在哪里不是很重要。

typedef struct {
	ElemType data;
	int parent;
}PTNode;
typedef struct {
	PTNode nodes[MaxSize];
	int n;//节点数量
}PTree;

找双亲节点:parent指向的即是双亲节点。

找孩子节点:只能遍历树。

删除节点:将最后一个节点移动到该节点处覆盖该节点值。(保证遍历树的时候不会遍历到空值浪费时间)。

添加节点:在索引n添加节点设置parent即可。


1.1.2 孩子表示法(顺序+链式存储)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hsNNFrl5-1649824356853)(树与二叉树 第三节.assets/p3-1649308465182.png)]

存储方式:普通节点包括数据域data和指针域firstChild,firstChild指向一个索引节点,该索引节点的数据域是普通节点的第一个孩子节点的索引,索引节点的指针域next指向其兄弟节点。

存储代码:

#define MaxSize 100
#define ElemType char
typedef struct CTNode{
	int child;
	CTNode* next;
}CTNode;
typedef struct {
	ElemType data;
	CTNode* firstChild;
}CTBox;
typedef struct {
	CTBox nodes[MaxSize];
	int n,r;//节点数和根的位置
}CTree;

只有找孩子简单,其他操作都十分复杂。

1.1.3 孩子兄弟表示法(链式存储)

我们对用二叉链表存储二叉树已经十分熟悉了,这里将用二叉链表存储普通的树,它的代码是这样的。

#define ElemType char
typedef struct CSNode {
	ElemType data;
	CSNode* firstChild, * nextSibling;//分别指向第一和孩子节点和下一个兄弟节点
}CSNode,CSTree;

节点的两个指针域分别指向第一个孩子和下一个兄弟,使用孩子兄弟表示法,上面的树可以变成如下的样子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XueDZOW-1649824356854)(树与二叉树 第三节.assets/p4.png)]

一棵普通的树使用孩子兄弟表示法它的物理结构变成了二叉链表(二叉树),而对于二叉链表(二叉树)的各种操作我们已经很熟悉了。

1.2 森林的存储结构

多棵树就构成了森林,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H4THqibb-1649824356854)(树与二叉树 第三节.assets/p5.png)]

我们将每棵树的根节点看作兄弟,既可以用孩子兄弟表示法将森林作为一棵树来存储,变成如下形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUUfD2sy-1649824356855)(树与二叉树 第三节.assets/p6.png)]

如此森林使用孩子兄弟表示法它的物理结构变成了二叉链表(二叉树),而对于二叉链表(二叉树)的各种操作我们已经很熟悉了。

1.3 树与森林的抽象数据类型

我们将树和森林都是用孩子兄弟表示法变成"二叉树"的样子,不倒霉不至于遇到树和森林的算法题,若遇到了将其用孩子兄弟表示法,变成“二叉树”后就是我们熟悉的算法了,只要稍微修改即可。

树的先根遍历:就是二叉树的先序遍历。

树的后根遍历:就是二叉树的后序遍历。

森林的先序遍历:先序遍历每一棵树,或者用孩子兄弟表示森林时先序遍历二叉树。

森林的中序遍历:后序遍历每棵树,或者用孩子兄弟表示法时中序遍历二叉树。(有点绕,试一下就相信了)

2.树的应用

一、二叉排序树(BST)
2.1 应用场景

设想,我们按顺序存储一段数据,那我们该选择哪种数据结构能方便查找某一数据呢?线性表和普通链表都需要遍历数据可能遍历完。如果用树呢?看下面这棵二叉树,这颗二叉树规定左子树的节点的值都小于根节点,根节点都小于右子树节点的值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awqbE9p6-1649824356855)(树与二叉树 第三节.assets/p12.png)]

如此,假如我们要找28,28小于50向左,大于25向右,下雨35向左,等于28找到。如此只需要比较4次即可。上图就是一棵二叉排序树

2.2. 二叉排序树的定义

1)二叉排序树是一棵空二叉树或者是一棵二叉树;

2)二叉排序树的根节点关键字大于左子树所有节点关键字小于右子树所有节点关键字;

3)左子树和右子树也是二叉排序树;

2.3 二叉排序树算法
#define OK 1
#define ERROR 0
#define ElemType int
#define Status int
typedef struct BSTNode {
	int data;
	BSTNode* lchild, * rchild;
}BSTNode, * BSTree;

2.3.1 二排序叉树的查找算法

BSTNode* BST_Search(BSTree T, ElemType key) {
	while (T != NULL && T->data != key) {
		if (T->data > key)
			T = T->lchild;
		else T = T->rchild;
	}
	return T;
}
BSTNode* BST_Search(BSTree T, ElemType key) {//二叉排序树的递归查找
	if (T != NULL) {
		if (T->data = key)
			return T;
		else if (key < T->data)
			BST_Search(T->lchild,key);
		else BST_Search(T->rchild,key);
	}
	return NULL;
}

2.3.2 二叉排序树的插入

Status BST_insert(BSTree &T, ElemType key) {
	if (T == NULL) {//空树
		T = (BSTNode*)malloc(sizeof(BSTNode));
		if (!T) {
			cout << "申请内存错误!" << endl;
			exit(1);
		}
		T->data = key;
		T->lchild = T->rchild = NULL;
	}
	else {
		if (T->data > key)
			BST_insert(T->lchild, key);
		else if (T->data < key)
			BST_insert(T->rchild, key);
		else {//不允许插入相同值的关键字
			return ERROR;
		}
	}
}

2.3.3 二叉排序树的构造

Status CreatBST(BSTree &T,ElemType key[],int n) {
	T = NULL;
	int i = 0;
	while (i < n) {
		BST_insert(T, key[i]);
		i++;
	}
	return OK;
}

小实验:

int main() {
	BSTree T;
	ElemType key[10] = { 15,28,9,16,3,24,55,28,17,10 };
	CreatBST(T, key, 10);
	cout << "在二叉排序树查找3:" << endl;
	BSTNode* s = BST_Search(T, 3);
	cout << s->data << endl;

	cout << "在二叉排序树插入12后查找12:" << endl;
	BST_insert(T, 12);
	s = BST_Search(T, 12);
	cout << s->data << endl;

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ku7MOoYr-1649824356856)(树与二叉树 第三节.assets/p13.png)]

2.3.4 二叉排序树的删除

1)若是叶子节点则直接删除;

2)若删除节点只有一棵左子树或右子树,让其孩子代替该节点的位置成为该节点父亲节点的孩子;

3)若删除节点有两棵树,首先令该节点的中序直接后继(其右子树最左的节点是后继,该后继是叶子节点或者只有右子树)代替该节点成为其父亲节点的孩子节点以及其孩子节点的父节点,然后该后继若为叶子节点则删除完成,若有右子树令其右子树称为其原来父节点的左子树。

删除的算法不难,但是为了方便找到父节点我们需要用三叉链表来存储,大家可以自己试一下。

二、平衡二叉树
三、哈夫曼树与哈夫曼编码
3.1 应用场景

假设有一场考试,100道选择题的答案为50个A,30个B,10个C,10个D。我们如何用计算机传递出答案,是我们传递的数据量最少呢?

如果用ASCII码传递,一个字符8bit,我们需要800bit。

了解计算机组成原理我们可以使用2bit对字符进行编码,A为00,B为01,C为10,D为11。这样我们需要200bit就足够了。

如果A为0,B为10,C为111,D为110,这样我们需要50 * 1 + 30 * 2 +10 * 3 + 10 * 3 = 170bit就足够了,这就是哈夫曼编码。

那么存在一个疑问,如果将B改为1,只占一个bit不是可以更好吗?答案是不行的,假如我们传输ABBBCD,编码会是0111111110,出现歧义了,我们可以翻译为ACCD也可以翻译为ABBBBBBD等。但是如果用哈夫曼编码就不会出现这种情况,可以尽情尝试。

因为哈夫曼编码是一种前缀编码,即每一个字符的编码都不是另一个字符的前缀。

3.2 哈夫曼树的逻辑结构

节点的权:树的节点常常被赋予一个表示某种意义的值,称为节点的权。

带权路径长度(WPL):从根结点出发到任意节点的边数乘以该节点的权称为该节点的带权路径长度。树中所有叶节点的带权路径之和称为树的带权路径长度。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DvruRyjT-1649824356857)(树与二叉树 第三节.assets/p7-1649674266925.png)]

如上图所示,该树的带权路径长度=5 * 1 + 4 * 2 + 3 * 3 + 1 * 3 = 25;

哈夫曼树:在带有n个叶节点的二叉树中,WPL最小的二叉树称为哈夫曼树,也称最优二叉树。

(哈夫曼树关注的是叶节点,叶节点与前缀编码的关系密不可分,哈夫曼树并不唯一!)

3.3 哈夫曼树的构造

1)将叶子节点全部加入待构造集合,在待构造集合中将权值最小的两个节点作为新建节点的左右孩子(那个左哪个右都可以),新建节点加入待构造集合,这两个节点从待构造集合中删除,将新建节点的权值设为这两个节点的权值之和。

2)将重复上述过程直到待构造集合仅剩一个节点,即哈夫曼树的根节点。

举例:

如上述例子50个A,30个B,10个C,10个D,我们将A,B,C,D都设为叶子节点并加入待构造集合:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtcvBCzZ-1649824356857)(树与二叉树 第三节.assets/P8.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QSsp0qlv-1649824356858)(树与二叉树 第三节.assets/p9.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fAnaBI5v-1649824356858)(树与二叉树 第三节.assets/p10.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRcEdpP4-1649824356859)(树与二叉树 第三节.assets/p11.png)]

如图我们构造完成了一棵哈夫曼树,那每个字符编码是什么呢?我们可以规定,从根节点出发道某一结点,向左一次为0,向右一次为1,则A0,B10,C110,D111。这就是哈夫曼树的编码。(哈夫曼树的算法在普通考试中不会让手写算法的,暂时略过,到此树一章就完事了,还有一篇习题)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构与算法是计算机科学的基础和核心领域之一,第ba章介绍了数据结构的相关内容。 本章主要包括以下几个方面的内容: 1. 线性结构:线性结构是指数据元素之间存在一对一的关系,包括线性表、栈和队列。线性表是最基本的数据结构之一,它分为顺序表和链表两种形式。顺序表使用数组实现,插入和删除操作相对低效。链表使用指针实现,插入和删除操作较为灵活。 2. 结构:结构是一种层次结构,由节点和边组成。常见的结构有二叉树、二叉搜索和平衡二叉树等。二叉树中每个节点最多有两个子节点,二叉搜索中左子节点的值小于根节点,右子节点的值大于根节点,查找效率较高。平衡二叉树是一种保持左右子高度差不大于1的二叉搜索。 3. 图结构:图结构是由节点和边组成的非线性结构。图分为有向图和无向图。图的表示方法有邻接矩阵和邻接表两种。深度优先搜索和广度优先搜索是图的常用遍历方法,可用于寻找路径、连通分量等问题。 4. 排序算法:排序算法是对一组无序数据进行按照某个规则进行有序排列的算法。第ba章介绍了常见的排序算法,包括冒泡排序、插入排序、选择排序、归并排序、快速排序等。每种排序算法的时间复杂度和空间复杂度不同,选择合适的排序算法可以提高性能。 5. 查找算法:查找算法是在一组数据中搜索某个特定元素的算法。第ba章介绍了顺序查找、二分查找和哈希查找等常见的查找算法。二分查找是在有序数组中应用最广泛的查找算法,通过不断缩小查找范围来快速定位目标值。 通过学习这些内容,我们可以了解不同数据结构的特点和应用场景,以及常见的排序和查找算法。掌握好这些知识,对于解决实际问题和提高程序效率都有很大帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值