查找-二叉搜索树(Java实现)

版权声明:本文源自简书tianma,转载请务必注明出处:http://www.jianshu.com/p/9155e2e9e8c1

前言

如果查找的数据集是有序的线性表,并且是顺序存储的,查找可以用折半查找、插值查找、斐波那契查找算法(详细算法见:有序表查找(折半、插值、斐波那契查找))等实现。但是正是因为他们是顺序的,所以在插入和删除操作中需要耗费大量时间,也就是说这些算法适合静态查找(只有查找操作),不适合动态查找(不仅有查找操作还有插入删除等操作)。而二叉搜索树正适合动态查找。

定义

二叉搜索树又称为二叉排序树,它或者是空树,或者是具有下列性质的二叉树:

  1. 如果它的左子树不为空,那么左子树的所有节点都小于根节点的值;
  2. 如果它的右子树不为空,那么右子树的所有节点都大于根节点的;
  3. 它的左、右子树也分别是二叉搜索树.

二叉树是递归定义的数据结构,其中序遍历是递增的有序序列。

操作

1. 插入
插入节点的过程是:若原二叉查找树为空,则直接插入;否则,若关键字 k 小于根节点关键字,则插入到左子树中,若关键字 k 大于根节点关键字,则插入到右子树中。注意每次插入的节点必是叶节点。

2. 删除
二叉查找树的删除操作是相对复杂一点,它要按 3 种情况来处理:

  • 若被删除节点 t 是叶子节点,则直接删除,不会破坏二叉排序树的性质;
  • 若节点 t 只有左子树或只有右子树,则让 t 的子树成为 t 父节点的子树,替代 t 的位置;
  • 若节点 t 既有左子树,又有右子树,则用 t 的直接前驱或者直接后继代替 t,然后从二叉查找树中删除这个后继,这样就转换成了第一或第二种情况。

3. 查找
查找是从根节点开始,若二叉树非空,将给定值与根节点的关键字比较,若相等,则查找成功;若不等,则当给定值小于根节点关键字时,在根节点的左子树中查找,否则在根节点的右子树中查找。
其查找平均时间复杂度为O(logn),但是最差情况为插入的节点是有序的,则该二叉搜索树会变成左斜树(或者右斜树或者可以理解为“链表”),即最差时间复杂度为O(n),故而查找性能不是严格意义上的O(logn),不稳定。

Java实现
public class SortedBinaryTree<E> {

    private Node<E> root; // 根节点

    private int size; // 二叉树元素个数

    /**
     * 二叉树节点
     */
    private static class Node<E> {
        E element; // 节点元素
        Node<E> lChild; // 左孩子
        Node<E> rChild; // 右孩子

        public Node(E element) {
            this(element, null, null);
        }

        public Node(E element, Node<E> lChild, Node<E> rChild) {
            this.element = element;
            this.lChild = lChild;
            this.rChild = rChild;
        }
    }

    public SortedBinaryTree(List<E> elements) {
        for (E e : elements) {
            add(e);
        }
    }

    public SortedBinaryTree(E[] elements) {
        for (E e : elements) {
            add(e);
        }
    }

    public SortedBinaryTree() {
    }

    /**
     * 判断当前元素是否存在于树中
     * 
     * @param element
     * @return
     */
    public boolean contains(E element) {
        return search(root, element);
    }

    /**
     * 递归搜索,查找当前以curRoot为根节点的树中element存在与否
     * 
     * @param curRoot
     * @param element
     * @return
     */
    @SuppressWarnings("unchecked")
    private boolean search(Node<E> curRoot, E element) {
        if (curRoot == null)
            return false;
        Comparable<? super E> e = (Comparable<? super E>) element;
        int cmp = e.compareTo(curRoot.element);
        if (cmp > 0) {
            // 查找的元素大于当前根节点对应的元素,向右走
            return search(curRoot.rChild, element);
        } else if (cmp < 0) {
            // 查找的元素小于当前根节点对应的元素,向左走
            return search(curRoot.lChild, element);
        } else {
            // 查找的元素等于当前根节点对应的元素,返回true
            return true;
        }
    }

    /**
     * 非递归搜索,查找当前以curRoot为根节点的树中的element是否存在
     * 
     * @param curRoot
     *            二叉排序树的根节点
     * @param element
     *            被搜索的元素
     * @param target
     *            target[0]指向查找路径上最后一个节点: 如果当前查找的元素存在,则target[0]指向该节点
     * @return
     */
    @SuppressWarnings("unchecked")
    private boolean find(Node<E> curRoot, E element, Node<E>[] target) {
        if (curRoot == null)
            return false;
        Node<E> tmp = curRoot;
        Comparable<? super E> e = (Comparable<? super E>) element;
        while (tmp != null) {
            int cmp = e.compareTo(tmp.element);
            target[0] = tmp;
            if (cmp > 0) {
                // 查找的元素大于当前节点对应的元素,向右走
                tmp = tmp.rChild;
            } else if (cmp < 0) {
                // 查找的元素小于当前节点对应的元素,向左走
                tmp = tmp.lChild;
            } else {
                // 查找的元素等于当前根节点对应的元素,返回true
                return true;
            }
        }
        return false;
    }

    /**
     * 向二叉排序树中添加元素,如果当前元素已经存在,则添加失败,返回false,如果当前元素不存在,则添加成功,返回true
     * 
     */
    @SuppressWarnings("unchecked")
    public boolean add(E element) {
        if (root == null) {
            root = new Node<E>(element);
            size++;
            return true;
        }
        Node<E>[] target = new Node[1];
        if (!find(root, element, target)) {
            // 当前元素不存在,插入元素
            // 此时target节点即为需要插入的节点的父节点
            Comparable<? super E> e = (Comparable<? super E>) element;
            int cmp = e.compareTo(target[0].element);
            Node<E> newNode = new Node<E>(element);
            if (cmp > 0) {
                // 插入的元素大于target指向的节点元素
                target[0].rChild = newNode;
            } else {
                // 插入的元素小于target指向的节点元素
                target[0].lChild = newNode;
            }
            size++;
            return true;
        }
        return false;
    }

    /**
     * 删除二叉排序树中的元素,如果当前元素不存在,则删除失败,返回false;如果当前元素存在,则删除该元素,重构二叉树,返回true
     * 
     * @param element
     * @return
     */
    @SuppressWarnings("unchecked")
    public boolean remove(E element) {
        Node<E>[] target = new Node[1];
        if (find(root, element, target)) {
            // 被删除的元素存在,则继续执行删除操作
            remove(target[0]);
            return true;
        }
        return false;
    }

    /**
     * 释放当前节点
     * 
     * @param node
     */
    private void free(Node<E> node) {
        node.element = null;
        node.lChild = null;
        node.rChild = null;
        node = null;
    }

    /**
     * 删除二叉排序树中指定的节点
     * 
     * @param node
     */
    private void remove(Node<E> node) {
        Node<E> tmp;
        if (node.lChild == null && node.rChild == null) {
            // 当前node为叶子节点,删除当前节点,则node = null;
            node = null;
        } else if (node.lChild == null && node.rChild != null) {
            // 如果被删除的节点左子树为空,则只需要重新连接其右子树
            tmp = node;
            node = node.rChild;
            free(tmp);
        } else if (node.lChild != null && node.rChild == null) {
            // 如果被删除的节点右子树为空,则只需要重新连接其左子树
            tmp = node;
            node = node.lChild;
            free(tmp);
        } else {
            // 当前被删除的节点左右子树均存在,不为空
            // 找到离当前node节点对应元素且最近的节点target(左子树的最右边节点 或者 右子树最左边节点)
            // 将node节点元素替换成target节点的元素,将target节点删除
            tmp = node; // tmp是target的父节点
            Node<E> target = node.lChild; // 找到左子树最大子树
            while (target.rChild != null) { // 在左子树中进行右拐
                tmp = target;
                target = target.rChild;
            }
            node.element = target.element; // node.element元素替换为target.element
            if (tmp == node) {
                // tmp == node 说明没有在左子树中进行右拐,也就是node节点的左孩子没有右孩子,
                // 需要重新连接tmp节点左孩子
                tmp.lChild = target.lChild;
            } else {
                // tmp != node, 进行了右拐,那么将重新连接tmp的右子树,将target.lChild赋值给tmp.rChild
                tmp.rChild = target.lChild;
            }
            // 释放节点
            free(target);
        }
        // 删除成功,size--;
        size--;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    public List<E> preOrderTraverse() {
        List<E> list = new ArrayList<E>();
        preOrderTraverse(root, list);
        return list;
    }

    private void preOrderTraverse(Node<E> curRoot, List<E> list) {
        if (curRoot == null)
            return;
        E e = curRoot.element;
        list.add(e);
        preOrderTraverse(curRoot.lChild, list);
        preOrderTraverse(curRoot.rChild, list);
    }

    public List<E> inOrderTraverse() {
        List<E> list = new ArrayList<E>();
        inOrderTraverse(root, list);
        return list;
    }

    private void inOrderTraverse(Node<E> curRoot, List<E> list) {
        if (curRoot == null)
            return;
        inOrderTraverse(curRoot.lChild, list);
        list.add(curRoot.element);
        inOrderTraverse(curRoot.rChild, list);
    }

    public List<E> postOrderTraverse() {
        List<E> list = new ArrayList<E>();
        postOrderTraverse(root, list);
        return list;
    }

    private void postOrderTraverse(Node<E> curRoot, List<E> list) {
        if (curRoot == null)
            return;
        inOrderTraverse(curRoot.lChild, list);
        inOrderTraverse(curRoot.rChild, list);
        list.add(curRoot.element);
    }

    /**
     * 返回中序遍历结果
     */
    @Override
    public String toString() {
        return inOrderTraverse().toString();
    }

    public static void main(String[] args) {
        Integer[] elements = new Integer[] { 62, 88, 58, 47, 73, 99, 35, 51, 93, 29, 37, 49, 56, 36, 48, 50 };
        SortedBinaryTree<Integer> tree = new SortedBinaryTree<Integer>(elements);
        System.out.println(tree);
        System.out.println(tree.contains(93));
        System.out.println(tree.size());
        System.out.println(tree.remove(47));
        System.out.println(tree.preOrderTraverse());
        System.out.println(tree.size());
    }

}

Github地址
SortedBinaryTree

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容