Structure de données et algorithme (implémentation de Golang) (27) Arbre de recherche de l'algorithme de recherche binaire

Arbre de recherche binaire

L'arbre de recherche binaire, également appelé arbre de tri binaire, arbre de recherche binaire, est un arbre binaire avec des règles spécifiques, définies comme suit:

  1. Il s'agit d'un arbre binaire ou d'un arbre vide.
  2. Les valeurs de tous les nœuds du sous-arbre gauche sont inférieures à son nœud racine et les valeurs de tous les nœuds du sous-arbre droit sont supérieures à son nœud racine.
  3. Les sous-arbres gauche et droit sont également un arbre de recherche binaire.

La caractéristique de l'arbre de recherche binaire est que la recherche du fils gauche vers le fils gauche peut trouver le plus petit élément, et la recherche du fils droit vers le fils droit peut trouver le plus grand élément.

Il semble que nous pouvons l'utiliser pour implémenter le tri des éléments, mais nous utilisons le tas binaire pour implémenter le tri par tas, car l'arborescence de recherche binaire n'est pas garantie d'être un arbre binaire équilibré. Dans le pire des cas, l'arbre de recherche binaire dégénérera en Une liste chaînée, c'est-à-dire que tous les nœuds n'ont pas de sous-arbre gauche ou de sous-arbre droit, la hiérarchie d'arbre est trop profonde, ce qui entraîne de mauvaises performances de tri.

En utilisant la recherche binaire, nous pouvons trouver rapidement la valeur dont nous avons besoin dans une arborescence de recherche binaire.

Analysons la méthode d'ajout, de suppression et de recherche d'éléments dans une arborescence de recherche binaire.

Tout d'abord, ajoutez des éléments

Ce qui suit est une représentation d'un arbre de recherche binaire:

// 二叉查找树
type BinarySearchTree struct {
    Root *BinarySearchTreeNode // 树根节点
}

// 二叉查找树节点
type BinarySearchTreeNode struct {
    Value int64                 // 值
    Times int64                 // 值出现的次数
    Left  *BinarySearchTreeNode // 左子树
    Right *BinarySearchTreeNode // 右字树
}

// 初始化一个二叉查找树
func NewBinarySearchTree() *BinarySearchTree {
    return new(BinarySearchTree)
}

Un nœud représente un élément, et la Valuevaleur du nœud est la clé utilisée pour la recherche binaire. Lorsque la Valuevaleur est répétée, nous augmentons le nombre de fois où la valeur apparaît de Times1. Le code pour ajouter des éléments est le suivant:

// 添加元素
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
    }
}

S'il s'agit d'un arbre vide lors de l'ajout d'éléments, initialisez le nœud racine.

Ensuite, la valeur ajoutée est comparée au nœud racine pour déterminer si elle doit être insérée dans le sous-arbre gauche ou droit du nœud racine, ou ne pas être insérée.

Lorsque la valeur est inférieure au nœud racine, l'élément est inséré dans le sous-arbre gauche du nœud racine. Lorsque la valeur est supérieure au nœud racine, l'élément est inséré dans le sous-arbre droit du nœud racine.

Ensuite, faites fonctionner récursivement les sous-arbres gauche et droit du nœud racine.

Deuxièmement, trouvez l'élément maximum ou minimum

La recherche des valeurs maximales et minimales est relativement simple. Continuez à chercher le fils gauche jusqu'au fils gauche pour trouver le plus petit élément. Continuez à chercher le bon fils au bon fils pour trouver le plus grand élément.

// 找出最小值的节点
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()
}

Troisièmement, trouvez l'élément spécifié

La technique de recherche binaire est également utile ici:

// 查找节点
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)
    }
}

S'il s'agit d'un arbre vide, retournez nil, sinon comparez avec le nœud racine.

S'il est juste égal à la valeur du nœud racine, renvoyez le nœud, sinon, selon la comparaison de valeurs, continuez à rechercher récursivement le sous-arbre gauche ou l'arbre de mots droit.

Quatrièmement, trouver le père de l'élément spécifié

Cela revient à rechercher l'élément spécifié, sauf que le nœud parent de l'élément est suivi.

// 查找指定节点的父亲
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)
        }
    }
}

Le code a été ajusté en conséquence pour faciliter l'obtention du nœud parent.

Si le nœud parent retourné est vide, cela signifie qu'il n'y a pas de père.

Cinq, supprimer des éléments

Il existe quatre cas de suppression d'éléments:

  1. Dans le premier cas, le nœud racine est supprimé et le nœud racine n'a pas de fils, supprimez-le directement.
  2. Dans le deuxième cas, le nœud supprimé a un nœud parent, mais il n'y a pas de sous-arborescence, c'est-à-dire le nœud feuille supprimé, supprimez-le directement.
  3. Dans le troisième cas, il y a deux sous-arbres sous le nœud supprimé, car les valeurs du sous-arbre droit sont plus grandes que le sous-arbre gauche, puis remplacez le nœud supprimé par le plus petit élément du sous-arbre droit, puis la nature de l'arbre de recherche binaire Encore satisfait. Le plus petit élément du sous-arbre droit peut être trouvé tant que vous continuez à regarder à gauche du sous-arbre droit.
  4. Dans le quatrième cas, le nœud supprimé n'a qu'un seul sous-arbre, de sorte que le sous-arbre peut remplacer directement le nœud supprimé.

Le code est implémenté comme suit:

// 删除指定的元素
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
            }
        }
    }
}

Recherchez d'abord le nœud de l'élément que vous souhaitez supprimer : tree.Root.Find(value), puis recherchez le père du nœud :, tree.Root.FindParent(value)remplissez le nœud supprimé selon quatre situations différentes. Le noyau réside dans le fait que dans le troisième cas, lorsque le nœud supprimé a deux sous-arbres, il est nécessaire de remplacer le nœud supprimé par le plus petit nœud du sous-arbre droit.

Le code ci-dessus peut être optimisé, vous pouvez découvrir son nœud parent lors de la recherche du nœud de l'élément supprimé, il n'est pas nécessaire d'interroger séparément le nœud parent, dans le troisième cas, vous pouvez le supprimer directement lorsque vous trouvez le plus petit nœud du sous-arbre droit Il n'est pas nécessaire de l'utiliser de manière récursive tree.Delete(minNode.Value).

Étant donné que cette forme générale d'arbre de recherche binaire est rarement utilisée, la plupart des programmes utilisent des arbres AVL ou rouges-noirs. L'optimisation ci-dessus peut être comprise.

Six, traversée dans l'ordre (pour réaliser le tri)

Le tri peut être réalisé à l'aide d'un arbre de recherche binaire, tant que l'arbre est parcouru dans l'ordre.

Nous imprimons d'abord le sous-arbre gauche, puis imprimons la valeur du nœud racine, puis imprimons le sous-arbre droit. Il s'agit d'un processus récursif.

// 中序遍历
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()
}

Sept, code complet

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()
}

Après avoir exécuté le programme, le résultat:

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

8. Résumé

L'arbre de recherche binaire peut dégénérer en une liste chaînée, ou il peut s'agir d'un arbre binaire très équilibré. La complexité temporelle de la recherche, de l'ajout et de la suppression d'éléments dépend de la hauteur de l'arbre h.

  1. Lorsque l'arbre binaire est pleine, la hauteur de l'arbre est le plus petit, à ce moment le nombre de nœuds d'arbres net la hauteur hrelation est la suivante: h = log(n).
  2. Lorsqu'une liste d'arbre binaire, le nombre d'arbres nœuds à ce moment net la hauteur hrelation est: h = n.

L'efficacité de l'arbre de recherche binaire provient des caractéristiques de sa recherche binaire. La complexité temporelle réside dans la hauteur de l'arbre binaire, de sorte que la plage de complexité temporelle de la recherche, de l'ajout et de la suppression est log(n)~n.

Afin d'améliorer la vitesse de recherche de l'arbre de recherche binaire, la hauteur de l'arbre doit être aussi petite que possible. L'arbre AVL et l'arbre rouge-noir sont des arbres de recherche binaires relativement équilibrés, en raison de l'opération d'équilibrage de rotation spéciale, la hauteur de l'arbre est considérablement réduite. Ils ont une efficacité de recherche élevée, et la complexité moyenne en temps des opérations d'ajout, de suppression et de recherche sont tous log(n), et ils sont souvent utilisés dans divers programmes.

L'arbre de recherche binaire est la base de l'arborescence AVL de structure de données avancée, arbre rouge-noir à apprendre plus tard.

Entrée d'article de série

Je suis la star Chen, bienvenue J'ai personnellement écrit des structures de données et algorithmes (golang atteindre) , à partir de l'article à lire GitBook plus convivial .

Je suppose que tu aimes

Origine www.cnblogs.com/nima/p/12724873.html
conseillé
Classement