C#数据结构与算法揭秘九

简介:

这节,我们说一说二叉树常见的应用的场景。呵呵。。。。。。。。。。。。。。

定义一个哈夫曼树,首先,要高清楚什么是哈夫曼树。所谓哈夫曼树是又叫最优二叉树,指的是对于一组具有确定权值的叶子结点的具有最小带权路径长度的二叉树。

介绍哈夫曼树的一些基本概念。

(1)路径(Path):从树中的一个结点到另一个结点之间的分支构成这两个结点间的路径。
(2)路径长度(Path Length):路径上的分支数。
(3)树的路径长度(Path Length of Tree):从树的根结点到每个结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。
(4)结点的权(Weight of Node):在一些应用中,赋予树中结点的一个有实际意义的数。
(5)结点的带权路径长度(Weight Path Length of Node):从该结点到树的根结点的路径长度与该结点的权的乘积。

(6)树的带权路径长度(WPL) :树中所有叶子结点的带权路径长度之和,记为   ∑==n1kkkWPLLW

具体情况,如图a所示:

在下图所示的的四棵二叉树, 都有 4个叶子结点, 其权值分别为 1、 2、 3、4,它们的带权路径长度分别为:

wl=(1+2+3+4)*2=20  如图所示:

wl=(3+4)*3+2*2+1=26 如图所示:

WPL=1×3+2×3+3×2+4×1=19  如图所示:

WPL=2×1+1×2+3×3+4×3=25  如图所示:

那么, 如何构造一棵哈夫曼树呢?哈夫曼最早给出了一个带有一般规律的算法,俗称哈夫曼算法。现叙述如下:
(1)根据给定的n个权值{w1,w2,…,wn},构造n棵只有根结点的二叉树集合F={T1,T2,…,Tn};
(2)从集合 F 中选取两棵根结点的权最小的二叉树作为左右子树,构造一棵新的二叉树, 且置新的二叉树的根结点的权值为其左、 右子树根结点权值之和。
(3)在集合 F 中删除这两棵树,并把新得到的二叉树加入到集合 F 中;
(4)重复上述步骤,直到集合中只有一棵二叉树为止,这棵二叉树就是哈夫曼树。他的步骤如下图所示:

 

由哈夫曼树的构造算法可知, 用一个数组存放原来的 n个叶子结点和构造过程中临时生成的结点,数组的大小为 2n-1。所以,哈夫曼树类 HuffmanTree中有两个成员字段: data数组用于存放结点, leafNum表示哈夫曼树叶子结点的数目。结点有四个域,一个域 weight,用于存放该结点的权值;一个域 lChild,用于存放该结点的左孩子结点在数组中的序号;一个域 rChild,用于存放该结点的右孩子结点在数组中的序号; 一个域 parent, 用于判定该结点是否已加入哈夫曼树中。哈夫曼树结点的结构为,如图所示:

所以,结点类 Node有 4 个成员字段,weight 表示该结点的权值,lChild和rChild分别表示左、右孩子结点在数组中的序号,parent 表示该结点是否已加入哈夫曼树中,如果 parent的值为-1,表示该结点未加入到哈夫曼树中。当该结点已加入到哈夫曼树中时,parent的值为其双亲结点在数组中的序号。

结点类 Node的定义如下:
public class Node
{
private int weight; //结点权值
private int lChild; //左孩子结点
private int rChild; //右孩子结点
private int parent; //父结点

//结点权值属性
public int Weight
{
get
{
return weight;
}
set
{
weight = value;
}
}

//左孩子结点属性
public int LChild
{
get
{
return lChild;
}
set
{
lChild = value;
}
}

//右孩子结点属性
public int RChild
{
get
{
return rChild;
}
set
{
rChild = value;

}
}

//父结点属性
public int Parent
{
get
{
return parent;
}
set
{
parent = value;
}
}


//构造器

//默认为空  就是空值了
public Node()
{
weight = 0;
lChild = -1;
rChild = -1;
parent = -1;
}

//构造器 

//为权重,做结点,有结点都赋值的吧
public Node(int w, int lc, int rc, int p)
{
weight = w;
lChild = lc;
rChild = rc;
parent = p;
}
}
哈夫曼树类 HuffmanTree中只有一个成员方法 Create,它的功能是输入 n个叶子结点的权值,创建一棵哈夫曼树。哈夫曼树类 HuffmanTree的实现如下。 如图所示:


public class HuffmanTree
{
private Node[] data; //结点数组
private int leafNum; //叶子结点数目

//索引器 运用当前的索引器来遍历的吧
public Node this[int index]
{

get
{
return data[index];
}
set
{
data[index] = value;
}
}

//叶子结点数目属性
public int LeafNum
{
get
{
return leafNum;
}
set
{
leafNum = value;
}
}

//构造器   创建一个2*n-1二叉树   带权叶子的结点为n
public HuffmanTree (int n)
{
data = new Node[2*n-1];
leafNum = n;
}

//创建哈夫曼树   在创建哈夫曼树 就开始了
public void Create()
{
int m1;
int m2;
int x1;
int x2;

//输入 n个叶子结点的权值
for (int i = 0; i < this.leafNum; ++i)
{
data[i].Weight = Console.Read();
}

//处理 n 个叶子结点,建立哈夫曼树
for (int i = 0; i < this.leafNum - 1; ++i)
{
max1 = max2 = Int32.MaxValue;
tmp1 = tmp2 = 0;

//找权重的 最小的结点加入到哈夫曼树。
//在全部结点中找权值最小的两个结点
for (int j = 0; j < this.leafNum + i; ++j)
{
if ((data[i].Weight < max1)
&& (data[i].Parent == -1))
{
max2 = max1;
tmp2 = tmp1;
tmp1 = j;
max1 = data[j].Weight;
}
else if ((data[i].Weight < max2)
&& (data[i].Parent == -1))
{
max2 = data[j].Weight;
tmp2 = j;
}
}

data[tmp1].Parent = this.leafNum + i; //加入其中
data[this.leafNum + i].Weight = data[tmp1].Weight
+ data[tmp2].Weight;
data[this.leafNum + i].LChild = tmp1;
data[this.leafNum + i].RChild = tmp2;
}
}
}

//这个算法的时间的复杂度是O(n²)  具体的情况如上图所示。

 我们花了这么大的篇幅,介绍了哈夫曼树,他能做什么,就是哈夫曼编码。哈夫曼就是把互相的路径写成01,这个应用最常用的应用,就是想win-zip,win-rar就是基于这个基础。

应用举例二。编写算法,在二叉树中查找值为 value的结点。

算法实现如下:
Node<T> Search(Node<T> root, T value)
{
Node<T> p = root;

if (p == null)
{
return null;
}

if (p.Data.Equals(value))
{
return p;
}

if (p.LChild != null)
{
return Search(p.LChild, value);
}

if (p.RChild != null)
{
return Search(p.RChild, value);
}

return null;    由于用到了递归,这个算法的时间的复杂度是O(n²) 如图所示:


}

  统计出二叉树中叶子结点的数目。

  就是 统计其中二叉树的没有子节点的数目。通过递归来查找的。递归实现该算法。如果二叉树为空,则返回 0;如果二叉树只有一个结点,则根结点就是叶子结点,返回 1,否则返回根结点的左分支的叶子结点数目和右分支的叶子结点数目的和。

int CountLeafNode(Node<T> root)
{
if (root == null)
{
return 0;
}
else if (root.LChild == null && root.RChild == null)
{
return 1;
}
else
{

return (CountLeafNode(root.LChild) +
CountLeafNode(root.RChild));
}

由于用到了递归算法的实现,其时间复杂度是O(n²),其中的算法的实现,如图所示:


}

最后一个实例,我们来聊聊一下实际的商业的项目中用到到的树的数据结构,就是比如你构建一个父子级的菜单,这个就是树的典型的应用。这在做数据库设计时候,一个字段ParentCode指向了富集。他最终的效果,如图所示:

    这就是树形数据结构的,典型应用。下篇文章,我们来讨论一下图形结构。

目录
相关文章
|
2月前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
62 1
|
5月前
|
搜索推荐 算法 C#
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
54 1
|
19天前
|
存储 算法 C#
C#编程与数据结构的结合
【4月更文挑战第21天】本文探讨了C#如何结合数据结构以构建高效软件,强调数据结构在C#中的重要性。C#作为面向对象的编程语言,提供内置数据结构如List、Array和Dictionary,同时也支持自定义数据结构。文章列举了C#实现数组、链表、栈、队列等基础数据结构的示例,并讨论了它们在排序、图算法和数据库访问等场景的应用。掌握C#数据结构有助于编写高性能、可维护的代码。
|
2月前
|
搜索推荐 C#
C#实现插入排序算法
C#实现插入排序算法
12 1
|
2月前
|
搜索推荐 C#
C#实现选择排序算法
C#实现选择排序算法
17 2
|
2月前
|
搜索推荐 C#
C#实现冒泡排序算法
C#实现冒泡排序算法
20 0
|
2月前
|
存储 程序员 编译器
C#基本数据结构
C#基本数据结构
11 0
|
4月前
|
算法 C#
C# .Net Core bytes转换为GB/MB/KB 算法
C# .Net Core bytes转换为GB/MB/KB 算法
46 0
|
5月前
|
存储 算法 数据处理
C# | 上位机开发新手指南(十一)压缩算法
流式压缩 流式压缩是一种能够实时处理数据流的压缩方式,例如音频、视频等实时传输的数据。 通过流式压缩算法,我们可以边读取边压缩数据,并能够随时输出已压缩的数据,以确保数据的实时性和减少存储和传输所需的带宽。 块压缩 块压缩则是将数据划分为固定大小的块,在每个块内进行独立的压缩处理。块压缩通常适用于文件、存储、传输等离线数据处理场景。 字典压缩 字典压缩是一种基于字典的压缩算法,通过建立一个字典来存储一组重复出现的字符串,并将这些字符串替换成字典中相应的索引,从而减少数据的存储和传输。字典压缩算法可以更好地处理数据中的重复模式,因为它们可以通过建立字典来存储和恢复重复出现的字符串。
49 0
C# | 上位机开发新手指南(十一)压缩算法
|
5月前
|
算法 C# 数据安全/隐私保护
C# | 上位机开发新手指南(十)加密算法——ECC
本篇文章我们将继续探讨另一种非对称加密算法——ECC。 严格的说,其实ECC并不是一种非对称加密算法,它是一种基于椭圆曲线的加密算法,广泛用于数字签名和密钥协商。 与传统的非对称加密算法(例如RSA)不同,ECC算法使用椭圆曲线上的点乘法来生成密钥对和进行加密操作,而不是使用大数分解等数学算法。这使得ECC算法具有相同的安全性和强度,但使用更少的位数,因此在资源受限的环境中具有优势。 ECC算法虽然使用公钥和私钥进行加密和解密操作,但是这些操作是基于点乘法实现的,而不是基于大数分解等算法实现的。因此,ECC算法可以被视为一种非对称加密算法的变体,但是它与传统的非对称加密算法有所不同。
136 0
C# | 上位机开发新手指南(十)加密算法——ECC