【C語言->數據結構與算法】->樹與二叉樹概念&哈夫曼樹的構造

Ⅰ 樹

由於樹的應用場合很少,不是很實用,所以在此只做簡單介紹。

A. 樹的概念

樹狀圖是一種數據結構,它是由n(n>=1)個有限結點組成一個具有層次關係的集合。把它叫做“樹”是因爲它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:
每個結點有零個或多個子結點;沒有父結點的結點稱爲根結點;每一個非根結點有且只有一個父結點;除了根結點外,每個子結點可以分爲多個不相交的子樹

樹的存儲結構:非線性結構,一對多

關於樹,有幾個相關的詞需要了解:節點, 葉子節點, 節點的度,樹的度,我簡單介紹一下有關樹的這幾個詞。

節點的度:一個節點的子節點數量,稱爲該節點的度。
葉子節點:度爲0的節點。
節點(非葉子節點):度不爲0的節點。
樹的度:樹中節點的度的最大值。

以以下這個樹爲例,我們來搞清楚關於樹的這幾個名詞。

在這裏插入圖片描述
樹的度:4
樹的層數(高度,深度):4
樹中的節點總數:18
葉子節點數:11

B. 樹的表達形式(存儲結構)

由於樹的每一個節點的最大子節點數量是不確定的,意味着其子節點數量不確定,因此,很難給出一個相對穩定的存儲結構。

typedef struct TREE_NODE {
		USER_TYPE data;
		int childCount;
		struct TREE_NODE **children;
	}TREE_NODE;

USER_TYPE 根據用戶需要存入的數據類型改變。
Alt
也可以用連續存儲空間表示一棵樹,如上面這顆可以表示爲👇
在這裏插入圖片描述
左邊一列爲下標。

但是這樣表示有個很大的缺陷:
當對樹中的節點做刪除操作時,不能移動其它節點,會造成空間浪費。

所以由於樹的操作複雜且耗時,所以樹的應用場合很少。

C. 樹的遍歷

樹的遍歷:無缺失、不重複地訪問樹中所有的節點,稱爲樹的遍歷。

a. 廣度優先遍歷(隊列)

	將根節點入隊列;
	while (隊列非空) {
		隊首節點N出隊列,並訪問;
		將N節點的所有子節點入隊列;
	}

以下圖的樹爲例
在這裏插入圖片描述
按照上面僞代碼的步驟,先將根節點入隊列
[A]
隊首節點N出隊列,並訪問;
A  [ ]
將N節點的所有子節點入隊列;
[B, C, D]
隊首節點N出隊列,並訪問;
B  [C, D]
將N節點的所有子節點入隊列;
[C, D, E, F]
按照這樣循環,就可以得到這樣的結果:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
這樣就完成了廣度優先遍歷。

b. 深度優先遍歷(堆棧)

	將根節點入棧;
	while (棧非空) {
		棧頂節點N出棧,並訪問;
		將N節點的所有子節點入棧;
	}

同樣還是這個圖
在這裏插入圖片描述
按照上面僞代碼的步驟,先將根節點入棧。
(A)
棧頂節點N出棧,並訪問;
A  ()
將N節點的所有子節點入棧;
(B, C, D)
然後再循環,棧頂節點N出棧,並訪問;
B  (C, D)
將N節點的所有子節點入棧;
(E, F, C, D)
按此循環,將得到以下結果👇
在這裏插入圖片描述
在這裏插入圖片描述
這樣就完成了深度優先遍歷。

Ⅱ. 二叉樹

A. 二叉樹的有關概念

首先我們需要明確一件事情,二叉樹不是樹!

在計算機科學中,二叉樹是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。

一棵深度爲k,且有2^k-1個結點的二叉樹,稱爲滿二叉樹。這種樹的特點是每一層上的結點數都是最大結點數。

完全二叉樹是效率很高的數據結構,完全二叉樹是由滿二叉樹而引出來的。對於深度爲K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲完全二叉樹。
若設二叉樹的深度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層所有的結點都連續集中在最左邊,這就是完全二叉樹。如下圖。

完全二叉樹有以下幾種形態:
在這裏插入圖片描述

二叉樹和樹最大的區別是:二叉樹嚴格區分左右孩子

三個節點能構成幾種二叉樹?答案是5種。
在這裏插入圖片描述

B. 二叉樹中相關公式

N0 = N2 + 1, 即度爲0的節點數等於度爲2的節點數加1。

一顆高度爲h的二叉樹中,節點個數最多是2h - 1

一顆滿二叉樹的第h層的節點個數:2h-1

高h的滿二叉樹的節點個數:2h - 1

一個一維數組可以看成是一顆完全二叉樹。
一顆擁有n個節點的完全二叉樹,其最後一個非葉子節點的編號(從0開始編號):n / 2 - 1;
一顆擁有n個節點的完全二叉樹,且n爲偶數,其葉子節點的個數爲:n0 = n / 2;
假設完全二叉樹節點編號從0開始,則父節點與子節點(若存在的話),其編號的關係爲:假設父節點編號爲t,則其左孩子節點編號爲2 * t + 1,其有孩子節點編號爲2 * t + 2

C. 二叉樹的存儲結構

typedef struct B_TREE_NODE {
		USER_TYPE data;
		struct B_TREE_NODE *leftChild;
		struct B_TREE_NODE *rightChild;
	}B_TREE_NODE, B_TREE;

有一個需要警惕的寫法👇

typedef struct B_TREE_NODE {
		USER_TYPE data;
		struct B_TREE_NODE *leftChild;
		struct B_TREE_NODE *rightChild;
	}B_NODE, *B_TREE;

有的資料甚至教材上直接在此定義了*B_TREE,這個寫法對程序的可讀性造成了極大的影響,因爲這個類型並不能反映這是指針類型,有的材料爲了書寫方便這樣定義了指針,使得代碼無比醜陋。
在定義類型的時候一定要注意程序的可讀性以及合理性。

Ⅲ 哈夫曼樹及編碼

哈夫曼壓縮方式:頻度壓縮
假設一個文本文件全部由英文字母構成,那麼哈夫曼壓縮就是將出現次數(頻度)最高的字母用最短的編碼來表示。ASCII碼佔1byte,即8bit,那麼將其用2bit的編碼來取代,則每個字符會節省6bit的空間。

A. 構造哈夫曼樹

我用一個字符串當作例子來構造一棵哈夫曼樹。

egeaefebaeeauefffeeffeebe

a. 頻度統計

要構造一棵哈夫曼樹,我們需要先知道每個字符的頻度。

字符 頻度
a 3
b 2
e 12
f 6
g 1
u 1

b. 生成哈夫曼樹

我們需要找到最小頻度的節點,然後生成新節點,生成過程如下👇
第一步
在這裏插入圖片描述
第二步
在這裏插入圖片描述
第三步
在這裏插入圖片描述
第四步
在這裏插入圖片描述
第五步
在這裏插入圖片描述
這樣,一棵哈夫曼樹就構造完成了。

B. 哈夫曼編碼

上一步我們構造出了一棵哈夫曼樹,根據這棵樹我們現在進行編碼。
首先,我們對方向編碼,可以向左是0向右是1,也可以反過來,這只是一個編碼規則。我定爲向左爲0.
在這裏插入圖片描述
接下來就是從根節點沿着這棵樹編碼。
比如e,從根節點向左一次就到了,所以e的編碼爲:0
同樣的,比如f,需要從根節點先向右,再向左,所以f的編碼是:10

按照以上規律,我們可以寫出哈夫曼編碼

字符 編碼
a 110
b 1110
e 0
f 10
g 11110
u 11111

所以egeaefebaeeauefffeeffeebe轉化成哈夫曼編碼爲:
011110011001001110110001101111101010100010100011100

哈夫曼編碼完成。

C. 解碼

解碼依舊是根據構造的哈夫曼樹,以及你制定的編碼規則。

011110011001001110110001101111101010100010100011100

根據這個編碼,我做幾個解碼。
從根節點,先向左,到了e,所以第一個是e,然後回到根節點。
向右,向右,向右,向右,向左,到了g,所以第二個是g,再回到根節點。

根據這個規律,按照之前構造的哈夫曼樹還原出這串字符串。
這就是解碼的實現。

關於哈夫曼編碼的應用,請看我後面的博文,用哈夫曼編碼完成文件的壓縮及解壓縮,是真真實實可以用的程序。
【C語言->數據結構與算法】->哈夫曼壓縮&解壓縮->第一階段->哈夫曼編碼&解碼的實現
【C語言->數據結構與算法】->哈夫曼壓縮&解壓縮->終局->壓縮率百分之八十六

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章