数据结构-二叉查找树(查找,插入,删除,遍历)

一、什么是二叉查找树

二叉查找树是一颗空树,或者是具有一下性质的二叉树:
1.若根结点的左子树不为空,那么左子树上所有结点的值都小于根节点的值
2.若根结点的右子树不为空,那么右子树上所有结点的值都小于根节点的值
3.根节点的左子树和右子树也是二叉查找树

下图就是一颗二叉查找树,每个结点的值都大于他的左子树的所有结点的值,每个结点的值都小于他的右子树所有结点的值。


图一

下图也是一个二叉查找树,虽然他是一个斜树,树的高度等于结点的数量,但是他也符合二叉查找树的定义(只是搜索的效率很低)。


图二

二、二叉查找树的作用

从名字就可以看出,我们构造二叉查找树的作用是高效地查找、插入和删除。

2.1 查找

比如,我们在图一所示的二叉查找树中查找数字5是否存在,查找的步骤如下:
1、5和结点④比较,5大于4,下一步我们要搜索结点④的右子树(根据二叉查找树的定义,一个结点值小于他的右子树上所有结点的值)
2、5和结点⑥比较,5小于6,下一步我们要搜索结点⑥的左子树
3、5等于结点5,查找成功

2.2 插入

我们依然用图一作为例子,这次我们要在图一所示的二叉树中插入一个7,步骤如下:
1、7和根结点④比较,7大于4,下一步搜索右子树
2、7和结点⑥比较,7大于6,下一步搜索右子树
3、7和结点⑧比较,7小于8,下一步搜索左子树
4、8的左子树为空,那么这里就是7应该插入的位置,将7插入到8的左子树中。
如下如所示


图三-插入结点

2.3删除结点

删除结点比较复杂,我们需要分为四种情况。
我们以下图为例来分析这四种情况


图四
2.3.1 待删除的结点既没有左子树有没有右子树

用图四作为例子,我们需要删除结点9,步骤如下
1、我们首先要找到结点9的父节点8(如果要删除的结点没有父节点,也就是要删除的结点就是根结点,那么直接将树设置为空树即可)。
2、我们将结点9从其父节点8的右子树上删除。


图五
2.3.2 待删除的结点只有左子树

用图四作为例子,我们要删除结点15,它只有左子树,删除的步骤如下
1、找到待删除的结点15
2、将结点15的左子结点的值12赋值给结点15
3、将结点结点12的左子树和右子树分别设置为待删除结点的左子树和右子树
如下图所示:


图六
2.3.3 待删除的结点只有右子树

用图四作为例子,我们要删除结点5,和2.3.2类似,步骤如下
1、找到待删除的结点5
2、将结点5的右子结点的值8赋值给结点5
3、将结点结点8的左子树和右子树分别设置为待删除结点的左子树和右子树

2.3.3 待删除的结点有左子树又有右子树

首先我们应该知道,二叉查找树中序遍历时结点的值递增的(大家可以自己想一下为什么)。
用图四作为例子,我们要删除结点10,为了保持二叉查找树的性质,我们需要找到中序遍历中10的左边一个结点或者右边一个结点来替代10的位置,这里我们选择中序遍历中10的左边一个结点。删除的步骤如下
1、首先找到待删除的结点
2、找到中序遍历中10的左边的那个结点9和该节点的父节点8
3、删除结点9
4、将结点10的值替换为9
如下图所示


图七

2.4 时间复杂度分析

当二位查找树是一颗完全二叉树时,树的高度是log(n) + 1,那么查找,插入,删除的时间复杂度都是log(n)。
但是如果二叉查找树不平衡,更机端的情况下变为一个斜树,如图二所示,那么时间复杂度就会退化为O(n)。

三、代码

这里我使用java来写二叉查找树的搜索,插入,删除。其他语言思路一样。
我创建了一个类BinarySearchTree,类中有一个静态内部类TreeNode用来表示二叉树的结点,类中有一个TreeNode类型成员变量head用来保存二叉查找树的根节点。
思路在第二章已经说明,这里就不多解释了。
大家多练练手,明白其中的原理即可。

package MyBinarySearchTree;

import java.util.Stack;


public class BinarySearchTree {
    /**
     * 在二叉搜索树中搜索是否存在指定的结点,如果存在指定结点,返回该结点,否则返回该结点在二叉树中所处位置的父节点
     * @param head
     * @param key
     * @return
     */
    public TreeNode head = null;  //根节点
    /**
     * 设置根节点的值
     * @param val
     */
    public void setHeadValue(int val) {
        if (head == null) {
            head = new TreeNode();
            head.value = val;
        } else {
            head.value = val;
        }
    }
    /**
     * 在二叉搜索树中搜索指定结点
     * @param key
     * @return
     */
    public boolean search(int key) {
        TreeNode res = search(head, null, key);
        if (res != null && res.value == key) {
            return true;
        } else {
            return false;
        }
    }
    /**
     * 输入根节点,父节点,和要查找的值,找到指定结点,如果该节点存在?返回该结点:否则返回该结点的父节点
     * @param myHead
     * @param father
     * @param key
     * @return
     */
    private TreeNode search(TreeNode myHead, TreeNode father, int key) {
        if (myHead == null) {
            return father;
        } else if (myHead.value == key) {
            return myHead;
        } else if (myHead.value > key) {
            return search(myHead.left, myHead, key);
        } else {
            return search(myHead.right, myHead, key);
        }
    }
    /**
     * 插入结点,返回结点引用
     * @param head
     * @param key
     * @return
     */
    public TreeNode insert(int key) {
        TreeNode res = null;
        if (head == null) {   //如果树为空,创建根节点,返回
            head = new TreeNode();
            head.value = key;
            res = head;
        } else {
            res = search(head, null, key);  //搜索该节点
            if (res.value != key) {     //如果二叉搜索树中不存在该结点,进行插入
                TreeNode newNode = new TreeNode();
                newNode.value = key;
                if (newNode.value > res.value) {
                    res.right = newNode;
                } else {
                    res.left = newNode;
                }
                res = newNode;
            }
        }
        return res;
    }
    /**
     * 删除指定结点,如果存在该结点,返回true,否则返回false
     * @param key
     * @return
     */
    public boolean delete(int key) {
        if (head == null) {
            return false;
        }
        TreeNode parent = null;
        TreeNode cur = head;
        while (cur != null) {  //找到要删除的结点和其父节点
            if (cur.value == key) {
                break;
            } else if (cur.value > key){
                parent = cur;
                cur = cur.left;
            } else {
                parent = cur;
                cur = cur.right;
            }
        }
        if (cur == null) { //如果不存在该结点,不需要删除,返回
            return false;
        } else {           //二叉搜索树中存在该结点
            if (cur.left == null && cur.right == null) {  //要删除的结点的左右子树都为空
                if (parent == null) { //要删除的结点就是根节点,并且其没有子结点,将根节点设置为null
                    head = null;
                } else {  //从parent中删除该结点
                    if (parent.left == cur) {
                        parent.left = null;
                    } else {
                        parent.right = null;
                    }
                }
            } else if (cur.left == null) {  //要删除的结点的左子树为空,要删除结点的右结点顶替
                cur.value = cur.right.value;
                cur.left = cur.right.left;
                cur.right = cur.right.right;
            } else if (cur.right == null) {  //要删除的结点的右子树为空,要删除结点的左结点顶替
                cur.value = cur.left.value;
                cur.right = cur.left.right;
                cur.left = cur.left.left;
            } else {   //要删除的结点的所有子树都不为空,找到中序遍历的前一个结点,替换并删除。
                TreeNode temp = cur;
                parent = cur;
                cur = cur.left;
                while (cur.right != null) {
                    parent = cur;
                    cur = cur.right;
                }
                temp.value = cur.value;
                if (parent.left == cur) {
                    parent.left = cur.left;
                } else {
                    parent.right = cur.left;
                }
            }
            return true;
        }
    }
    /**
     * 中序遍历
     */
    public void midSearch() {
        TreeNode cur = head;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            cur = stack.pop();
            System.out.println(cur.value);
            cur = cur.right;
        }
    }
    /**
     * 二叉树结点
     * @author TinyMonster
     *
     */
    public static class TreeNode {
        int value = 0;
        TreeNode left = null;
        TreeNode right = null;
    }
    /*
     * 
     */
    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();
        tree.setHeadValue(4);
        tree.insert(2);
        tree.insert(1);
        tree.insert(3);
        tree.insert(6);
        tree.insert(5);
        tree.insert(7);
        tree.midSearch();
        System.out.println("-----查找结点7-----");
        System.out.println(tree.search(7));
        System.out.println("-----删除结点7-----");
        tree.delete(7);
        tree.midSearch();
        System.out.println("-----删除结点4-----");
        tree.delete(4);
        tree.midSearch();
    }
}

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