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指向了富集。他最終的效果,如圖所示:

    這就是樹形數據結構的,典型應用。下篇文章,我們來討論一下圖形結構。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM