Árbol de búsqueda binario y recorrido de árbol binario (recursivo y no recursivo)

definición

El árbol de búsqueda binaria (árbol de búsqueda binaria) también se conoce como árbol de clasificación binaria y árbol de búsqueda binaria. El árbol de búsqueda binaria nace para realizar búsquedas rápidas. Sin embargo, no solo admite la búsqueda rápida de datos, sino que también admite la inserción y eliminación rápidas de datos. El árbol de búsqueda binaria requiere que se satisfaga cualquier nodo en el árbol, y el valor de cada nodo en el subárbol izquierdo debe ser menor que el valor de este nodo, y el valor de cada nodo en el subárbol derecho es mayor que el valor de este valor de nodo.

Ambos son árboles de búsqueda binarios.

Encontrar

Para encontrar un nodo en el árbol de búsqueda binaria, primero tomamos el nodo raíz, y si es igual a los datos que estamos buscando, regresamos. Si los datos que se buscarán son más pequeños que el valor del nodo raíz, busque de forma recursiva en el subárbol izquierdo; si los datos que se buscarán son mayores que el valor del nodo raíz, busque de forma recursiva en el subárbol derecho.

Implementación de código de búsqueda

public class BinarySearchTree {
    
    
    private Node root;

    public Node find(int data) {
    
    
        Node temp = root;
        while (temp != null) {
    
    
            if (data == temp.data) {
    
    
                return temp;
            } else if(data > temp.data) {
    
    
                temp = temp.rchild;
            } else {
    
    
                temp = temp.lchild;
            }
        }
        return null;
    }
}

public class Node {
    
    
    int data;
    Node lchild;
    Node rchild;

    public Node(int data) {
    
    
        this.data = data;
    }
}

insertar

La operación de insertar es similar a la de buscar. Comenzando desde el nodo raíz, compare la relación entre los datos que se insertarán y el nodo a su vez.
Si los datos que se insertarán son más grandes que los datos del nodo y el subárbol derecho del nodo está vacío, inserte los datos directamente en la posición del nodo secundario derecho. Si el subárbol derecho no está vacío, continúe atravesando el subárbol derecho; si lo desea Los datos insertados son más pequeños que los datos del nodo, y el subárbol izquierdo del nodo está vacío, los datos se insertan directamente en la posición del nodo secundario izquierdo, si el subárbol izquierdo no está vacío , luego continúe atravesando el subárbol izquierdo.

Implementación de código insertado

public void insert(int data) {
    
    
        if (root == null) {
    
    
            root = new Node(data);
            return;
        }
        
        Node temp = root;
        while (true) {
    
    
            if (data > temp.data) {
    
    
                if (temp.rchild == null) {
    
    
                    temp.rchild = new Node(data);
                    return;
                }
                temp = temp.rchild;
            } else {
    
    
                if (temp.lchild == null) {
    
    
                    temp.lchild = new Node(data);
                    return;
                }
                temp = temp.lchild;
            }
        }
    }

Eliminar

La operación de búsqueda e inserción del árbol de búsqueda binaria es relativamente simple, pero la operación de eliminación es más complicada, que se puede dividir en tres situaciones.
En el primer caso, el nodo a eliminar es un nodo hoja. En este momento, solo es necesario establecer directamente el puntero al nodo que se eliminará en el nodo principal en nulo. Elimine 28 como se muestra en la figura siguiente.
En el segundo caso, el nodo que se eliminará tiene solo un nodo secundario (nodo secundario izquierdo o nodo secundario derecho), solo necesitamos actualizar el puntero en el nodo principal para que apunte al punto de bytes del nodo que se eliminará. Consulte 34 en la figura siguiente.
En el tercer caso, el nodo que se va a eliminar tiene dos nodos secundarios. En este caso, debemos reemplazar el nodo que se eliminará con el nodo más pequeño en el subárbol derecho del nodo que se eliminará. Consulte 15 en la figura siguiente.


Una vez completada la eliminación,

se implementa el código

public TreeNode deleteNode(TreeNode root, int key) {
    
    
    if (root == null) return null;

    if (root.val == key) {
    
    
        if (root.left == null) return root.right;
        if (root.right == null) return root.left;

        TreeNode minNode = getMin(root.right);
        root.val = minNode.val;
        root.right = deleteNode(root.right, minNode.val);
    } else if (root.val > key) {
    
    
        root.left = deleteNode(root.left, key);
    } else {
    
    
        root.right = deleteNode(root.right, key);
    }

    return root;
}

private TreeNode getMin(TreeNode root) {
    
    
    while (root.left != null) {
    
    
        root = root.left;
    }
    return root;
}

Implementación no recursiva:

 public void delete(int data) {
    
    
        Node temp = root;  // temp指向要删除的节点
        Node ftemp = null; // ftemp指向要删除节点的父节点

        while (temp != null && temp.data != data) {
    
    
            ftemp = temp;
            if (data > temp.data) {
    
    
                temp = temp.rchild;
            } else {
    
    
                temp = temp.lchild;
            }
        }

        if (temp == null) return; // 没有找到对应的节点

        // 若找到,temp就是要删除的节点
        // 要删除的节点有两个子节点
        if (temp.lchild != null && temp.rchild != null) {
    
    
            Node minTemp = temp.rchild;  // 存储右子树的最小节点
            Node fminTemp = temp; // minTemp的父节点

            // 找到右子树的最小节点
            while (minTemp.lchild != null) {
    
    
                fminTemp = minTemp;
                minTemp = minTemp.lchild;
            }

            temp.data = minTemp.data; // 将最小节点的值替换到temp中
            temp = minTemp;  // 变成删除叶子节点
            ftemp = fminTemp;
        }

        // 删除节点是叶子节点或者仅有一个节点
        Node child; // temp的子节点
        if (temp.lchild != null) {
    
    
            child = temp.lchild;
        } else if (temp.rchild != null) {
    
    
            child = temp.rchild;
        } else {
    
    
            child = null;
        }

        if (ftemp == null) {
    
      // 删除的是根节点
            root = child;
        } else if (ftemp.lchild == temp) {
    
    
            ftemp.lchild = child;
        } else {
    
    
            ftemp.rchild = child;
        }
    }

Árbol de búsqueda binaria que admite datos repetidos

Las operaciones anteriores en el árbol de búsqueda binaria son para el caso donde no hay datos duplicados. Si necesita almacenar datos duplicados, al insertar datos, si el valor de un nodo es igual al valor a insertar, colocamos los datos a insertar en el subárbol derecho del nodo. En otras palabras, trate los datos recién insertados como un valor mayor que este nodo.

Al buscar datos, cuando nos encontramos con un nodo con el mismo valor, no detenemos la operación de búsqueda, sino que continuamos buscando en el subárbol derecho hasta encontrar un nodo hoja. De esta forma, se pueden conocer todos los nodos que cumplen los requisitos.
Al eliminar un nodo, también debe buscar cada nodo que se eliminará primero y luego eliminarlo a su vez de acuerdo con la operación de eliminación anterior.

Encuentra los nodos más grandes y más pequeños

    public Node findMin() {
    
    
        if (root == null) return null;
        Node temp = root;
        while (temp.lchild != null) {
    
    
            temp = temp.lchild;
        }
        return temp;
    }

    public Node findMax() {
    
    
        if (root == null) return null;
        Node temp = root;
        while (temp.rchild != null) {
    
    
            temp = temp.rchild;
        }
        return temp;
    }

Recorrido del árbol binario

Recorrido de preorden, medio y posorden (recursivo)

Para recorrer el árbol de búsqueda binaria, puede generar una secuencia de datos ordenada, y la complejidad de tiempo es O (n), que es muy eficiente

    // 前序遍历
    public void preOrder(Node node) {
    
    
        if (node != null) {
    
    
            System.out.println(node.data);
            preOrder(node.lchild);
            preOrder(node.rchild);
        }
    }

    // 中序遍历
    public void inOrder(Node node) {
    
    
        if (node != null) {
    
    
            inOrder(node.lchild);
            System.out.println(node.data);
            inOrder(node.rchild);
        }
    }

    // 后序遍历
    public void postOrder(Node node) {
    
    
        if (node != null) {
    
    
            postOrder(node.lchild);
            postOrder(node.rchild);
            System.out.println(node.data);
        }
    }

Recorrido de pedido previo, de orden medio y posterior al pedido (no recursivo)

prólogo

    // 前序遍历
    public void preOrderTraversal(Node node) {
    
    
        if (node == null) return;

        Stack<Node> stack = new Stack<>();
        stack.push(node);

        while (!stack.isEmpty()) {
    
    
            node = stack.pop();
            if (node.rchild != null) {
    
    
                stack.push(node.rchild);
            }
            if (node.lchild != null) {
    
    
                stack.push(node.lchild);
            }
            System.out.println(node.data);
        }
    }

Orden medio

    // 中序遍历
    public void inOrderTraversal(Node node) {
    
    
        Stack<Node> stack = new Stack<>();

        while (node != null || !stack.isEmpty()) {
    
    
            if (node != null) {
    
    
                stack.push(node);
                node = node.lchild;
            } else {
    
    
                node = stack.pop();
                System.out.println(node.data);
                node = node.rchild;
            }
        }
    }

Publicar secuencia

    // 后序遍历
    public void postOrderTraversal(Node node) {
    
    
        if (node == null) return;

        Stack<Node> stack1 = new Stack<>();
        Stack<Node> stack2 = new Stack<>();

        stack1.push(node);
        while (!stack1.isEmpty()) {
    
    
            node = stack1.pop();
            stack2.push(node);
            if (node.lchild != null) {
    
    
                stack1.push(node.lchild);
            }
            if (node.rchild != null) {
    
    
                stack1.push(node.rchild);
            }
        }
        while (!stack2.isEmpty()) {
    
    
            System.out.println(stack2.pop().data);
        }
    }

Recorrido de secuencia

// 层序遍历
public void levelOrder(Node node) {
    
    
    if (node == null) return;

    Queue<Node> queue = new LinkedList<>();
    queue.offer(node);

    while (!queue.isEmpty()) {
    
    
        node = queue.poll();
        System.out.println(node.data);
        if (node.lchild != null) {
    
    
            queue.offer(node.lchild);
        }
        if (node.rchild != null) {
    
    
            queue.offer(node.rchild);
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_46122005/article/details/110895892
Recomendado
Clasificación