數據結構-二叉樹
樹
樹的定義
樹是n個結點的有限集合,在一個非空的樹中有且只有一個根結點,其餘結點可分爲m個互不相交的有限子集,其中每個子集又是一棵樹,並且成爲根節點的子樹。
文字定義有點繞,但圖形化很容易記住:
|----------A----------|
| | |
|---B---| C |---D---|
| | | | |
E F G H I
樹的基本概念
樹的基本概念主要有以下幾種:
- 雙親、孩子和兄弟
結點的子樹稱爲結點的孩子;相應地,該結點稱爲其子結點的雙親;具有相同雙親的結點互爲兄弟結點;上圖中,BCD是A的孩子,A是BCD的雙親,同時BCD互爲兄弟結點
- 結點的度
結點的度是指結點孩子結點的個數,A有3個孩子所以度爲3,其他結點也以此類推
- 葉子結點
度爲0的結點稱爲葉子結點,也稱爲終端結點,上圖中CEFGHI爲葉子結點
- 內部結點
度不爲0的結點稱爲內部結點,也稱爲中間結點,上圖中BD爲中間結點
- 結點的層次
根爲第一層結點,根的孩子爲第二層結點,結點的層次即爲目標節點與根結點構成的路徑中結點的個數
- 樹的高度
樹中所有結點的最大層次即爲樹的高度,上圖中樹的高度爲3
- 有序樹/無序樹
樹中每個結點的孩子結點順序不可交換爲有序樹,反之稱爲無序樹
二叉樹
二叉樹的定義:“所有結點的孩子數不超過2的有序樹”。
二叉樹的種類
- 滿二叉樹
所有非葉子結點的孩子數均爲2的二叉樹即爲滿二叉樹,若其深度爲k則樹的結點數爲2k - 1
- 完全二叉樹
高度爲h的二叉樹,除第h層外的每一層結點數均是滿的,且h層的結點不存在左兄弟結點爲空的情況,符合上述條件的二叉樹被稱爲完全二叉樹
- 線索二叉樹
在結點中記錄了某種遍歷方式對應的結點線性順序關係的二叉樹
- 最優二叉樹
最優二叉樹也稱爲哈夫曼樹,它是一類帶權路徑長度最短的樹,樹的帶權路徑長度等於所有葉子結點的帶權路徑長度只和,帶權路徑長度等於權重乘以路徑長度,經典應用-霍夫曼編碼
二叉樹的遍歷
二叉樹的遍歷,是指按某種策略訪問樹中的每個結點,主要有四種方式:先序、中序、後序與按層。爲了後文描述遍歷算法,先對樹結點做一個結構定義:
// tree node
{
data: '', // 結點的值
left: null, // 左孩子,不存在則爲null
right: null // 右孩子,不存在則爲null
}
先序遍歷
先序遍歷是指先訪問根結點,繼而先序遍歷左子樹再先序遍歷右子樹,算法代碼如下:
function preOrder(root) {
if (root === null) {
return;
}
console.log(root.data);
preOrder(root.left);
preOrder(root.right);
}
中序遍歷
中序遍歷是指先中序訪問左子樹,繼而訪問根結點再中序遍歷右子樹,算法代碼如下:
function inOrder(root) {
if (root === null) {
return;
}
inOrder(root.left);
console.log(root.data);
inOrder(root.right);
}
非遞歸方式:
function inOrderWithStack(root) {
let stack = [];
let p = root;
let q = null;
while(p !== null || stack.length !== 0) {
if (p !== null) {
stack.push(p); // 結點入棧
p = p.left;
continue;
}
q = stack.pop(); // 結點出棧
console.log(q.data);
p = q.right;
}
}
後序遍歷
後序遍歷是指先後序訪問左子樹,繼而後序訪問右子樹再訪問根結點,算法代碼如下:
function postOrder(root) {
if (root === null) {
return;
}
postOrder(root.left);
postOrder(root.right);
console.log(root.data);
}
按層遍歷
按層遍歷是指根據樹的層從小到大,每一層從左向右訪問各結點,刷法代碼如下:
function levelOrder(root) {
let p = null; // 用於保存隊列出來的結點
let queue = [];
queue.push(root);
while((p = queue.shift())) { // queue爲空時表達式的值爲undefined,即爲false
consolg.log(p.data);
p.left && queue.push(p.left); // if的一種簡寫
p.right && queue.push(p.right);
}
}
樹的應用
查找二叉樹
查找二叉樹也稱爲二叉排序書,它滿足以下兩個條件:
- 所有結點的左子樹結點值均小於自己
- 所有結點的右子樹結點值均大於自己
查找二叉樹主要作用就是可以進行快速查找,從某查找二叉樹中查找值的算法如下:
function searchBST(root, key) {
let p = root;
while(p !== null && p.data !== key) {
p = key < p.data ? p.left : p.right;
}
return p;
}
查找二叉樹的插入算法與查找算法相似,如下:
function insertBST(root, key) {
let node = treeNodeFrom(key);
let p = root;
let q = null; // 指向p的雙親結點
while(p !== null && p.data !== key) {
q = p;
p = key < p.data ? p.left : p.right;
}
if (p !== null) {
throw new Error('duplicate key');
}
if (q.data > key) {
q.left = node;
}
else {
q.right = node;
}
}
平衡二叉樹
平衡樹滿足以下兩個條件:
- 左子樹和右子樹都是平衡樹
- 左子樹與右子樹的高度差其絕對值不超過1
一顆平衡查找二叉樹的查找效率會優於不平衡的查找二叉樹,但是插入和刪除結點可能涉及需要對樹做調整使其保持平衡,這其中的操作用代碼表示會比較複雜,暫時不展開。
B樹
B樹是一個一般化的二叉查找樹,主要用在數據庫和文件系統上,相信內容移步wiki。