樹結構示意圖
樹的術語
樹(tree):n個節點構成的有限集合,當n=0時稱為空樹,對於任一非空樹,具有一個根節點用r表示,其余節點可分為m(m>0)個互不相交的有限集T1,T2,T3等,其中每一個集合本身又是一棵樹,稱為原來樹的子樹
節點的度(Degree):節點的子樹個數
樹的度:樹的所有節點中最大的度數
葉節點(Leaf):度為0的節點(也稱為葉子節點)
父節點(Parent):有子樹的節點是其子樹的根節點的父節點
子節點(Child):若A節點是B節點的父節點,則稱B節點是A節點的子節點;子節點也稱為孩子節點
兄弟節點(Sibling):具有同一父節點的各節點彼此是兄弟節點
路徑和路徑長度:從節點n1到nk的路徑為一個節點序列n1,n2......nk,ni是ni+1的父節點,路徑包含邊的個數為路徑的長度
節點的層次(Level):規定根節點在1層,其他任一節點的層數是其父節點的層數加一
樹的深度(Depth):樹中所有節點中的最大層次是這棵樹的深度
樹的表示方法
二叉樹
如果樹中每個節點最多只能有兩個子節點,這樣的樹就稱為二叉樹
二叉樹的定義:
1、二叉樹可以為空,也就是沒有節點
2、若不為空,則它是由根節點和稱為其左子樹TL和右子樹TR的兩個互不相交的二叉樹組成
二叉樹有五種形態:
二叉樹的特性:
一個二叉樹第i層的最大節點樹為:2^(i-1),i>=1
深度為k的二叉樹有最大節點總數為:2^k-1,k>=1
對任何非空二叉樹T,若n0表示葉節點的個數,n2是度為2的非葉節點個數,那么兩者滿足關系n0=n2+1
完美二叉樹
在二叉樹中,除了最下一層的葉節點外,每層節點都有2個子節點,就構成了滿二叉樹
完全二叉樹
除二叉樹最后一層外,其他各層的節點數都達到最大個數,且最后一層從左向右的葉節點連續存在,只缺右側若干節點,完美二叉樹是特殊的完全二叉樹
二叉樹的存儲
二叉樹常見的存儲方式是數組和鏈表
使用數組
完全二叉樹:按從上至下、從左至右順序存儲
非完全二叉樹:非完全二叉樹要轉成完全二叉樹才可以按照上面的方案存儲,但是會造成很大的空間浪費
二叉樹最常見的方式還是使用鏈表存儲
每個節點封裝成一個Node,Node中包含存儲的數據,左節點的引用,右節點的引用
二叉搜索樹
二叉搜索樹(BST,Binary Search Tree)也稱二叉排序樹或者二叉查找樹
二叉搜索樹是一顆二叉樹,可以空
如果不為空,滿足一下性質:
- 非空左子樹的所有鍵值小於其根節點的鍵值
- 非空右子樹的所有鍵值大於其根節點的鍵值
- 左、右子樹本身也都是二叉搜索樹
二叉搜索樹的特點:
二叉搜索樹的特點就是相對較小的值總是保存在左節點上,相對較大的值總是保存在右節點上,二叉搜索樹的查找效率特別高
二叉搜索樹常見的操作方法
<1> insert(key):向樹中插入一個新的鍵
<2> search(key):在樹中查找一個鍵,如果節點存在,則返回true,如果不存在,則返回false
<3> inOrderTraverse:通過中序遍歷方式遍歷所有節點
<4> preOrderTraverse:通過先序遍歷方式遍歷所有節點
<5> postOrderTraverse:通過后序遍歷方式遍歷所有節點
<6> min:返回樹中最小的值/鍵
<7> max:返回樹中最大的值/鍵
<8> remove(key):從樹中移除某個鍵
封裝二叉搜索樹
//封裝二叉搜索樹 function BinarySearchTree() { function Node(key) { this.key = key this.left = null this.right = null } this.root = null }
insert方法
//封裝二叉搜索樹 function BinarySearchTree() { function Node(key) { this.key = key this.left = null this.right = null } this.root = null BinarySearchTree.prototype.insert = function (key) { //!、根據key創建節點 var newNode = new Node(key) //2、判斷是否存在根節點 if (this.root == null) { this.root = newNode } else { this.insertNode(this.root, newNode) } } //判斷節點大小的遞歸方法 BinarySearchTree.prototype.insertNode = function (node, newNode) { if (newNode.key < node.key) { //如果新節點key小於根節點的key就向左查找 if (node.left == null) { //沒有左節點,就放在這里當做左節點 node.left = newNode } else { //如果有左節點,遞歸調用 this.insertNode(node.left, newNode) } } else { //如果新節點key大於根節點的key就向右查找 if (node.right == null) { //沒有右節點,就放在這里當做右節點 node.right = newNode } else { //如果有右節點,遞歸調用 this.insertNode(node.right, newNode) } } } }
測試:
var tree = new BinarySearchTree() tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25)
二叉樹的遍歷
二叉樹遍歷常見的有三種方式:先序遍歷、中序遍歷、后序遍歷、層序遍歷(使用較少)
先序遍歷
先訪問根節點,然后再訪問左子節點,最后訪問右子節點
//遍歷 BinarySearchTree.prototype.preOrderTraversal = function (handler) { this.preOrderTraversalNode(this.root, handler) } BinarySearchTree.prototype.preOrderTraversalNode = function (node, handler) { if(node != null) { //處理經過的節點 handler(node.key) //處理經過節點的左子節點 this.preOrderTraversalNode(node.left,handler) //處理經過節點的右子節點 this.preOrderTraversalNode(node.right,handler) } }
測試:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) var result = '' tree.preOrderTraversal(function (key) { result += key + ' ' }) alert(result)
中序遍歷
先訪問左子節點,然后再訪問根節點,最后訪問右子節點
BinarySearchTree.prototype.midOrderTraversal = function (handler) { this.midOrderTraversalNode(this.root, handler) } BinarySearchTree.prototype.midOrderTraversalNode = function (node,handler) { if(node != null) { //處理經過的節點的左子節點 this.midOrderTraversalNode(node.left,handler) //處理經過的節點 handler(node.key) //處理經過節點的右子節點 this.midOrderTraversalNode(node.right,handler) } }
測試:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) var result = '' tree.midOrderTraversal(function (key) { result += key + ' ' }) alert(result)
后序遍歷
先訪問左子節點,然后再訪問右子節點,最后訪問根節點
BinarySearchTree.prototype.postOrderTraversal = function (handler) { this.postOrderTraversalNode(this.root, handler) } BinarySearchTree.prototype.postOrderTraversalNode = function (node,handler) { if(node != null) { //處理經過的節點的左子節點 this.postOrderTraversalNode(node.left,handler) //處理經過節點的右子節點 this.postOrderTraversalNode(node.right,handler) //處理經過的節點 handler(node.key) } }
測試:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) var result = '' tree.postOrderTraversal(function (key) { result += key + ' ' }) alert(result)
獲取最大值最小值
BinarySearchTree.prototype.max = function () { //獲取根節點 var node = this.root //依次向右查找,直到節點為null var key = null while (node != null) { key = node.key node = node.right } return key } BinarySearchTree.prototype.min = function () { //獲取根節點 var node = this.root var key = null //依次向左查找,直到節點為null while (node != null) { key = node.key node = node.left } return key }
測試
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) alert(tree.max()) alert(tree.min())
搜索特定的值
BinarySearchTree.prototype.search = function (key) { //獲取根節點 var node = this.root //循環搜索key while (node != null) { if (key < node.key) { node = node.left } else if (key > node.key) { node = node.right } else { return true } } return false }
測試:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) alert(tree.search(25)) alert(tree.search(2))
刪除操作
思路:
1、找到要刪除的節點,如果沒有找到,不需要刪除
2、找到要刪除的節點
1)刪除葉子節點
2)刪除只有一個子節點的節點
3)刪除有兩個子節點的節點
BinarySearchTree.prototype.remove = function (key) { //1、尋找刪除的節點 //定義變量,保存一些信息 var current = this.root var parent = null var isLeftChild = true //尋找刪除的節點 while (current.key != key) { parent = current if (key < current.key) { isLeftChild = true current = current.left } else { isLeftChild = false current = current.right } //沒有找到,已經找到了最后的節點仍然沒有找到 if (current == null) return false } //2、根據情況刪除節點 //刪除的節點是葉子節點 if (current.left == null && current.right == null) { if (current == this.root) { this.root = null } else if (isLeftChild) { parent.left = null } else { parent.right = null } } // 刪除的節點有一個子節點 else if (current.right == null) { if (current == this.root) { this.root = current.left } else if (isLeftChild) { parent.left = current.left } else { parent.right = current.left } } else if (current.left == null) { if (current == this.root) { this.root = current.right } else if (isLeftChild) { parent.left = current.right } else { parent.right = current.right } } // 刪除的節點有兩個子節點 else { //獲取后繼節點 var successor = this.getSuccessor(current) //判斷是否根節點 if(current == this.root) { this.root = successor }else if(isLeftChild) { parent.left = successor } else { parent.right = successor } successor.left = current.left } } //找后繼節點的方法 BinarySearchTree.prototype.getSuccessor = function (delNode) { //定義變量 var successor = delNode var current = delNode.right var successorParent = delNode //循環查找 while(current!=null){ successorParent = successor successor = current current = current.left } //判斷尋找的后繼節點是否直接就是delNode的right節點 if(successor!=delNode.right){ successorParent.left = successor.right successor.right = delNode.right } return successor }
測試:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) tree.remove(9) tree.remove(7) tree.remove(15) var result = '' tree.postOrderTraversal(function (key) { result += key + ' ' }) alert(result)