【数据结构】05树

    • 1.2 结点的分类
    • 1.3 结点间的关系
    • 1.4 树的其他概念
    • 1.5 树的性质
  • 2. 二叉树
    • 2.1 满二叉树
    • 2.2 完全二叉树
    • 2.3 二叉排序树(二叉查找树)
  • 3. 二叉树的存储结构
    • 3.1 二叉树顺序存储结构
    • 3.2 二叉树的链式存储结构
  • 4. 二叉树的遍历
    • 4.1 层次遍历
    • 4.1 前序遍历
    • 4.2 中序遍历
    • 4.3 后序遍历
  • 5. 线索二叉树
    • 5.1 线索二叉树的数据结构
    • 5.2 线索二叉树求前驱和后继
  • 6. 二叉排序树
    • 6.1 二叉排序树的插入
    • 6.2 创建二叉排序树
    • 6.3 二叉排序树的查找
    • 6.4 二叉排序树的遍历
    • 6.5 删除二叉排序树的结点
    • 6.6 二叉排序树的查找效率
    • 6.7 完整实现
  • 7. 哈夫曼树(最优二叉树)
    • 7.1 构造哈夫曼树
    • 7.2 哈夫曼编码

树(Tree)是 n ( n ≥ 0 ) n(n\ge0) n(n0)个结点的有限集。 n = 0 n=0 n=0时称为空树。在任意一颗非空树中:①有且仅有一个特定的称为根(Root)的结点②当 n > 1 n>1 n>1时,其余结点可分为 m m m( m > 0 m>0 m>0)个互不相交的有限集 T 1 、 T 2 、 . . . 、 T m T_1、T_2、...、T_m T1T2...Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。如图中所示
在这里插入图片描述
子树 T 1 T_1 T1 T 2 T_2 T2是根结点A的子树。当然DGHI组成的树,又是以B为根结点的子树,EJ是以C为根结点的子树。

注意: 1. n>0时,树的根结点是唯一的,不可能存在多个根结点。 2. m>0时,子树的个数没有限制,但它们一定是互不相交的。

1.2 结点的分类

树的结点包含一个数据元素及其若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支节点。除根结点之外,分支节点也称为内部节点。树的度是树内各结点的度的最大值(不是和)。如下图中所示,这棵树的度是3。
在这里插入图片描述

1.3 结点间的关系

结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent)结点。同一个双亲的孩子之间称为兄弟(Sibling)结点。结点的祖先是从根到该结点所经分支上的所有结点。对于图中所示的树来说。B是A的Child,A是B的Parent,B和C是Sibling。对H来说,GIJ都是它的Sibling,D是他的Parent,ABD都是它的祖先。

1.4 树的其他概念

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。树中结点的最大层次称为树的深度(Depth)或高度,上面图中所示的树的深度为4。

1.5 树的性质

  1. 树中的结点总数等于所有结点的度加一(对于上图:(2+1+2+3+1)+1 =10 个结点)
  2. m叉树中第 i , i ≥ 1 i,i\ge1 i,i1层上至多可以有 m i − 1 m^{i-1} mi1个结点。

2. 二叉树

二叉树(Binary Tree)是树形结构中最重要的类型,它的规律性强,应用广泛。
二叉树的每个结点最多只能拥有两棵子树,分别称为左子树和右子树。二叉树可以有五种基本形态:

  • 空树
  • 只有根结点
  • 只有左子树
  • 只有右子树
  • 左右子树都有

2.1 满二叉树

一颗高度为h,且含有 2 h − 1 2^{h}-1 2h1个结点的二叉树称为满二叉树。
对于编号为i的结点,左子结点编号为2i,右结点编号为2i+1,双亲结点为 ⌊ \lfloor i/2 ⌋ \rfloor (向下取整)。

2.2 完全二叉树

一颗高度为h,有n个结点的完全二叉树,它的每个结点都与高度相同的满二叉树中的结点编号一一对应。(完全二叉树除了最后一层,其他的都是满的)。如果最后一层中有度为1的结点,那么它的子结点一定是左节点。
性质:

  • 对于编号为i的结点,左子节点为2i,右结点为2i+1,双亲结点为 ⌊ \lfloor i/2 ⌋ \rfloor
  • 若结点编号i ≤ \le ⌊ \lfloor n/2 ⌋ \rfloor ,则结点为分支结点,否则为叶结点。
  • 如果编号为i的结点为叶结点或只有左孩子,则编号大于i的结点均为叶结点。

2.3 二叉排序树(二叉查找树)

树上任意结点,如果存在左子树和右子树,则左子树上所有结点元素值都小于该结点,右子树上所有结点元素值都大于该结点。

3. 二叉树的存储结构

3.1 二叉树顺序存储结构

二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标能够体现结点之间的逻辑关系。
完全二叉树:编号为i的结点,左子节点为2i,右子节点为2i+1,双亲结点 ⌊ \lfloor i/2 ⌋ \rfloor
体现在数组中下表为i的结点,左子节点下标为2i+1,右子节点下标为2i+2,双亲结点下标为 ⌊ \lfloor (i-1)/2 ⌋ \rfloor
在这里插入图片描述
普通二叉树:补齐成为完全二叉树,按照完全二叉树存放,数组中利用特殊值来填充。但利用顺序存储的方法存储普通二叉树,补齐成为完全二叉树再存储,容易造成空间的浪费,比较适合顺序存储结构。

3.2 二叉树的链式存储结构

struct BiTNode{
    TElemType data;//数据域
    struct BiTNode *lchild,*rchild;//左右孩子结点指针
    struct BiTNode *parent; // 指向双亲结点指针
}BiTNode;
typedef BiNode* BiTree;

4. 二叉树的遍历

二叉树的遍历,是指从根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次。

4.1 层次遍历

规则:树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下的逐层遍历,同一层中,按从左到右的顺序对结点逐个访问。
方法:初始化将二叉树根结点插入队列中。开始,出队队头元素,并将队头元素的左右子结点入队(没有子结点入队为空),直到队列中为空。依次出队的元素就是层次遍历的顺序。
图示:
在这里插入图片描述
代码实现:

void LevelOrder(BiTree TT)
{
	std::queue<BiTreeNode*> qq; // 队列
	
	qq.push(TT); //根结点入队

	while (!qq.empty())
	{
		// 队头出队
		BiTreeNode* out = qq.front();
		qq.pop();
		cout << out->data << " ";
		if (out->lchild != nullptr) // 左子结点
		{
			qq.push(out->lchild);
		}
		if (out->rchild != nullptr) // 右子结点
		{
			qq.push(out->rchild);
		}
	}
	cout << endl;
}

4.1 前序遍历

规则:二叉树为空,则空操作返回,否则从根结点开始,先访问根结点,然后前序遍历左子树,再前序遍历右子树。
代码实现:

// 前序遍历
void PreOrder(BiTree TT)
{
	// 为空返回
	if (TT==nullptr)
	{
		return;
	}
	// 访问根结点
	cout << TT->data << " ";
	// 访问左子树
	PreOrder(TT->lchild);
	// 访问右子树
	PreOrder(TT->rchild);
}

4.2 中序遍历

规则:二叉树为空,则空操作返回,否则从根结点开始,先中序遍历根结点的左子树,然后访问根结点,最后中序遍历右子树。
代码实现:

// 中序遍历
void MidOrder(BiTree TT)
{
	// 为空返回
	if (TT == nullptr)
	{
		return;
	}
	
	MidOrder(TT->lchild);// 访问左子树
	cout << TT->data << " "; // 访问根结点
	MidOrder(TT->rchild); // 访问右子树
}

4.3 后序遍历

规则:二叉树为空,则空操作返回,否则从左到右,先叶子后结点的方式遍历访问左右子树,最后访问的是根结点。
代码实现:

// 后序遍历
void PostOrder(BiTree TT)
{
	if (TT == nullptr)
	{
		return;
	}
	// 访问左子树
	PostOrder(TT->lchild);
	// 访问右子树
	PostOrder(TT->rchild);
	// 访问根结点
	cout << TT->data << " ";
}

5. 线索二叉树

把指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded Binary Tree) 。
二叉树线索化是将二叉链表中的空指针指向前驱或后继结点。而前驱或后继结点的信息只有遍历时才能得到,因此二叉树的线索化又分为先序线索二叉树、中序线索二叉树和后序线索二叉树。

  • 如果该结点没有左子结点(左子树),则将左指针指向遍历序列中它的前驱结点。
  • 如果该结点没有右子节点(右子树),则将右指针指向遍历序列中它的后继节点。

5.1 线索二叉树的数据结构

struct TBtNode
{
	ElemType data;
	TBtNode* lchild;
	TBtNode* rchild;
	bool ltag,rtage; // 左右指针的类型,0-非线索指针,1-线索指针
};
typedef TBtNode* TBtree;

5.2 线索二叉树求前驱和后继

先序线索二叉树:可求后继:

  • 如果当前结点的右指针存放的是线索,右指针指向的结点就是后继结点。
  • 如果当前结点的右指针存放的是结点,如果结点存在取左子结点,否则取右子结点。

后序线索二叉树:可求前驱:

  • 如果当前结点的左指针存放的是线索,左指针指向的结点就是前驱结点
  • 如果当前结点的左指针存放的是结点,如果结点存在取右子结点,否则取左子结点。

中序线索二叉树:可求前驱和后继:
求后继:

  • 如果当前结点右指针存放的是线索,右指针指向的结点就是后继节点
  • 如果当前结点右指针存放的是结点,右子树中序遍历的第一个结点即后继结点

求前驱

  • 如果当前结点左指针存放的是线索,左指针指向的结点就是后继结点
  • 如果当前结点左指针存放的是结点,左子树中序遍历的第一个结点即后继结点

6. 二叉排序树

二叉排序树(二叉搜索树,二叉查找树,Binary Sort Tree BST),一颗飞控的二叉排序树具有下列性质:

  1. 如果左子树不空,则左子树上所有结点的值都小于根结点值
  2. 如果右子树不空,则右子树上所有结点的值都大于根结点值
  3. 左右子树也分别是二叉排序树

左子树<根<右子树
数据结构

typedef int ElemType;

struct BSTNode
{
	ElemType data; // 数据域
	BSTNode* lchild; // 左子结点
	BSTNode* rchild; // 右子结点
};

typedef BSTNode* BSTree;

6.1 二叉排序树的插入

  • 从根结点开始,递归插入,小于当前结点的值,递归左子树,大于当前结点的值递归右子树。
  • 插入元素肯定是在叶结点或根结点
  • 如果插入元素重复,则返回异常
// 在二叉排序树中插入结点
bool InsertBST(BSTree& tree, ElemType* data)
{
	if ( tree== nullptr)  // 当树为空,创建根结点
	{
		tree = new BSTNode;
		memcpy(&(tree->data), data, sizeof(ElemType));
		tree->lchild = tree->rchild = nullptr;
		return true;
	}
	// 如果元素已存在返回
	if (*data == (tree)->data)
	{
		return false;
	}
	// 插入
	if (*data < (tree)->data)
	{
		return InsertBST((tree)->lchild, data); // 向左递归
	}
	else
	{
		return InsertBST((tree)->rchild, data); // 向右递归
	}
}

6.2 创建二叉排序树

  • 相同的序列创建的二叉排序树是唯一的
  • 同一集合创建的二叉排序树是不同的(根结点不同,从而二叉排序树不同)
  • 用二叉树的先序遍历创建的二叉排序树与原树相同
// 创建二叉排序树
void CreateBST(BSTree& tree, ElemType arr[], int len)
{
	tree = NULL;
	for (int i = 0; i < len; i++)
	{
		InsertBST(tree, &arr[i]);
	}
}

6.3 二叉排序树的查找

根据带查找元素值与结点值的大小比较,小于结点值,递归左子树,大于结点值递归右子树。

// 在二叉排序树中查找结点
BSTNode* FindNode(BSTree tree, ElemType data)
{
	if (tree == nullptr) // 查找失败
	{
		return nullptr; 
	}
	if (data == tree->data)
	{
		return tree;
	}
	if (data < tree->data)
	{
		return FindNode(tree->lchild, data); //向左递归
	}
	else
	{
		return FindNode(tree->rchild, data); //向右递归
	}
}

6.4 二叉排序树的遍历

利用中序遍历输出的二叉排序树是按照从小到大的顺序(先左子树(即先小的),然后根结点,最后右子树(最后大的))

// 中序遍历二叉排序树
void InOrder(BSTree* tree)
{
	if (*tree == nullptr)
	{
		return;
	}
	// 先左子树
	InOrder(&((*tree)->lchild));
	// 根结点
	cout << (*tree)->data << " ";
	// 右子树
	InOrder(&((*tree)->rchild));
}

6.5 删除二叉排序树的结点

  • 如果树只有根结点,并且待删除的结点就是根结点
  • 如果待删除的结点是叶结点,直接删除,不会破坏二叉排序树的性质
  • 如果待删除的结点只有左子树或右子树,则让子树代替自己
  • 如果待删除的结点有左子树和右子树,让左子树最右侧的结点代替自己,然后删除左子树最右侧的结点。(也可以让右子树最左侧的结点代替自己,然后删除右子树最左侧的结点。)
bool DeleteNode(BSTree& tree, ElemType* data)
{
	if (tree == nullptr) // 树为空
	{
		return false;
	}
	// (1) 树只有根节点,并且待删除结点就是根结点
	if ((tree->lchild == nullptr && tree->rchild == nullptr) && *data == tree->data)
	{
		// 删除根结点
		delete tree;
		tree = nullptr;
		return true;
	}
	// 
	BSTNode* ptr = tree; // 
	BSTNode* pre_ptr = nullptr; // 记录双亲结点
	int r_or_l = 0; // 记录结点是双亲结点的左子树还是右子树
	while (ptr != nullptr)
	{
		if (ptr->data == *data) // 找到结点
		{
			break;
		}

		pre_ptr = ptr; // 记录双亲结点

		if (*data < ptr->data) // 向左递归 
		{
			ptr = ptr->lchild;
			r_or_l = 1;
		}
		else                   // 向右递归
		{
			ptr = ptr->rchild;
			r_or_l = 0;
		}
	}
	if (ptr == nullptr) // 未找到
	{
		return false;
	}

	// (2) 如果待删除的结点是叶结点,直接删除
	if (ptr->lchild == nullptr && ptr->rchild == nullptr)
	{
		if (r_or_l == 0) // 当前结点是双亲结点的右子结点
		{
			pre_ptr->rchild = nullptr;
		}
		else  // 当前结点是双亲结点的左子结点
		{
			pre_ptr->lchild = nullptr;
		
		}
		delete ptr; // 删除当前结点
		ptr = nullptr;
		return true;
	}
	// (3) 如果待删除结点只有左子树或只有右子树
	if (ptr->lchild == nullptr || ptr->rchild == nullptr)
	{
		if (ptr->lchild != nullptr) // 只有左子树
		{
			// 左子树取代当前结点
			// 双亲结点指向当前结点的左子树
			if (r_or_l == 0) // 当前结点的左子树,是双亲结点的右子树
			{
				pre_ptr->rchild = ptr->lchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
			else  // 当前结点的左子树,是双亲结点的左子树
			{
				pre_ptr->lchild = ptr->lchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
		}
		else // 只有右子树
		{
			// 右子树取代当前结点
			// 双亲结点指向当前结点的右子树
			if (r_or_l == 0) // 当前结点的右子树,是双亲结点的右子树
			{
				pre_ptr->rchild = ptr->rchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
			else  // 当前结点的右子树,是双亲结点的左子树
			{
				pre_ptr->lchild = ptr->rchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
			return true;
		}
	}
	// (4) 如果待删除结点已经有左子树和右子树,让左子树最右侧的结点取代自己,然后再删除左子树最右侧的结点
	BSTNode* tmp_ptr = ptr->lchild;
	BSTNode* pre_ptr2 = nullptr; // 记录最右侧结点的双亲结点位置
	while (tmp_ptr->rchild) // 找到当前结点的左子树最右侧结点
	{
		pre_ptr2 = tmp_ptr;
		tmp_ptr = tmp_ptr->rchild;
	}
	// 最右侧结点替代当前结点
	ptr->data = tmp_ptr->data;
	
	// 左子树最右侧结点必定没有右子树
	// 双亲结点右指针最右侧结点的左子树
	pre_ptr2->rchild = tmp_ptr->lchild; 
	
	// 删除最右侧结点
	delete tmp_ptr;
	tmp_ptr = nullptr;
	return true;
}

6.6 二叉排序树的查找效率

在查找操作中,需要对比结点值的次数即查找长度,反映了查找运算的时间复杂度。
查找成功的平均查找长度(ASL ,Average Search Length)。ASL=∑(每层结点个数 X 该层层数)/结点个数
例如对二叉排序树
在这里插入图片描述
的ASL=(11+22+33+42)/8=2.75
在这里插入图片描述
查找失败的ASL=(21+34+4*4)/9=3.33
最好的情况:平均查找长度O(log2n)
最坏的情况:平均查找长度O(n)

6.7 完整实现

#include <iostream>
using namespace std;
typedef int ElemType;

struct BSTNode
{
	ElemType data; // 数据域
	BSTNode* lchild; // 左子结点
	BSTNode* rchild; // 右子结点
};

typedef BSTNode* BSTree;


// 在二叉排序树中插入结点
bool InsertBST(BSTree& tree, ElemType* data)
{
	if ( tree== nullptr)  // 当树为空,创建根结点
	{
		tree = new BSTNode;
		memcpy(&(tree->data), data, sizeof(ElemType));
		tree->lchild = tree->rchild = nullptr;
		return true;
	}
	// 如果元素已存在返回
	if (*data == (tree)->data)
	{
		return false;
	}
	// 插入
	if (*data < (tree)->data)
	{
		return InsertBST((tree)->lchild, data); // 向左递归
	}
	else
	{
		return InsertBST((tree)->rchild, data); // 向右递归
	}
}

// 创建二叉排序树
void CreateBST(BSTree& tree, ElemType arr[], int len)
{
	tree = NULL;
	for (int i = 0; i < len; i++)
	{
		InsertBST(tree, &arr[i]);
	}
}

// 在二叉排序树中查找结点
BSTNode* FindNode(BSTree tree, ElemType data)
{
	if (tree == nullptr) // 查找失败
	{
		return nullptr; 
	}
	if (data == tree->data)
	{
		return tree;
	}
	if (data < tree->data)
	{
		return FindNode(tree->lchild, data); //向左递归
	}
	else
	{
		return FindNode(tree->rchild, data); //向右递归
	}
}


// 删除二叉树结点
bool DeleteNode(BSTree& tree, ElemType* data)
{
	if (tree == nullptr) // 树为空
	{
		return false;
	}
	// (1) 树只有根节点,并且待删除结点就是根结点
	if ((tree->lchild == nullptr && tree->rchild == nullptr) && *data == tree->data)
	{
		// 删除根结点
		delete tree;
		tree = nullptr;
		return true;
	}
	// 
	BSTNode* ptr = tree; // 
	BSTNode* pre_ptr = nullptr; // 记录双亲结点
	int r_or_l = 0; // 记录结点是双亲结点的左子树还是右子树
	while (ptr != nullptr)
	{
		if (ptr->data == *data) // 找到结点
		{
			break;
		}

		pre_ptr = ptr; // 记录双亲结点

		if (*data < ptr->data) // 向左递归 
		{
			ptr = ptr->lchild;
			r_or_l = 1;
		}
		else                   // 向右递归
		{
			ptr = ptr->rchild;
			r_or_l = 0;
		}
	}
	if (ptr == nullptr) // 未找到
	{
		return false;
	}

	// (2) 如果待删除的结点是叶结点,直接删除
	if (ptr->lchild == nullptr && ptr->rchild == nullptr)
	{
		if (r_or_l == 0) // 当前结点是双亲结点的右子结点
		{
			pre_ptr->rchild = nullptr;
		}
		else  // 当前结点是双亲结点的左子结点
		{
			pre_ptr->lchild = nullptr;
		
		}
		delete ptr; // 删除当前结点
		ptr = nullptr;
		return true;
	}
	// (3) 如果待删除结点只有左子树或只有右子树
	if (ptr->lchild == nullptr || ptr->rchild == nullptr)
	{
		if (ptr->lchild != nullptr) // 只有左子树
		{
			// 左子树取代当前结点
			// 双亲结点指向当前结点的左子树
			if (r_or_l == 0) // 当前结点的左子树,是双亲结点的右子树
			{
				pre_ptr->rchild = ptr->lchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
			else  // 当前结点的左子树,是双亲结点的左子树
			{
				pre_ptr->lchild = ptr->lchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
		}
		else // 只有右子树
		{
			// 右子树取代当前结点
			// 双亲结点指向当前结点的右子树
			if (r_or_l == 0) // 当前结点的右子树,是双亲结点的右子树
			{
				pre_ptr->rchild = ptr->rchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
			else  // 当前结点的右子树,是双亲结点的左子树
			{
				pre_ptr->lchild = ptr->rchild;
				delete ptr; // 删除当前结点
				ptr = nullptr;
			}
			return true;
		}
	}
	// (4) 如果待删除结点已经有左子树和右子树,让左子树最右侧的结点取代自己,然后再删除左子树最右侧的结点
	BSTNode* tmp_ptr = ptr->lchild;
	BSTNode* pre_ptr2 = nullptr; // 记录最右侧结点的双亲结点位置
	while (tmp_ptr->rchild) // 找到当前结点的左子树最右侧结点
	{
		pre_ptr2 = tmp_ptr;
		tmp_ptr = tmp_ptr->rchild;
	}
	// 最右侧结点替代当前结点
	ptr->data = tmp_ptr->data;
	
	// 左子树最右侧结点必定没有右子树
	// 双亲结点右指针最右侧结点的左子树
	pre_ptr2->rchild = tmp_ptr->lchild; 
	
	// 删除最右侧结点
	delete tmp_ptr;
	tmp_ptr = nullptr;
	return true;
}

// 先序遍历二叉排序树
void PreOrder(BSTree* tree)
{
	if (*tree == nullptr)
	{
		return;
	}

	// 根结点
	cout << (*tree)->data << " ";
	// 左子树
	PreOrder(&((*tree)->lchild));
	// 右子树
	PreOrder(&((*tree)->rchild));
}


// 中序遍历二叉排序树
void InOrder(BSTree* tree)
{
	if (*tree == nullptr)
	{
		return;
	}
	// 先左子树
	InOrder(&((*tree)->lchild));
	// 根结点
	cout << (*tree)->data << " ";
	// 右子树
	InOrder(&((*tree)->rchild));
}


// 后序遍历二叉排序树
void PostOrder(BSTree* tree)
{
	if (*tree == nullptr)
	{
		return;
	}
	// 先左子树
	InOrder(&((*tree)->lchild));
	// 右子树
	InOrder(&((*tree)->rchild));
	// 根结点
	cout << (*tree)->data << " ";
}

int main(void)
{
	BSTree tree;

	//ElemType arr[] = { 1,3,5,2,4,8,6,7 };
    //
	//  1 
	//    3
	//   2  5
	//     4 8
	//      6
	//       7
	ElemType arr[] = { 50,30,20,40,32,31,38,35,39,34,36,37,33 };
	CreateBST(tree, arr, sizeof(arr) / sizeof(ElemType));
	PreOrder(&tree);
	cout << endl;
	InOrder(&tree);
	cout << endl;
	PostOrder(&tree);
	cout << endl;
	ElemType e = 38;
	DeleteNode(tree, &e);
	InOrder(&tree);
	cout << endl;
	return 0;
}

7. 哈夫曼树(最优二叉树)

结点的路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数称为路径长度。
结点的权:结点的数值有某种现实的含义(如重要性、两个点之间的距离等)
结点的带权路径长度:从树的根结点到该结点的路径长度与该结点上的权值的乘积
树的带权路径长度:为树中所有叶子结点的带权路径长度之和(WPL,Weight Path Length)
在这里插入图片描述
在含有n个带权结点的二叉树中,WPL最小的二叉树称为哈夫曼树(最优二叉树)。如同图中所示,第三棵树才是哈夫曼树。
哈夫曼树并不唯一:将第三棵树左右结点位置调换依然是一颗哈夫曼树

7.1 构造哈夫曼树

详细过程图解:
初始结点看作是只有单个结点的树
在这里插入图片描述
先挑两个根结点权值最小的树,构建一颗新的树,并新增一个结点,权值为两子树权值之和
在这里插入图片描述
继续挑选权值最小的树,构建新的树
在这里插入图片描述
继续挑选权值最小的树,构建新的树
在这里插入图片描述
继续挑选权值最小的树,构建新的树
在这里插入图片描述继续挑选权值最小的树,构建新的树
在这里插入图片描述
继续挑选权值最小的树,构建新的树在这里插入图片描述
继续挑选权值最小的树,构建新的树
在这里插入图片描述
最终
在这里插入图片描述1. 初始结点都会成为叶结点,叶结点的权值越大,离根结点越近。
2. 如果叶结点有n个,共合并n-1次,哈夫曼树的结点总数为2n-1
3. 哈夫曼树不存在度为1的结点
4. 哈夫曼树不唯一,只要WPL最小就行

7.2 哈夫曼编码

可变长度编码,任何一个字符的编码都不是另一个字符编码的前缀,这种编码称作前缀编码。
利用哈夫曼树来设计前缀编码,用0和1表示左子树和右子树。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/540086.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务器代理

服务器代理 配置&#xff1a;64G内存1 3090&#xff08;24g&#xff09;1P4000&#xff08;8g&#xff09; SSH连接 工作路径&#xff1a;/home/ubuntu/workspace/python Anaconda路径&#xff1a;/home/Ubuntu 1.在工作路径下创建自己的文件夹作为workspace 2.以用户ubunbtu登…

Java技术学习|SpringBoot面试篇

学习材料声明 黑马程序员黑马程序员SpringBoot3Vue3全套视频教程&#xff0c;springbootvue企业级全栈开发从基础、实战到面试一套通关 经过了基础知识后端开发前端开发&#xff0c;终于到了面试篇。 前置知识 1.ApplicationContextInitializer 首先&#xff0c;SpringBoot…

(学习日记)2024.04.16:UCOSIII第四十四节:内存管理

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

【GD32】MQ-8氢气检测传感器

2.36 MQ-8氢气检测传感器 MQ-8气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(Sn0s)。当传感器所处环境中存在氢气时&#xff0c;传感器的电导率随空气中氢气浓度的增加而增大。使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出信号。MQ-8气体…

数字乡村发展新模式:科技创新引领农业现代化与乡村振兴协同发展

随着信息技术的飞速发展&#xff0c;数字乡村已成为新时代农业现代化与乡村振兴协同发展的新模式。科技创新作为推动这一模式的核心动力&#xff0c;正引领着乡村产业结构的优化升级&#xff0c;促进农村经济的全面振兴&#xff0c;让农民在现代化的进程中共享发展成果。 一、科…

VUE_H5页面跳转第三方地图导航,兼容微信浏览器

当前项目是uniapp项目&#xff0c;若不是需要替换uni.showActionSheet选择api onMap(address , organName , longitude 0, latitude 0){var ua navigator.userAgent.toLowerCase();var isWeixin ua.indexOf(micromessenger) ! -1;if(isWeixin) {const mapUrl_tx "…

Python数据分析案例39——电商直播间评论可视化分析(LDA)

1. 引言 1.1 直播电商的发展背景 随着互联网技术的飞速发展&#xff0c;电商行业迎来了新的变革——直播电商。直播电商是一种结合了直播技术和电子商务的新型销售模式。在这种模式下&#xff0c;商家或主播通过实时视频直播的方式&#xff0c;展示产品并与消费者互动&#x…

ipad协议847最新版

iPad协议是一种模拟iPad端微信的人工操作&#xff0c;并与微信服务器进行通信的协议。该协议涉及到一些关键点&#xff0c;包括PB协议、mmtls、07加密算法、rqt算法、aes加密、rsa加密等。只要理解了这些关键点&#xff0c;就可以模拟官方微信的所有功能&#xff0c;并且还可以…

AndroidAutomotive模块介绍(二)应用及接口介绍

前言 上一篇文章中从整体角度描述了 Android Automotive 模块。本篇文章将对 Android Automotive 中的 APP 以及 API 部分展开描述。 上一篇&#xff1a;AndroidAutomotive模块介绍&#xff08;一&#xff09;整体介绍 下一篇&#xff1a;AndroidAutomotive模块介绍&#xff0…

自然语言处理NLP:文本预处理Text Pre-Processing

大家好&#xff0c;自然语言处理(NLP)是计算机科学领域与人工智能领域中的一个重要方向&#xff0c;其研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。本文将介绍文本预处理的本质、原理、应用等内容&#xff0c;助力自然语言处理和模型的生成使用。 1.文本…

SqlServer功能性配置选择

功能性配置 下面的是必选的

SpringBoot整合Nacos

文章目录 nacosnacos下载nacos启动nacos相关配置demo-dev.yamldemo-test.yamluser.yaml 代码pom.xmlUserConfigBeanAutoRefreshConfigExampleValueAnnotationExampleDemoApplicationbootstrap.yml测试结果补充.刷新静态配置 nacos nacos下载 下载地址 一键傻瓜试安装即可,官…

5.0 HDFS 集群

5.0 HDFS 集群 分类 编程 HDFS 集群是建立在 Hadoop 集群之上的&#xff0c;由于 HDFS 是 Hadoop 最主要的守护进程&#xff0c;所以 HDFS 集群的配置过程是 Hadoop 集群配置过程的代表。 使用 Docker 可以更加方便地、高效地构建出一个集群环境。 每台计算机中的配置 Hado…

【Python】读取时间判定操作次数问题和一些解决办法

几种类 datetime.striptime() 计算两个字符串之间的时间差 datetime.striptime()计算两个字符串之间的时间差 datatime类提供函数处理日期和时间 Striptime()分析字符串值以固定格式表示时间然后存储为函数参数 输出就是&#xff1a; time.sleep() time模块打印时间按照对…

动态规划-路径问题

文章目录 1. 不同路径&#xff08;62&#xff09;2. 不同路径 II&#xff08;63&#xff09;3. 珠宝的最高价值&#xff08;LCR 166&#xff09;4. 下降路径最小和&#xff08;931&#xff09;5. 最小路径和&#xff08;64&#xff09;6. 地下城游戏&#xff08;174&#xff09…

程序员Java.vue,python前端后端爬虫开发资源分享

bat面试资料 bat面试题汇总 提取码&#xff1a;724z 更多资料

【日常记录】【CSS】生成动态气泡小球

文章目录 1、分析2、实现 1、分析 核心有两点&#xff0c;通过这两个不一样就可以实现每个小球的颜色、动画时间不一致 给每个元素都设置一个css 变量 bgc 用于控制每一个小球的颜色给每个元素都设置一个css 变量 duration 用于控制每一个小球的时间 2、实现 <!DOCTYPE ht…

学习javaEE的日子 Day36 字符流

Day36 1.字符流 应用场景&#xff1a;操作纯文本数据 注意&#xff1a;字符流 字节流编译器 编译器&#xff1a;可以识别中文字符和非中文字符&#xff0c;非中文字符获取1个字节&#xff08;一个字节一个字符&#xff09;&#xff0c;编译器会根据编码格式获取中文字符对应的…

造船业的重要工具之一(火工平台)——河北北重厂家

火工平台是造船业的重要工具之一&#xff0c;它是用于火焰切割和焊接的设备。在造船过程中&#xff0c;需要对金属材料进行切割和焊接&#xff0c;以构建船体结构。火工平台可以提供高温火焰&#xff0c;使得金属材料可以被切割或焊接。 火工平台通常由两个主要部分组成&#…

武汉星起航顺应政策东风,打造跨境电商孵化新标杆

在国家政策的鼎力支持下&#xff0c;跨境电商行业迎来了蓬勃发展的黄金时期。武汉星起航电子商务有限公司作为行业的佼佼者&#xff0c;积极响应国家政策号召&#xff0c;凭借专业的运营团队和丰富的经验&#xff0c;成功打造了一站式的跨境电商亚马逊孵化平台&#xff0c;为合…