Golang データ構造: 二分探索ツリー

Golang における二分探索木の実装と共通操作、データ構造シリーズ 原文: flaviocopes.com

コンセプト

ツリー: 家系図に似た階層データ構造

バイナリ ツリー: 各ノードが最大 2 つの子ノードを持つツリー

二分探索木(二分探索木):左ノードの値が右ノードの値より小さい二分木

  • 深さ: ルート ノードから現在のノードまでの一意のパスの長さ

  • 高さ: 現在のノードからリーフまでの最長パスの長さ

  • ルート: 深さ 0 のツリー ノード

  • 内部ノード: 少なくとも 1 つの子ノードを持つノード

  • リーフ: 子のないノード

  • 兄弟ノード (sibling): 同じ親ノードを持つ子ノード

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-qZNOjHZ4-1689064644667)(https://contents.yinzige.com/concept.png) )]

二分探索木

共通の操作とノードの定義

Insert(v)		// 向二叉搜索树的合适位置插入节点
Search(k)		// 检查序号为 k 的元素在树中是否存在
Remove(v)		// 移除树中所有值为 v 的节点
Min()			// 获取二叉搜索树中最小的值
Max()			// 获取二叉搜索树中最大的值
InOrderTraverse()	// 中序遍历树
PreOrderTraverse()	// 先序遍历树
PostOrderTraverse()	// 后续遍历树
String()		// 在命令行格式化打印出二叉树

コードの再利用性を提供するために genny も使用します。ツリー タイプの名前は ですItemBinarySearchTree。ツリー ノードの構造は次のように定義されます。

type Node struct {
    
    
	key   int	// 中序遍历的节点序号
	value Item	// 节点存储的值
	left  *Node	// 左子节点
	right *Node	// 右子节点
}

キーは、プリオーダー トラバーサルにおける各ノードの位置のシリアル番号です。キーの値はここでは int であり、同等のデータ型であれば何でも構いません。

挿入とトラバース

挿入操作では再帰を使用する必要があり、挿入操作ではツリー内で新しいノードの適切な位置を上から下まで見つける必要があります。新しいノードの値がどのノードよりも小さい場合は、左側のノードの検索を続けます。サブツリーを検索し、同様に正しいサブツリーが見つかるまで検索します。リーフ ノードに移動して再度挿入します。

トラバーサル操作には 3 つのモードがあります。

  • インオーダートラバーサル (インオーダー): 左のサブツリー –> ルートノード –> 右のツリー:1->2->3->4->5->6->7->8->9->10->11

  • 事前順序トラバーサル (事前順序): ルート ノード –> 左サブツリー –> 右サブツリー:8->4->2->1->3->6->5->7 >10->9->11

  • 事後トラバーサル (事後): 左のサブツリー –> 右のサブツリー –> ルート ノード:1->3->2->5->7->6->4->9->11->10->8


コード

入れる

// 向二叉搜索树的合适位置插入节点
func (tree *ItemBinarySearchTree) Insert(key int, value Item) {
    
    
	tree.lock.Lock()
	defer tree.lock.Unlock()
	newNode := &Node{
    
    key, value, nil, nil}
	// 初始化树
	if tree.root == nil {
    
    
		tree.root = newNode
	} else {
    
    
		// 在树中递归查找正确的位置并插入
		insertNode(tree.root, newNode)
	}
}

func insertNode(node, newNode *Node) {
    
    
	// 插入到左子树
	if newNode.key < node.key {
    
    
		if node.left == nil {
    
    
			node.left = newNode
		} else {
    
    
			// 递归查找左边插入
			insertNode(node.left, newNode)
		}
	} else {
    
    
		// 插入到右子树
		if node.right == nil {
    
    
			node.right = newNode
		} else {
    
    
			// 递归查找右边插入
			insertNode(node.right, newNode)
		}
	}
}

検索

// 检查序号为 k 的元素在树中是否存在
func (tree *ItemBinarySearchTree) Search(key int) bool {
    
    
	tree.lock.RLock()
	defer tree.lock.RUnlock()
	return search(tree.root, key)
}
func search(node *Node, key int) bool {
    
    
	if node == nil {
    
    
		return false
	}
	// 向左搜索更小的值
	if key < node.key {
    
    
		return search(node.left, key)
	}
	// 向右搜索更大的值
	if key > node.key {
    
    
		return search(node.right, key)
	}
	return true // key == node.key
}

取り除く

ノードを削除するプロセス

まず再帰的に検索してから、ノードを削除します。ただし、削除する場合は、ノードが所有する子ノードの数に応じて、以下の3つの場合に分けて行う必要があります。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-m2ENIRMb-1689064644669)(https://contents.yinzige.com/remove-node) .png)]

コード
// 删除指定序号的节点
func (tree *ItemBinarySearchTree) Remove(key int) {
    
    
	tree.lock.Lock()
	defer tree.lock.Unlock()
	remove(tree.root, key)
}

// 递归删除节点
func remove(node *Node, key int) *Node {
    
    
	// 要删除的节点不存在
	if node == nil {
    
    
		return nil
	}

	// 寻找节点
	// 要删除的节点在左侧
	if key < node.key {
    
    
		node.left = remove(node.left, key)
		return node
	}
	// 要删除的节点在右侧
	if key > node.key {
    
    
		node.right = remove(node.right, key)
		return node
	}

	// 判断节点类型
	// 要删除的节点是叶子节点,直接删除
	// if key == node.key {
    
    
	if node.left == nil && node.right == nil {
    
    
		node = nil
		return node
	}

	// 要删除的节点只有一个节点,删除自身
	if node.left == nil {
    
    
		node = node.right
		return node
	}
	if node.right == nil {
    
    
		node = node.left
		return node
	}

	// 要删除的节点有 2 个子节点,找到右子树的最左节点,替换当前节点
	mostLeftNode := node.right
	for {
    
    
		// 一直遍历找到最左节点
		if mostLeftNode != nil && mostLeftNode.left != nil {
    
    
			mostLeftNode = mostLeftNode.left
		} else {
    
    
			break
		}
	}
	// 使用右子树的最左节点替换当前节点,即删除当前节点
	node.key, node.value = mostLeftNode.key, mostLeftNode.value
	node.right = remove(node.right, node.key)
	return node
}

最小、最大

// 获取树中值最小的节点:最左节点
func (tree *ItemBinarySearchTree) Min() *Item {
    
    
	tree.lock.RLock()
	defer tree.lock.RUnlock()
	node := tree.root
	if node == nil {
    
    
		return nil
	}
	for {
    
    
		if node.left == nil {
    
    
			return &node.value
		}
		node = node.left
	}
}

// 获取树中值最大的节点:最右节点
func (tree *ItemBinarySearchTree) Max() *Item {
    
    
	tree.lock.RLock()
	defer tree.lock.RUnlock()
	node := tree.root
	if node == nil {
    
    
		return nil
	}
	for {
    
    
		if node.right == nil {
    
    
			return &node.value
		}
		node = node.right
	}
}

トラバース

// 先序遍历:根节点 -> 左子树 -> 右子树
func (tree *ItemBinarySearchTree) PreOrderTraverse(printFunc func(Item)) {
    
    
	tree.lock.RLock()
	defer tree.lock.RUnlock()
	preOrderTraverse(tree.root, printFunc)
}
func preOrderTraverse(node *Node, printFunc func(Item)) {
    
    
	if node != nil {
    
    
		printFunc(node.value)                   // 先打印根结点
		preOrderTraverse(node.left, printFunc)  // 再打印左子树
		preOrderTraverse(node.right, printFunc) // 最后打印右子树
	}
}

// 中序遍历:左子树 -> 根节点 -> 右子树
func (tree *ItemBinarySearchTree) PostOrderTraverse(printFunc func(Item)) {
    
    
	tree.lock.RLock()
	defer tree.lock.RUnlock()
	postOrderTraverse(tree.root, printFunc)
}
func postOrderTraverse(node *Node, printFunc func(Item)) {
    
    
	if node != nil {
    
    
		postOrderTraverse(node.left, printFunc)  // 先打印左子树
		postOrderTraverse(node.right, printFunc) // 再打印右子树
		printFunc(node.value)                    // 最后打印根结点
	}
}

// 后序遍历:左子树 -> 右子树 -> 根结点
func (tree *ItemBinarySearchTree) InOrderTraverse(printFunc func(Item)) {
    
    
	tree.lock.RLock()
	defer tree.lock.RUnlock()
	inOrderTraverse(tree.root, printFunc)
}
func inOrderTraverse(node *Node, printFunc func(Item)) {
    
    
	if node != nil {
    
    
		inOrderTraverse(node.left, printFunc)  // 先打印左子树
		printFunc(node.value)                  // 再打印根结点
		inOrderTraverse(node.right, printFunc) // 最后打印右子树
	}
}

// 后序遍历打印树结构
func (tree *ItemBinarySearchTree) String() {
    
    
	tree.lock.Lock()
	defer tree.lock.Unlock()
	if tree.root == nil {
    
    
		println("Tree is empty")
		return
	}
	stringify(tree.root, 0)
	println("----------------------------")
}
func stringify(node *Node, level int) {
    
    
	if node == nil {
    
    
		return
	}

	format := ""
	for i := 0; i < level; i++ {
    
    
		format += "\t" // 根据节点的深度决定缩进长度
	}
	format += "----[ "
	level++
	// 先递归打印左子树
	stringify(node.left, level)
  	// 打印值
	fmt.Printf(format+"%d\n", node.key)
	/// 再递归打印右子树
	stringify(node.right, level)
}

要約する

二分探索木の演算は、追加、削除、検査がすべて再帰に関係するため、実装時に再帰の終了条件を明確に解析し、適切な条件下でreturn無限ループを回避する必要があります。

おすすめ

転載: blog.csdn.net/qq_24694139/article/details/131663664