二叉搜索樹是一種支持快速查找、增加和刪除元素的數據結構。二叉搜索樹中的節點是有序的,因此支持快速查找。該數據結構由P.F.Windley、A.D.Booth、A.J.T.Colin和T.N.Hibbard發明的。
二叉搜索樹的平均空間復雜度是O(n),插入、刪除和搜索操作的時間就復雜的是O(logn)。二叉搜索樹節點包含以下屬性:
- 一個整型key值
- 一個整型value值
- 包含左節點leftNode和右節點rightNode
- 左節點key小于當前節點,右節點key大于當前節點
節點定義代碼如下:
type TreeNode struct {
key int
value int
leftNode *TreeNode
rightNode *TreeNode
}
二叉搜索樹結構體定義,其包含一個根節點rootNode是TreeNode類型,以及一個互斥鎖sync.RWMutex類型。二叉搜索樹的遍歷是從根節點出發的,然后是左節點和右節點:
type BinarySearchTree struct {
rootNode *TreeNode
lock sync.RWMutex
}
定義好了二叉搜索樹結構體類型,接下來實現其方法。
插入方法:InsertElement
該方法作用是將一個包含key和value的節點插入到二叉搜索樹中。插入之前要獲取鎖,插入完成釋放鎖。
func (tree *BinarySearchTree)InsertElement(key int, value int) {
tree.lock.Lock()
defer tree.lock.Unlock()
var treeNode *TreeNode
treeNode = &TreeNode{
key: key,
value: value,
leftNode: nil,
rightNode: nil,
}
if tree.rootNode == nil {
tree.rootNode = treeNode
} else {
insertTreeNode(tree.rootNode, treeNode)
}
}
先根據key,value創建treeNode節點。然后判斷根節點是否為空,如果是空就將新增的節點賦值給根節點。如果根節點不為空,調用insertTreeNode函數找到合適的位置插入。
func insertTreeNode(rootNode *TreeNode, newTreeNode *TreeNode) {
if newTreeNode.key < rootNode.key {
if rootNode.leftNode == nil {
rootNode.leftNode = newTreeNode
} else {
insertTreeNode(rootNode.leftNode, newTreeNode)
}
} else {
if rootNode.rightNode == nil{
rootNode.rightNode = newTreeNode
} else{
insertTreeNode(rootNode.rightNode, newTreeNode)
}
}
}
根據二叉搜索樹的特點,每個節點的key比左節點key要大,比右節點的key要小。因此先和左節點的key比較,如果左節點為空,就將新增節點賦值給左節點,否則繼續遞歸找。同理如果新增節點key比左節點大,就和右節點比較,為空就賦值,否則遞歸。
inOrderTraverse方法:順序遍歷二叉搜索樹
該方法按順序遍歷樹中的每個節點。先調用RLock()讀鎖,遍歷完后釋放:
func (tree *BinarySearchTree)InOrderTraverseTree(function func(int)) {
tree.lock.RLock()
defer tree.lock.RUnlock()
inOrderTraverseTree(tree.rootNode, function)
}
func inOrderTraverseTree(treeNode *TreeNode, function func(int)) {
if treeNode != nil {
inOrderTraverseTree(treeNode.leftNode, function)
function(treeNode.value)
inOrderTraverseTree(treeNode.rightNode, function)
}
}
為了避免數據競爭,這里創建一個內部調用函數來實現有序遍歷二叉搜索樹。根據二叉搜索樹特點,先遍歷左子樹,然后當前節點最后遍歷右子樹,也稱為中序遍歷。
PreOrderTraverse方法:前序遍歷
先遍歷當前節點,然后是左子樹最后右子樹;
func (tree *BinarySearchTree)PreOrderTraverseTree(function func(int)) {
tree.lock.RLock()
defer tree.lock.RUnlock()
preOrderTraverseTree(tree.rootNode, function)
}
func preOrderTraverseTree(treeNode *TreeNode, function func(int)) {
if treeNode != nil {
function(treeNode.value)
preOrderTraverseTree(treeNode.leftNode, function)
preOrderTraverseTree(treeNode.rightNode, function)
}
}
PostOrderTraverse方法:后序遍歷
后序遍歷:先左子樹,然后右子樹最后是當前節點
func (tree *BinarySearchTree)PostOrderTraverseTree(function func(int)) {
tree.lock.RLock()
defer tree.lock.RUnlock()
postOrderTraverseTree(tree.rootNode, function)
}
func postOrderTraverseTree(treeNode *TreeNode, function func(int)) {
if treeNode != nil {
postOrderTraverseTree(treeNode.leftNode, function)
postOrderTraverseTree(treeNode.rightNode, function)
function(treeNode.value)
}
}
MinNode方法:最小節點
該方法:用于查找二叉搜索樹中key最小的節點值。
func (tree *BinarySearchTree)MinNode() *int {
tree.lock.RLock()
defer tree.lock.RUnlock()
var treeNode *TreeNode
treeNode = tree.rootNode
if treeNode == nil {
return (*int)(nil)
}
for {
//最小值位于左子樹
if treeNode.leftNode == nil {
return &treeNode.value
}
treeNode = treeNode.leftNode
}
}
根據二叉搜索樹的特點:左子樹值都小于當前節點,所以如果當前節點的左節點為空,說明當前節點的key是最小的
MaxNode方法:查詢最大key節點
該方法和MinNode方法相反,最大子位于當前節點右子樹。如果當前節點的右節點為空,說明當前節點是值最大節點。
func (tree *BinarySearchTree)MaxNode() *int {
tree.lock.RLock()
defer tree.lock.RUnlock()
var treeNode *TreeNode
treeNode = tree.rootNode
if treeNode == nil {
return (*int)(nil)
}
for {
if treeNode.rightNode == nil {
return &treeNode.value
}
treeNode = treeNode.rightNode
}
}
SearchNode方法:根據key查詢對應節點是否存在。
func (tree *BinarySearchTree)SearchNode(key int) bool {
tree.lock.RLock()
defer tree.lock.RUnlock()
return searchNode(tree.rootNode, key)
}
func searchNode(treeNode *TreeNode, key int) bool {
if treeNode == nil {
return false
}
if key < treeNode.key{
return searchNode(treeNode.leftNode, key)
}
if key > treeNode.key{
return searchNode(treeNode.rightNode, key)
}
return true
}
二叉搜索樹:當前節點比左子樹所有節點大,比右子樹所有節點小。因此在查找一個key是否存在時,如果當前節點key比搜索key更小,就繼續在左子樹查找;如果要搜索key大于當前節點的key就在右子樹查找。否則當前節點就是要查找的節點。
removeNode方法:刪除節點
func (tree *BinarySearchTree) RemoveNode(key int) {
tree.lock.Lock()
defer tree.lock.Unlock()
removeNode(tree.rootNode, key)
}
func removeNode(treenode *TreeNode, key int) *TreeNode {
if treenode == nil {
return nil
}
if key < treenode.key {
treenode.leftNode = removeNode(treenode.leftNode, key)
return treenode
}
if key > treenode.key {
treenode.rightNode = removeNode(treenode.rightNode, key)
return treenode
}
//key == treeNode.ley
if treenode.leftNode == nil && treenode.rightNode == nil {
treenode = nil
return nil
}
//要刪除的節點,左節點為空
if treenode.leftNode == nil {
treenode = treenode.rightNode
return treenode
}
//要刪除的節點,右節點為空
if treenode.rightNode == nil {
treenode = treenode.leftNode
return treenode
}
//要刪除的節點,左右節點都不為空
var leftmostrightNode *TreeNode
leftmostrightNode = treenode.rightNode
for {
if leftmostrightNode != nil && leftmostrightNode.leftNode != nil {
leftmostrightNode = leftmostrightNode.leftNode
} else {
break
}
}
//將右子樹最小節點值,替換待刪除節點的值
treenode.key, treenode.value = leftmostrightNode.key, leftmostrightNode.value
//遞歸刪除右子樹最小節點
treenode.rightNode = removeNode(treenode.rightNode, treenode.key)
return treenode
}
二叉搜索樹刪除節點比較復雜,遞歸的刪除。分4種情況:
1、待刪除節點,左右節點為空,直接刪除。
2、待刪除節點,左節點為空,直接將右節點賦值給刪除節點覆蓋
3、待刪除節點,右節點為空,直接將左節點賦值給刪除節點覆蓋。
4、待刪除節點,左右節點都不為空。用其右子樹的最小結點代替待刪除結點。代碼中的leftmostrightNode節點為右子樹最小節點。