【数据结构】05树

news2024/11/25 23:34:09

    • 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表示左子树和右子树。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

打造无尘车间:细致措施保障洁净环境

在现代工业生产中&#xff0c;无尘车间已经成为一种标准配置&#xff0c;特别是在对产品质量和生产环境要求极高的行业&#xff0c;如医药、电子等领域。无尘车间的建设并非简单的任务&#xff0c;它需要通过一系列精细的措施来确保空气中的尘埃和微生物达到最低水平&#xff0…

toLocaleString方法使用;js获取本地时间年月日和当前周,时分秒动态显示;

本项目是vue3的项目&#xff0c;以vue3为例&#xff1b; 使用toLocaleString方法 年月日&#xff1a; xxx.toLocaleString(chinese, { year: numeric, month: long, day: numeric }) 当前周&#xff1a; xxx.toLocaleString(chinese, { weekday: short }) 时分秒&#xff1a; x…

【C++学习】C++智能指针:提高代码安全与性能的利器

文章标题 智能指针的提出智能指针概念及使用RAII 智能指针的原理C库多种智能指针详解版本一&#xff1a;std::auto_ptr&#xff08;C98&#xff09;1. std::auto_ptr 使用2. std::auto_ptr 原理3. std::auto_ptr 模拟实现 版本二&#xff1a;unique_ptr (C11)1. unique_ptr 的使…

春招百题--堆--扩展篇--找出最小

其他类似题目&#xff1a; 373. 查找和最小的 K 对数字378. 有序矩阵中第 K 小的元素719. 找出第 K 小的数对距离786. 第 K 个最小的素数分数 2040. 两个有序数组的第 K 小乘积 2386. 找出数组的第 K 大和 215. 数组中的第K个最大元素 不纠结直接sort排序解决。 class Solut…

SpringBoot项目接入Nacos注册中心

前置 已经安装好Nacos服务&#xff0c;并且该项目所在服务器可以访问到 可以参考下&#xff1a; windows环境安装Nacos单机版-CSDN博客 Centos7安装Nacos单机版-CSDN博客 1. POM文件引入依赖 注意&#xff0c;父工程已经引入spring cloud依赖管理的情况下不用添加版本号 …

基于opencv的视觉巡线实现

前言 这段时间在和学弟打软件杯的比赛&#xff0c;有项任务就是机器人的视觉巡线&#xff0c;这虽然不是什么稀奇的事情&#xff0c;但是对于一开始不了解视觉的我来说可以说是很懵了&#xff0c;所以现在就想着和大家分享一下&#xff0c;来看看是如何基于opencv来实现巡线的…

天猫商品详情数据接口(Tmall.item_get)天猫搜索商品列表接口

天猫商品详情数据接口&#xff08;Tmall.item_get&#xff09;是天猫开放平台提供的一种API接口&#xff0c;旨在帮助开发者获取天猫平台上的商品详情信息。通过调用这个接口&#xff0c;开发者可以获取包括商品ID、标题、价格、库存量、图片等在内的详细数据&#xff0c;从而更…

由elemnent-ui模拟一个全选、反选效果想到的购物车逻辑案例

本文参考 https://blog.csdn.net/sumimg/article/details/137508302?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22137508302%22%2C%22source%22%3A%22sumimg%22%7D 我遇到的问题 点击店铺二级的时候&#xff0c;checkedCiti…

HNUST湖南科技大学嵌入式开发板使用-2024

目录 1.需要准备的软件(版本必须相同)꒰ঌ( ⌯ ⌯)໒꒱ 2.下载链接地址⌯▾⌯ 3.软件安装教程 4.安装好了&#xff0c;正常情况会是什么样子呢&#xff1f;(๑•̌.•๑) 4.1.拆入第一个接口(串口com接口是用来上传代码的ฅ˙Ⱉ˙ฅ) 4.2.拆入第三个接口&#xff08;SWD Jlink口…

鸿蒙应用开发之富文本(RichText)组件

前面学习了评分组件,现在来学习富文本组件,这个组件用来表示复杂的文本,把文本显示得更加有特色,比如网页一样显示。这种显示会比较复杂,所以应用的场合就会少一点。不过富文本显示最多的,就是即时通讯软件了,比如显示图片与文本,以及一些特殊的字符。 比如显示如下面的…

Unity Standalone File Browser,Unity打开文件选择器

Unity Standalone File Browser&#xff0c;Unity打开文件选择器 下载地址&#xff1a;GitHub链接&#xff1a; https://github.com/gkngkc/UnityStandaloneFileBrowser简单的示例代码 using SFB; using System; using System.IO; using UnityEngine; using UnityEngine.UI;…

【自然语言处理八-transformer实现翻译任务-一(输入)】

自然语言处理八-transformer实现翻译任务-一&#xff08;输入&#xff09; transformer架构数据处理部分模型的输入数据(图中inputs outputs outputs_probilities对应的label)以处理英中翻译数据集为例的代码 positional encoding 位置嵌入代码 鉴于transfomer的重要性&#xf…

Ja-netfilter(idea激活码破解原理)分析

Ja-netfilter&#xff08;idea破解&#xff09;分析 简介 ja-netfilter是一款可以破解jetbrainsIDE系列的javaagent jar 包。 原理简介 通过javaagent创造修改字节码的时机。通过asm修改相关类&#xff0c;达到hook特定方法的作用。主要依赖power&#xff08;rsa方法hook&a…

【Java】Set集合的基本使用

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 一、HashSet集合 1.HashSet集合的特点 2.HashSet常用方法 ①&#xff1a;add(Object o)&#xff1a;向Set集合中添加元素&#xff0c;不允许添加重复数据。 ②&#xff1a;size()&#xff1a;返回Set集合中的元素个数…

NASA数据集——亚洲夏季季风化学与气候影响项目超高灵敏度气溶胶光谱(UHSAS)数据

ACCLIP_Aerosol_AircraftInSitu_WB57_Data 简介 ACCLIP_Aerosol_AircraftInSitu_WB57_Data 是亚洲夏季季风化学与气候影响项目&#xff08;ACCLIP&#xff09;期间收集的原地气溶胶数据。本数据集收录了来自下一代激光质谱仪&#xff08;PALMS-NG&#xff09;、单颗粒烟尘光度…

0101tomcat部署war访问mysql失败-容器间通信-docker项目部署

文章目录 一、简介二、部署1、mysql数据迁移2、docker部署redis3、docker部署tomcat并运行war包 三、报错四、解决1 分析2 解决 结语 一、简介 最近参与开发一个项目&#xff0c;其中一部分系统需要迁移。从阿里云迁移到实体服务器&#xff0c;使用docker部署。系统使用Java语…

RAG进阶之通用文档处理:从RAGFlow、TextMonkey到mPLUG-DocOwl 1.5

前言 我司RAG项目组每个月都会接到一些B端客户的项目需求&#xff0c;做的多了&#xff0c;会发现很多需求是大同小异的&#xff0c;所以我们准备做一个通用的产品&#xff0c;特别是对通用文档的处理 而在此之前&#xff0c;我们则想先学习一下目前市面上各种优秀的解决方法…

IIS服务器更换即将过期的SSL证书

公司IIS服务器证书快要过期&#xff0c;替换证书的步骤&#xff1a; Winr输入mstsc命令&#xff0c;显示远程登录&#xff1b;输入服务器IP以及密码&#xff0c;进行远程登陆登陆IIS服务器&#xff0c;winr输入inetmgr命令显示IIS操控器&#xff1b;选择服务器证书--点击服务器…

成为摄影拍照高手,摄影技术进阶秘籍

一、资料前言 本套摄影高手资料&#xff0c;大小2.02G&#xff0c;共有57个文件。 二、资料目录 DSLR数码单反摄影圣经.pdf photoshop超细的人像后期磨皮及专业美化.docx “失传”的人像拍摄绝技.doc 白加黑减.怎样应用曝光补偿.pdf 标准镜头秘笈&#xff1a;标准镜如何…

快速寻找可以构建出网通信隧道的计算机

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 为加强内网的安全防范&#xff0c;安全管理员往往会限制内网计算机访问互联网&#xff0c;当然不同机构的限制策略是不一样的&#xff0c;有的完全阻断了内网计算机访问…