本文包括:二叉搜尋樹(建立、遍歷、搜尋、插入等)、JavaScript 實現翻轉二叉樹
什麼是二叉樹?
二叉樹的定義:二叉樹的每個結點至多隻有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。
二叉查詢樹(BST):又稱為是二叉排序樹(Binary Sort Tree)或二叉搜尋樹。二叉查詢樹是二叉樹的一種,但是它只允許你在左側節點儲存(比父節點)小的值,在右側節點儲存(比父節點)大(或者等於)的值。
建立一個二叉查詢樹
首先建立一個 BinarySearchTree
類。
// 使用了 ES6 的 Class 語法
class BinarySearchTree {
constructor() {
this.root = null
}
Node(key) {
let left = null
let right = null
return {
key,
left,
right
}
}
}
複製程式碼
來看一下二叉查詢樹的資料結構組織方式(沒有找到二叉搜尋樹的先用二叉樹的代替一下):
二叉樹是通過指標(指向下一個節點)來表示節點之間的關係的,所以需要在宣告 Node 的時候,定義兩個指標,一個指向左邊,一個指向右邊。 還需要宣告一個 root 來儲存樹的根元素。
向樹中插入一個鍵(節點)
class BinarySearchTree {
// ...省略前面的
insert (key) {
let newNode = this.Node(key)
if (this.root === null) {
// 如果根節點為空,那麼插入的節點就為根節點
this.root = newNode
} else {
// 如果根節點不為空
this.insertNode(this.root, newNode)
}
}
insertNode (node, newNode) {
// 當新節點比父節點小,插入左邊
if (newNode.key < node.key) {
// 左邊沒有內容則插入
if (node.left === null) {
node.left = newNode
} else {
// 有內容就繼續遞迴,直到沒有內容然後可以插入
this.insertNode(node.left, newNode)
}
} else {
// 右邊和左邊相同,不重複說明
if (node.right === null) {
node.right = newNode
} else {
this.insertNode(node.right, newNode)
}
}
}
}
複製程式碼
因為使用了 class 所以沒有學過 class 的同學可以先看一下 ES6 的 class,再來看文章。
仔細分析上面的程式碼,多看幾遍就可以瞭解其中的奧妙(也可以自己在遊覽器中執行一下,插入幾個值試一下)。
執行一遍試一下:
let m = new BinarySearchTree()
m.insert(5)
m.insert(4)
m.insert(3)
m.insert(6)
m.insert(7)
複製程式碼
會得到這樣的結構:
{
key: 5,
left: {
key: 4,
left: {
key: 3,
left: null,
right: null
},
right: null
},
right: {
key: 6,
left: null,
right: {
key: 7,
left: null,
right: null
}
}
}
複製程式碼
emmm,真複雜(自己看的都頭暈),還是畫個圖吧。
會生成這樣一個二叉查詢樹~,插入功能就算完成啦!
樹的遍歷
遍歷一棵樹是指訪問樹的每個節點並對它們進行某種操作的過程。訪問樹會有三種方法:中序、先序、後續。下面分別講解
中序遍歷
中序遍歷是一種以上行順序訪問 BST 所有節點的遍歷方式,也就是從最小到最大的順序進行訪問所有節點。具體方法,看程式碼吧,配上圖多看兩遍程式碼就能明白了(我是這麼認為的)。
class BinarySearchTree {
// ...省略前面的
inOrderTraverse (callback) {
this.inOrderTraverseNode(this.root, callback)
}
inOrderTraverseNode (node, callback) {
if (node !== null) {
this.inOrderTraverseNode(node.left, callback)
callback(node.key)
this.inOrderTraverseNode(node.right, callback)
}
}
}
複製程式碼
同樣,用圖展示一下遍歷的過程,具體過程看程式碼多思考一下。
先序遍歷
先序遍歷會先訪問節點本身,然後再訪問它的左側子節點,最後再訪問右側的節點。
class BinarySearchTree {
// ...省略前面的
preOrderTraverse (callback) {
this.preOrderTraverseNode(this.root, callback)
}
preOrderTraverseNode (node, callback) {
if (node !== null) {
callback(node.key)
this.preOrderTraverseNode(node.left, callback)
this.preOrderTraverseNode(node.right, callback)
}
}
}
複製程式碼
仔細看程式碼,發現和中序遍歷的區別不過是先執行了 callback
然後再遍歷左右。
後序遍歷
後序遍歷則是先訪問節點的後代節點,然後再訪問節點本身。實現:
class BinarySearchTree {
// ...省略前面的
postOrderTraverse (callback) {
this.postOrderTraverseNode(this.root, callback)
}
postOrderTraverseNode (node, callback) {
if (node !== null) {
this.postOrderTraverseNode(node.left, callback)
this.postOrderTraverseNode(node.right, callback)
callback(node.key)
}
}
}
複製程式碼
再仔細看程式碼,發現和中序遍歷的區別不過是先執行了遍歷了左右,最後執行了 callback
。
慣例,畫張圖~
三種遍歷方式講完啦,不懂的可以多看幾遍程式碼哦~
搜尋二叉搜尋樹中的值
在樹中,通常有三種經常使用的搜尋型別:
- 搜尋最大值
- 搜尋最小值
- 搜尋特定值
下面一一列舉
搜尋最小和最大值
首先我們知道二叉搜尋樹中的最小值在最左邊,最大值在最右邊。既然知道這個,那麼實現搜尋最大和最小就十分簡單了。所以直接上程式碼:
class BinarySearchTree {
// ...省略前面的
// 搜尋最小
min () {
return this.minNode(this.root)
}
minNode (node) {
if (node) {
// 如果節點存在,而且左邊不為 null
while (node && node.left !== null) {
node = node.left
}
return node.key
}
// 如果樹為空,則返回 null
return null
}
// 搜尋最大
max () {
return this.maxNode(this.root)
}
maxNode (node) {
if (node) {
while (node && node.right !== null) {
node = node.right
}
return node.key
}
return null
}
}
複製程式碼
搜尋特定的值
基本上的思路和遍歷節點差不多,具體看程式碼。
class BinarySearchTree {
// ...省略前面的
search (key) {
return this.searchNode(this.root, key)
}
searchNode (node, key) {
if (node === null) {
return false
}
// 如果 key 比節點的值小,那麼搜尋左邊的子節點,下面的相反
if (key < node.key) {
return this.searchNode(node.left, key)
} else if (key > node.key) {
return this.searchNode(node.right, key)
} else {
return true
}
}
}
複製程式碼
翻轉二叉樹
翻轉一個二叉樹,直觀上看,就是把二叉樹的每一層左右順序倒過來。
例如:
Input:
4
/ \
2 7
/ \ / \
1 3 6 9
複製程式碼
Output:
4
/ \
7 2
/ \ / \
9 6 3 1
複製程式碼
仔細看就是先把最底下的節點反轉,然後上一個節點再翻轉。例如:1 - 3 反轉成 3 - 1,6 - 9 反轉成 9 - 6, 然後再讓 2 - 7 反轉。當然反過來也一樣,先反轉 2 - 7 也是可以的。
所以具體的過程是:
- 翻轉根節點的左子樹(遞迴呼叫當前函式)
- 翻轉根節點的右子樹(遞迴呼叫當前函式)
- 交換根節點的左子節點與右子節點
最後看一下實現的程式碼:
class BinarySearchTree {
// ...省略前面的
invertTree (node = this.root) {
if (node === null) {
return
}
this.invertTree(node.left)
this.invertTree(node.right)
this.exchange(node)
}
exchange (node) {
let temp = node.left
node.left = node.right
node.right = temp
}
}
複製程式碼
這樣就簡單實現啦,舒服舒服~
程式碼
全部程式碼在這裡~
class BinarySearchTree {
constructor() {
this.root = null
}
Node(key) {
let left = null
let right = null
return {
key,
left,
right
}
}
insert(key) {
let newNode = this.Node(key)
if (this.root === null) {
// 如果根節點為空,那麼插入的節點就為根節點
this.root = newNode
} else {
this.insertNode(this.root, newNode)
}
}
insertNode(node, newNode) {
console.log(node)
if (newNode.key < node.key) {
if (node.left === null) {
node.left = newNode
} else {
this.insertNode(node.left, newNode)
}
} else {
if (node.right === null) {
node.right = newNode
} else {
this.insertNode(node.right, newNode)
}
}
}
inOrderTraverse(callback) {
this.inOrderTraverseNode(this.root, callback)
}
inOrderTraverseNode(node, callback) {
if (node !== null) {
this.inOrderTraverseNode(node.left, callback)
callback(node.key)
this.inOrderTraverseNode(node.right, callback)
}
}
preOrderTraverse(callback) {
this.preOrderTraverseNode(this.root, callback)
}
preOrderTraverseNode(node, callback) {
if (node !== null) {
callback(node.key)
this.preOrderTraverseNode(node.left, callback)
this.preOrderTraverseNode(node.right, callback)
}
}
postOrderTraverse(callback) {
this.postOrderTraverseNode(this.root, callback)
}
postOrderTraverseNode(node, callback) {
if (node !== null) {
this.postOrderTraverseNode(node.left, callback)
this.postOrderTraverseNode(node.right, callback)
callback(node.key)
}
}
// 搜尋最小
min() {
return this.minNode(this.root)
}
minNode(node) {
if (node) {
// 如果節點存在,而且左邊不為 null
while (node && node.left !== null) {
node = node.left
}
return node.key
}
// 如果樹為空,則返回 null
return null
}
// 搜尋最大
max() {
return this.maxNode(this.root)
}
maxNode(node) {
if (node) {
while (node && node.right !== null) {
node = node.right
}
return node.key
}
return null
}
search(key) {
return this.searchNode(this.root, key)
}
searchNode(node, key) {
console.log('node-', node, '---', node === null, '-key-', key)
if (node === null) {
return false
}
// 如果 key 比節點的值小,那麼搜尋左邊的子節點,下面的相反
if (key < node.key) {
return this.searchNode(node.left, key)
} else if (key > node.key) {
return this.searchNode(node.right, key)
} else {
console.log('didi')
return true
}
}
invertTree (node = this.root) {
if (node === null) {
return
}
this.invertTree(node.left)
this.invertTree(node.right)
this.exchange(node)
}
exchange (node) {
let temp = node.left
node.left = node.right
node.right = temp
}
}
複製程式碼
最後
文章是自己的學習的一個記錄,如果能夠順便幫助大家學習一下,那就再好不過了。
但是因為本人技術技術有限,所以文章難免會有疏漏,歡迎指出。
參考
- 書籍:《學習 JavaScript 資料結構與演算法》
- [資料結構][Leetcode]翻轉二叉樹