java 二叉搜索树

java二叉查找树实现:

二叉查找树,上图:比根节点小者在其左边,比根节点大者在其右边。

抽象数据结构,上代码:

/**
 * 二叉查找树数据结构(非线程安全):
 * 范型类型须实现Comparable接口,用于比较操作
 */
public class BinarySearchTree<T extends Comparable<T>> {
    private Node<T> root; // tree root
    private int count;    // tree element counts

    /**
     * 内部节点类
     */
    private static class Node<E>{
        E value; //元素对象
        Node<E> parent; //父节点
        Node<E> left; //左孩子节点
        Node<E> right; //右孩子节点
        public Node(E value, Node<E> parent, Node<E> left, Node<E> right) {
            this.value = value;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }
    }
}

一些基本操作实现:

  • 插入(insert): 依次比较根元素,小者放左边,大者放右边:
/**
 * 插入元素
 * @param t 待插入元素
 * @return 插入成功返回true, 反之返回false
 */
public boolean insert(T t){
    if (root == null){ //若为空树
        root = new Node<T>(t, null, null, null);
        return true;
    }
    Node<T> newNode = new Node<T>(t, null, null, null);
    Node<T> pointer = root;
    while(true){
    if (newNode.value.compareTo(pointer.value) > 0){
        if (pointer.right == null){ //插入右边
        newNode.parent = pointer;
        pointer.right = newNode;
        count++;
        return true;
        } else{
        pointer = pointer.right;
        }
    } else if (newNode.value.compareTo(pointer.value) < 0){
        if (pointer.left == null){ //插入左边
        newNode.parent = pointer;
        pointer.left = newNode;
        count++;
        return true;
        } else{
        pointer = pointer.left;
        }
    } else { //相等了
        return false;
    }
    }
}
  • 查找(get):
/**
 * 查找元素
 * @param t 待查找元素
 * @return 对应元素或null
 */
public T get(T t) {
    Node<T> n = getN(t);
    return n == null? null : n.value;
}

/**
 * 查找节点
 * @param t 待查找元素
 * @return 元素对应节点或null
 */
private Node<T> getN(T t) {
    Node<T> cur = root;
    while (cur != null){
        if (cur.value.compareTo(t) < 0){ //右边子树找
            cur = cur.right;
        } else if(cur.value.compareTo(t) > 0){ //左边子树找
            cur = cur.left;
        } else{ //找到该节点
            break;
        }
    }
    return cur;
}
  • 查找最大,最小元素:
/**
 * 获取某节点为根的树的最小元素
 */
public T min(Node<T> n){
    Node<T> min = minN(n);
    return min == null ? null : min.value;
}

/**
 * 获取某节点为根的树的最小节点
 * @param n 树根节点
 * @return 该子树最小节点
 */
private Node<T> minN(Node<T> n){
    Node<T> min = n;
    while (min != null && min.left != null){
        min = min.left;
    }
    return min;
}
/**
 * 获取某节点为根的树的最大元素
 * @return 最大元素, 没有返回null
 */
public T max(Node<T> n){
    Node<T> max = maxN(n);
    return max == null ? null : max.value;
}

/**
 * 获取某节点为根的树的最大节点
 */
private Node<T> maxN(Node<T> n){
    Node<T> max = n;
    while (max != null && max.right != null){
        max = max.right;
    }
    return max;
}
  • 遍历树(中序遍历):
/**
 * 中序遍历
 */
public void leftRootRight(){
    printLRR(root);
}

/**
 * 中序遍历打印元素
 */
private void printLRR(Node<T> node) {
    if (node != null){
        printLRR(node.left);
        System.out.println(node.value);
        printLRR(node.right);
    }
}
  • 获取前驱(prev)元素:

主要有两种情况:

1.该节点左子树不为空:其前驱节点为其左子树的最大元素:

2.该节点左子树为空: 其前驱节点为其祖先节点(递归),且该祖先节点的右孩子也为其祖先节点(就是一直往其parent找,出现左拐后的那个祖先节点):

代码实现:

/**
 * 获取元素前驱(中序遍历)
 * @param t 指定元素
 * @return 元素前驱,没有返回null
 */
public T prev(T t){
    //先找到该元素
    Node<T> cur = getN(t);
    if (cur != null){
        return locatePrev(cur);
    }
    return null;
}

/**
 * 定位到前驱节点
 * @param cur 当前节点
 * @return 前驱节点,没有返回null
 */
private T locatePrev(Node<T> cur) {
    Node<T> prev = locatePrevN(cur);
    return prev == null ? null : prev.value;
}

/**
 * 定位到前驱节点
 * @param cur 当前节点
 * @return 当前节点的前驱节点
 */
private Node<T> locatePrevN(Node<T> cur){
    if (cur != null){
        //1.如果该节点左子树不会空,则其前驱为其左子树的最大元素
        if (cur.left != null) return maxN(cur.left);
        //2.该节点左子树为空, 则其前驱为:其祖先节点(递归), 且该祖先节点的右孩子也是其祖先节点
        //  通俗的说,一直忘上找找到左拐后那个节点;
        Node<T> p = cur.parent;
        while(p != null && cur == p.left){
            cur = p;
            p = p.parent;
        }
        return p == null ? null: p;
    }
    return null;
}
  • 获取后继节点,也分两种情况:

1.该节点右子树不为空,其后继节点为其右子树的最小元素:

2.该节点右子树为空,其后继节点为其祖先节点(递归),且此祖先节点的左孩子也是该节点的祖先节点,就是说一直往上找其祖先节点,直到出现右拐后的那个祖先节点:

实现代码:

/**
 * 获取元素后继元素(中序遍历)
 * @param t 指定元素
 * @return 后继元素,没有返回null
 */
public T next(T t){
    //先找到该元素
    Node<T> cur = getN(t);
    if (cur != null){
        return locateNext(cur);
    }
    return null;
}

/**
 * 定位当前节点的后继元素
 * @param cur 当前节点
 * @return 其后继元素
 */
private T locateNext(Node<T> cur) {
    Node<T> next = locateNextN(cur);
    return next == null ? null : next.value;
}

/**
 * 定位到当前节点的后继节点
 * @param cur 当前节点
 * @return 当前节点的后继节点
 */
private Node<T> locateNextN(Node<T> cur) {
    if (cur == null) return null;
    //1.若其右子树不为空,那么其后继节点就是其右子树的最小元素
    if (cur.right != null) return minN(cur.right);
    //2.若为空,应该为其祖先节点(递归),且该祖先节点的左孩子也是其祖先节点
    //  通俗的说,一直忘上找,找到右拐后那个节点;
    Node<T> p = cur.parent;
    while (p != null && cur == p.right){
        cur = p;
        p = p.parent;
    }
    return p;
}
  • 删除(remove), 可分为三种情况:

1.该节点为叶子节点,直接删除:

2.该节点有一个孩子,将其孩子接上其父节点:

3.该节点有2个孩子,先删除其右子树的最小元素(该元素最多只会有一个孩子),将这个最小元素去替换要删除的节点:

实现代码:

/**
 * 移除某元素
 * @param t 待删除元素
 * @return 删除成功返回true, 反之false
 */
public boolean remove(T t){
    //找到该节点
    Node<T> cur = getN(t);
    if (cur != null){
        if (doRemove(cur)){
            cur=null; count--;
            return true;
        }
    }
    return false;
}

/**
 * 执行删除操作
 */
private boolean doRemove(Node<T> cur) {
    //该节点是否为根
    boolean isRoot = cur == root;
    //1.该节点为叶子节点, 直接将其父节点对应(左或右)孩子置空
    if (cur.left == null && cur.right == null){
        if (isRoot) return true; //若树只有一个根节点
        if (cur == cur.parent.right) //该节点为父节点的右孩子
            cur.parent.right = null;
        else                    //该节点为父节点的左孩子
            cur.parent.left = null;
        return true;
    } else if(cur.left != null && cur.right != null){
        //2.该节点有2个孩子, 我们先找出一个替换节点(该节点的后继节点,后继节点没有则前驱节点)
        //找到其后继节点
        Node<T> replaceNode = locateNextN(cur);
        if (replaceNode == null) //若没有后继节点则用前驱节点
            replaceNode = locatePrevN(cur);
        doRemove(replaceNode);
        cur.value = replaceNode.value;
        return true;
    } else{ //3.该节点有1个孩子, 直接将其父节点对应(左或右)孩子接到其非空孩子
        Node<T> needLinkedNode = null;
        if (cur.left == null && cur.right != null){ //该节点有右孩子
            needLinkedNode = cur.right;
        } else if(cur.left != null && cur.right == null){ //该节点有左孩子
            needLinkedNode = cur.left;
        }
        if(isRoot){ //若该节点为根
            root = needLinkedNode;
            return true;
        }
        if (cur == cur.parent.right)  //该节点为父节点右孩子
            cur.parent.right = needLinkedNode;
        else
            cur.parent.left = needLinkedNode;
        return true;
    }
}
				
时间: 2024-10-23 21:29:24

java 二叉搜索树的相关文章

Java数据结构之二叉搜索树

Java数据结构之二叉搜索树 1.二叉搜索树组成 二叉搜索树又称为二叉排序树,它或者是一颗空树,或者是一颗具有如下特性的非空二叉树,需要满足一下三个条件: (1)若它的左子树非空,则左子树上所有结点的关键字均小于根结点的关键字: (2)若它的右子树非空,则右子树上所有结点的关键字均大于(可以等于)根结点的关键字. (3)左子树右子树本身又各是一颗二叉搜索树 在算法描述中,均以结点值的比较来代表其关键字的比较,因为若结点的值为类类型时,该类必须实现系统提供的java.lang.comparable

平衡二叉搜索树(AVL树)的原理及实现源代码(有图文详解和C++、Java实现代码)

一.AVL树(平衡二叉搜索树)是什么? AVL树是根据它的发明者G.M. Adelson-Velsky和E.M. Landis命名的.AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树. 2.带有平衡条件:每个非叶子结点的左右子树的高度之差的绝对值(平衡因子)最多为1. 例如: 5             5 / \            /  \ 2   6         2   6 / \    \         / \ 1  4   7       1  4

二叉搜索树的Java实现

为了更加深入了解二叉搜索树,博主自己用Java写了个二叉搜索树,有兴趣的同学可以一起探讨探讨. 首先,二叉搜索树是啥?它有什么用呢? 二叉搜索树, 也称二叉排序树,它的每个节点的数据结构为1个父节点指针,1个左孩子指针,1个有孩子指针,还有就是自己的数据部分了,因为只有左右两孩子,所以才叫二叉树,在此基础上,该二叉树还满足另外一个条件:每个结点的左孩子都不大于该结点&&每个结点的右孩子都大于该结点.这样,我们队这棵树进行中序遍历,就能把key从小到大排序了…… 那么问题来了,我都有线性表有

Java实现二叉搜索树的添加,前序、后序、中序及层序遍历,求树的节点数,求树的最大值、最小值,查找等操作

什么也不说了,直接上代码. 首先是节点类,大家都懂得 /** * 二叉树的节点类 * * @author HeYufan * * @param <T> */ class Node<T extends Comparable<? super T>> { /** * 节点储存的值 */ private T data; /** * 左子节点 */ private Node<T> leftNode; /** * 右子节点 */ private Node<T>

二叉树、二叉搜索树、AVL树的java实现

数据结构一直都是断断续续的看,总是觉得理解的不够深入,特别是对树的理解,一直都很浅显,今儿又看了一遍,来做个总结吧. 首先,树中的一些概念: 1.树的节点包含一个数据元素,以及若干指向其子树的分支.节点拥有的子树的数量称为节点的度.节点的最大层次称为树的深度或高度. 2.二叉树是一种树形结构,其特点是每个节点至多有两棵子树,且子树有左右之分,次序不能随意颠倒. 3.满二叉树:一棵深度为k且有2^k - 1个节点的二叉树,称之为满二叉树. 4.完全二叉树:对一个深度为k,节点个数为n的二叉树,当且

剑指Offer面试题24(Java版):二叉搜索树的后序遍历序列

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则返回true,否则返回false.假设输入的数组的任意两个数字都互不相同. 例如输入数组{5,7,6,9,11,10,8}则返回true,因为这个整数序列是下图二叉树的后序遍历的结果.如果输入的数组是{7,4,6,5},由于没有哪颗二叉搜索树的后续遍历的结果是这个序列,因此返回false. 在后序遍历得到的序列中,最后一个数字是树的根节点的值.数组中前面的数字可以分为两部分:第一部分是左子树结点的值,它们都比根节点的值

【LeetCode-面试算法经典-Java实现】【096-Unique Binary Search Trees(唯一二叉搜索树)】

[096-Unique Binary Search Trees(唯一二叉搜索树)] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 原题 Given n, how many structurally unique BST's (binary search trees) that store values 1-n? For example, Given n = 3, there are a total of 5 unique BST's. 1 3 3 2 1 \ / / /

【LeetCode-面试算法经典-Java实现】【098-Validate Binary Search Tree(验证二叉搜索树)】

[098-Validate Binary Search Tree(验证二叉搜索树)] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 原题 Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is defined as follows: The left subtree of a node contains only nodes with keys le

剑指Offer面试题27(Java版):二叉搜索树与双向链表

题目:输入一颗二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建新的结点.仅仅能调整树中结点指针的指向. 比方例如以下图中的二叉搜索树.则输出转换之后的排序双向链表为: 在二叉树中,每一个结点都有两个指向子节点的指针.在双向链表中.每一个结点也有两个指针.他们分别指向前一个结点和后一个结点.因为这两种结点的结构相似,同一时候二叉搜索树也是一种排序的数据结构.因此在理论上有可能实现二叉搜索树和排序的双向链表的转换. 在搜索二叉树中,左子结点的值总是小于父节点的值,右子节点的值总是大于