二叉查找樹,又叫二叉排序樹,二叉搜索樹,是一種有特定規則的二叉樹,定義以下:node
二叉查找樹的特色是,一直往左兒子往下找左兒子,能夠找到最小的元素,一直往右兒子找右兒子,能夠找到最大的元素。算法
看起來,咱們能夠用它來實現元素排序,但是咱們卻使用了二叉堆來實現了堆排序,由於二叉查找樹不保證是一個平衡的二叉樹,最壞狀況下二叉查找樹會退化成一個鏈表,也就是全部節點都沒有左子樹或者沒有右子樹,樹的層次太深致使排序性能太差。segmentfault
使用二分查找,能夠很快在一顆二叉查找樹中找到咱們須要的值。數組
咱們來分析二叉查找樹添加,刪除,查找元素的方法。數據結構
如下是一個二叉查找樹的表示:併發
// 二叉查找樹 type BinarySearchTree struct { Root *BinarySearchTreeNode // 樹根節點 } // 二叉查找樹節點 type BinarySearchTreeNode struct { Value int64 // 值 Times int64 // 值出現的次數 Left *BinarySearchTreeNode // 左子樹 Right *BinarySearchTreeNode // 右字樹 } // 初始化一個二叉查找樹 func NewBinarySearchTree() *BinarySearchTree { return new(BinarySearchTree) }
一個節點表明一個元素,節點的Value
值是用來進行二叉查找的關鍵,當Value
值重複時,咱們將值出現的次數Times
加 1。添加元素代碼以下:數據結構和算法
// 添加元素 func (tree *BinarySearchTree) Add(value int64) { // 若是沒有樹根,證實是顆空樹,添加樹根後返回 if tree.Root == nil { tree.Root = &BinarySearchTreeNode{Value: value} return } // 將值添加進去 tree.Root.Add(value) } func (node *BinarySearchTreeNode) Add(value int64) { if value < node.Value { // 若是插入的值比節點的值小,那麼要插入到該節點的左子樹中 // 若是左子樹爲空,直接添加 if node.Left == nil { node.Left = &BinarySearchTreeNode{Value: value} } else { // 不然遞歸 node.Left.Add(value) } } else if value > node.Value { // 若是插入的值比節點的值大,那麼要插入到該節點的右子樹中 // 若是右子樹爲空,直接添加 if node.Right == nil { node.Right = &BinarySearchTreeNode{Value: value} } else { // 不然遞歸 node.Right.Add(value) } } else { // 值相同,不須要添加,值出現的次數加1便可 node.Times = node.Times + 1 } }
若是添加元素時是顆空樹,那麼初始化根節點。函數
而後添加的值和根節點比較,判斷是要插入到根節點左子樹仍是右子樹,仍是不用插入。性能
當值比根節點小時,元素要插入到根節點的左子樹中,當值比根節點大時,元素要插入到根節點的右子樹中,相等時不插入,只更新次數。學習
而後再分別對根節點的左子樹和右子樹進行遞歸操做便可。
查找最大值和最小值比較簡單,一直往左兒子往下找左兒子,能夠找到最小的元素,一直往右兒子找右兒子,能夠找到最大的元素。
// 找出最小值的節點 func (tree *BinarySearchTree) FindMinValue() *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } return tree.Root.FindMinValue() } func (node *BinarySearchTreeNode) FindMinValue() *BinarySearchTreeNode { // 左子樹爲空,表面已是最左的節點了,該值就是最小值 if node.Left == nil { return node } // 一直左子樹遞歸 return node.Left.FindMinValue() } // 找出最大值的節點 func (tree *BinarySearchTree) FindMaxValue() *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } return tree.Root.FindMaxValue() } func (node *BinarySearchTreeNode) FindMaxValue() *BinarySearchTreeNode { // 右子樹爲空,表面已是最右的節點了,該值就是最大值 if node.Right == nil { return node } // 一直右子樹遞歸 return node.Right.FindMaxValue() }
二分查找的技巧也在這裏有用武之地了:
// 查找節點 func (tree *BinarySearchTree) Find(value int64) *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } return tree.Root.Find(value) } func (node *BinarySearchTreeNode) Find(value int64) *BinarySearchTreeNode { if value == node.Value { // 若是該節點剛剛等於該值,那麼返回該節點 return node } else if value < node.Value { // 若是查找的值小於節點值,從節點的左子樹開始找 if node.Left == nil { // 左子樹爲空,表示找不到該值了,返回nil return nil } return node.Left.Find(value) } else { // 若是查找的值大於節點值,從節點的右子樹開始找 if node.Right == nil { // 右子樹爲空,表示找不到該值了,返回nil return nil } return node.Right.Find(value) } }
若是是空樹,返回 nil,不然與根節點比較。
若是剛恰好等於根節點的值,返回該節點,不然根據值的比較,繼續往左子樹或右字樹遞歸查找。
與查找指定元素同樣,只不過追蹤的是該元素的父親節點。
// 查找指定節點的父親 func (tree *BinarySearchTree) FindParent(value int64) *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } // 若是根節點等於該值,根節點其沒有父節點,返回nil if tree.Root.Value == value { return nil } return tree.Root.FindParent(value) } func (node *BinarySearchTreeNode) FindParent(value int64) *BinarySearchTreeNode { // 外層沒有值相等的斷定,由於在內層已經斷定完畢後返回父親節點。 if value < node.Value { // 若是查找的值小於節點值,從節點的左子樹開始找 leftTree := node.Left if leftTree == nil { // 左子樹爲空,表示找不到該值了,返回nil return nil } // 左子樹的根節點的值恰好等於該值,那麼父親就是如今的node,返回 if leftTree.Value == value { return node } else { return leftTree.FindParent(value) } } else { // 若是查找的值大於節點值,從節點的右子樹開始找 rightTree := node.Right if rightTree == nil { // 右子樹爲空,表示找不到該值了,返回nil return nil } // 右子樹的根節點的值恰好等於該值,那麼父親就是如今的node,返回 if rightTree.Value == value { return node } else { return rightTree.FindParent(value) } } }
代碼相應的進行了調整,方便獲取到父親節點。
若是返回的父親節點爲空,表示沒有父親。
刪除元素有四種狀況:
代碼實現以下:
// 刪除指定的元素 func (tree *BinarySearchTree) Delete(value int64) { if tree.Root == nil { // 若是是空樹,直接返回 return } // 查找該值是否存在 node := tree.Root.Find(value) if node == nil { // 不存在該值,直接返回 return } // 查找該值的父親節點 parent := tree.Root.FindParent(value) // 第一種狀況,刪除的是根節點,且根節點沒有兒子 if parent == nil && node.Left == nil && node.Right == nil { // 置空後直接返回 tree.Root = nil return } else if node.Left == nil && node.Right == nil { // 第二種狀況,刪除的節點有父親節點,但沒有子樹 // 若是刪除的是節點是父親的左兒子,直接將該值刪除便可 if parent.Left != nil && value == parent.Left.Value { parent.Left = nil } else { // 刪除的原來是父親的右兒子,直接將該值刪除便可 parent.Right = nil } return } else if node.Left != nil && node.Right != nil { // 第三種狀況,刪除的節點下有兩個子樹,由於右子樹的值都比左子樹大,那麼用右子樹中的最小元素來替換刪除的節點。 // 右子樹的最小元素,只要一直往右子樹的左邊一直找一直找就能夠找到,替換後二叉查找樹的性質又知足了。 // 找右子樹中最小的值,一直往右子樹的左邊找 minNode := node.Right for minNode.Left != nil { minNode = minNode.Left } // 把最小的節點刪掉 tree.Delete(minNode.Value) // 最小值的節點替換被刪除節點 node.Value = minNode.Value node.Times = minNode.Times } else { // 第四種狀況,只有一個子樹,那麼該子樹直接替換被刪除的節點便可 // 父親爲空,表示刪除的是根節點,替換樹根 if parent == nil { if node.Left != nil { tree.Root = node.Left } else { tree.Root = node.Right } return } // 左子樹不爲空 if node.Left != nil { // 若是刪除的是節點是父親的左兒子,讓刪除的節點的左子樹接班 if parent.Left != nil && value == parent.Left.Value { parent.Left = node.Left } else { parent.Right = node.Left } } else { // 若是刪除的是節點是父親的左兒子,讓刪除的節點的右子樹接班 if parent.Left != nil && value == parent.Left.Value { parent.Left = node.Right } else { parent.Right = node.Right } } } }
首先查找到要刪除元素的節點:tree.Root.Find(value)
,而後找到該節點父親:tree.Root.FindParent(value)
,根據四種不一樣狀況對刪除節點進行補位。核心在於,第三種狀況下,刪除的節點有兩個子樹狀況下,須要用右子樹中最小的節點來替換被刪除節點。
上面的代碼能夠優化,能夠在查找刪除元素的節點時順道查出其父親節點,沒必要要分開查詢父親節點,在第三種狀況下查出右子樹的最小節點時能夠直接將其移除,沒必要遞歸使用tree.Delete(minNode.Value)
。
因爲這種通用形式的二叉查找樹實現甚少使用,大部分程序都使用了AVL樹或紅黑樹,以上優化理解便可。
使用二叉查找樹能夠實現排序,只須要對樹進行中序遍歷便可。
咱們先打印出左子樹,而後打印根節點的值,再打印右子樹,這是一個遞歸的過程。
// 中序遍歷 func (tree *BinarySearchTree) MidOrder() { tree.Root.MidOrder() } func (node *BinarySearchTreeNode) MidOrder() { if node == nil { return } // 先打印左子樹 node.Left.MidOrder() // 按照次數打印根節點 for i := 0; i <= int(node.Times); i++ { fmt.Println(node.Value) } // 打印右子樹 node.Right.MidOrder() }
package main import ( "fmt" ) // 二叉查找樹節點 type BinarySearchTree struct { Root *BinarySearchTreeNode // 樹根節點 } // 二叉查找樹節點 type BinarySearchTreeNode struct { Value int64 // 值 Times int64 // 值出現的次數 Left *BinarySearchTreeNode // 左子樹 Right *BinarySearchTreeNode // 右字樹 } // 初始化一個二叉查找樹 func NewBinarySearchTree() *BinarySearchTree { return new(BinarySearchTree) } // 添加元素 func (tree *BinarySearchTree) Add(value int64) { // 若是沒有樹根,證實是顆空樹,添加樹根後返回 if tree.Root == nil { tree.Root = &BinarySearchTreeNode{Value: value} return } // 將值添加進去 tree.Root.Add(value) } func (node *BinarySearchTreeNode) Add(value int64) { if value < node.Value { // 若是插入的值比節點的值小,那麼要插入到該節點的左子樹中 // 若是左子樹爲空,直接添加 if node.Left == nil { node.Left = &BinarySearchTreeNode{Value: value} } else { // 不然遞歸 node.Left.Add(value) } } else if value > node.Value { // 若是插入的值比節點的值大,那麼要插入到該節點的右子樹中 // 若是右子樹爲空,直接添加 if node.Right == nil { node.Right = &BinarySearchTreeNode{Value: value} } else { // 不然遞歸 node.Right.Add(value) } } else { // 值相同,不須要添加,值出現的次數加1便可 node.Times = node.Times + 1 } } // 找出最小值的節點 func (tree *BinarySearchTree) FindMinValue() *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } return tree.Root.FindMinValue() } func (node *BinarySearchTreeNode) FindMinValue() *BinarySearchTreeNode { // 左子樹爲空,表面已是最左的節點了,該值就是最小值 if node.Left == nil { return node } // 一直左子樹遞歸 return node.Left.FindMinValue() } // 找出最大值的節點 func (tree *BinarySearchTree) FindMaxValue() *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } return tree.Root.FindMaxValue() } func (node *BinarySearchTreeNode) FindMaxValue() *BinarySearchTreeNode { // 右子樹爲空,表面已是最右的節點了,該值就是最大值 if node.Right == nil { return node } // 一直右子樹遞歸 return node.Right.FindMaxValue() } // 查找指定節點 func (tree *BinarySearchTree) Find(value int64) *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } return tree.Root.Find(value) } func (node *BinarySearchTreeNode) Find(value int64) *BinarySearchTreeNode { if value == node.Value { // 若是該節點剛剛等於該值,那麼返回該節點 return node } else if value < node.Value { // 若是查找的值小於節點值,從節點的左子樹開始找 if node.Left == nil { // 左子樹爲空,表示找不到該值了,返回nil return nil } return node.Left.Find(value) } else { // 若是查找的值大於節點值,從節點的右子樹開始找 if node.Right == nil { // 右子樹爲空,表示找不到該值了,返回nil return nil } return node.Right.Find(value) } } // 查找指定節點的父親 func (tree *BinarySearchTree) FindParent(value int64) *BinarySearchTreeNode { if tree.Root == nil { // 若是是空樹,返回空 return nil } // 若是根節點等於該值,根節點其沒有父節點,返回nil if tree.Root.Value == value { return nil } return tree.Root.FindParent(value) } func (node *BinarySearchTreeNode) FindParent(value int64) *BinarySearchTreeNode { // 外層沒有值相等的斷定,由於在內層已經斷定完畢後返回父親節點。 if value < node.Value { // 若是查找的值小於節點值,從節點的左子樹開始找 leftTree := node.Left if leftTree == nil { // 左子樹爲空,表示找不到該值了,返回nil return nil } // 左子樹的根節點的值恰好等於該值,那麼父親就是如今的node,返回 if leftTree.Value == value { return node } else { return leftTree.FindParent(value) } } else { // 若是查找的值大於節點值,從節點的右子樹開始找 rightTree := node.Right if rightTree == nil { // 右子樹爲空,表示找不到該值了,返回nil return nil } // 右子樹的根節點的值恰好等於該值,那麼父親就是如今的node,返回 if rightTree.Value == value { return node } else { return rightTree.FindParent(value) } } } // 刪除指定的元素 func (tree *BinarySearchTree) Delete(value int64) { if tree.Root == nil { // 若是是空樹,直接返回 return } // 查找該值是否存在 node := tree.Root.Find(value) if node == nil { // 不存在該值,直接返回 return } // 查找該值的父親節點 parent := tree.Root.FindParent(value) // 第一種狀況,刪除的是根節點,且根節點沒有兒子 if parent == nil && node.Left == nil && node.Right == nil { // 置空後直接返回 tree.Root = nil return } else if node.Left == nil && node.Right == nil { // 第二種狀況,刪除的節點有父親節點,但沒有子樹 // 若是刪除的是節點是父親的左兒子,直接將該值刪除便可 if parent.Left != nil && value == parent.Left.Value { parent.Left = nil } else { // 刪除的原來是父親的右兒子,直接將該值刪除便可 parent.Right = nil } return } else if node.Left != nil && node.Right != nil { // 第三種狀況,刪除的節點下有兩個子樹,由於右子樹的值都比左子樹大,那麼用右子樹中的最小元素來替換刪除的節點,這時二叉查找樹的性質又知足了。 // 找右子樹中最小的值,一直往右子樹的左邊找 minNode := node.Right for minNode.Left != nil { minNode = minNode.Left } // 把最小的節點刪掉 tree.Delete(minNode.Value) // 最小值的節點替換被刪除節點 node.Value = minNode.Value node.Times = minNode.Times } else { // 第四種狀況,只有一個子樹,那麼該子樹直接替換被刪除的節點便可 // 父親爲空,表示刪除的是根節點,替換樹根 if parent == nil { if node.Left != nil { tree.Root = node.Left } else { tree.Root = node.Right } return } // 左子樹不爲空 if node.Left != nil { // 若是刪除的是節點是父親的左兒子,讓刪除的節點的左子樹接班 if parent.Left != nil && value == parent.Left.Value { parent.Left = node.Left } else { parent.Right = node.Left } } else { // 若是刪除的是節點是父親的左兒子,讓刪除的節點的右子樹接班 if parent.Left != nil && value == parent.Left.Value { parent.Left = node.Right } else { parent.Right = node.Right } } } } // 中序遍歷 func (tree *BinarySearchTree) MidOrder() { tree.Root.MidOrder() } func (node *BinarySearchTreeNode) MidOrder() { if node == nil { return } // 先打印左子樹 node.Left.MidOrder() // 按照次數打印根節點 for i := 0; i <= int(node.Times); i++ { fmt.Println(node.Value) } // 打印右子樹 node.Right.MidOrder() } func main() { values := []int64{3, 6, 8, 20, 9, 2, 6, 8, 9, 3, 5, 40, 7, 9, 13, 6, 8} // 初始化二叉查找樹並添加元素 tree := NewBinarySearchTree() for _, v := range values { tree.Add(v) } // 找到最大值或最小值的節點 fmt.Println("find min value:", tree.FindMinValue()) fmt.Println("find max value:", tree.FindMaxValue()) // 查找不存在的99 node := tree.Find(99) if node != nil { fmt.Println("find it 99!") } else { fmt.Println("not find it 99!") } // 查找存在的9 node = tree.Find(9) if node != nil { fmt.Println("find it 9!") } else { fmt.Println("not find it 9!") } // 刪除存在的9後,再查找9 tree.Delete(9) node = tree.Find(9) if node != nil { fmt.Println("find it 9!") } else { fmt.Println("not find it 9!") } // 中序遍歷,實現排序 tree.MidOrder() }
運行程序後,結果:
find min value: &{2 0 <nil> <nil>} find max value: &{40 0 <nil> <nil>} not find it 99! find it 9! not find it 9! 2 3 3 5 6 6 6 7 8 8 8 13 20 40
二叉查找樹可能退化爲鏈表,也多是一顆很是平衡的二叉樹,查找,添加,刪除元素的時間複雜度取決於樹的高度h
。
n
和高度h
的關係爲:h = log(n)
。n
和高度h
的關係爲:h = n
。二叉查找樹的效率來源其二分查找的特徵,時間複雜度在於二叉樹的高度,所以查找,添加和刪除的時間複雜度範圍爲log(n)~n
。
爲了提升二叉查找樹查找的速度,樹的高度要儘量的小。AVL樹和紅黑樹都是相對平衡的二叉查找樹,由於特殊的旋轉平衡操做,樹的高度被大大壓低。它們查找效率較高,添加,刪除,查找操做的平均時間複雜度都爲log(n)
,常常在各類程序中被使用。
二叉查找樹是後面要學習的高級數據結構AVL樹,紅黑樹的基礎。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。