二叉查找树的创建及遍历

二叉查找树的创建及遍历

前言

对于线性表,或者符号表,无论是利用数组实现,还是链表实现,都不能达到比较完美。
基于数组实现的顺序表,查询快(内存连续,通过索引即可查询),但增删慢(需要大量的数据移动);基于链表实现,查询慢(每次查询都要从头结点找起),但增删快(没有数据移动的操作)。
但是,树的特性可以弥补线性表中的不足。顺序表天生查询快,增删慢,可以结合树的特性使其增删加快;链表天生增删快,查询慢,可以结合树的特性使其查询加快。

本文讲解什么是树、什么是二叉树以及树的一些相关术语,并介绍二叉查找树的创建与遍历思路及代码实现。

一、树

树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构、等等。
树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
在这里插入图片描述

  • 树具有以下特点:
    1.每个结点有零个或多个子结点;
    2.没有父结点的结点为根结点;
    3.每一个非根结点只有一个父结点;
    4.每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;
  • 树的相关术语
    结点的度:
    一个结点含有的子树的个数称为该结点的度;
    叶结点:
    度为0的结点称为叶结点,也可以叫做终端结点
    分支结点:
    度不为0的结点称为分支结点,也可以叫做非终端结点
    结点的层次:
    从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推
    结点的层序编号:
    将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。
    树的度:
    树中所有结点的度的最大值
    树的高度(深度):
    树中结点的最大层次。
    从节点n1到nk的路径: 为节点n1,n2,…,nk的一个序列
    路径的长: 为该路径上边的条数,如n1到nk的路径长为k-1
    节点ni的深度:从根到ni的唯一路径的长
    节点ni的高度: 从节点ni到一片树叶的最长路径的长
    森林:
    m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根
    结点,森林就变成一棵树
    在这里插入图片描述孩子结点:
    一个结点的直接后继结点称为该结点的孩子结点
    双亲结点(父结点):
    一个结点的直接前驱称为该结点的双亲结点
    兄弟结点:
    同一双亲结点的孩子结点间互称兄弟结点

二、二叉树

二叉树就是度不超过2的树(每个结点最多有两个子结点)
在这里插入图片描述
满二叉树:
一个二叉树,如果每一个层的结点树都达到最大值,则这个二叉树就是满二叉树。
在这里插入图片描述
完全二叉树:
叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。即每层从左到右依次填满之后,再进行下一层。
在这里插入图片描述

三、二叉查找树

二叉查找树是用二叉树这种数据结构进行数据的查找。为了满足查找的需要,提高查找效率,要求二叉查找树每个结点的左子树上的值都小于该结点的值,右子树的值都大于该结点的值。
在这里插入图片描述

四、二叉查找树的创建

结点类设计
//二叉树结点
    private class Node
    {
        private Key key; //结点key
        private Value value; //结点值
        private Node left; //左子树
        private Node right; //右子树

        public Node(Key key, Value value, Node left, Node right)
        {
            this.key=key;
            this.value=value;
            this.right=right;
            this.left=left;
        }
    }
二叉查找树API设计

类名:BinaryTree<Key extends Comparable,Value>
构造方法: BinaryTree()
成员变量:
private Node root;//根节点
private int N; //树中元素的个数

成员方法方法说明
public int size()获取树中元素的个数
public void put(Key key,Value value)向树中插入一个键值对
private Node put(Node x, Key key, Value val)给子树x上,添加键一个键值对,并返回添加后的新树
public Value get(Key key)根据key,从树中找出对应的值
private Value get(Node x, Key key)从子树x中,找出key对应的值
public void delete(Key key)根据key,删除树中对应的键值对
private Node delete(Node x, Key key)删除子树x上的键为key的键值对,并返回删除后的新树
public Key min()获取树中最小键的key
public Node min(Node x)获取子树x中最小键的节点
public Key max()获取树中最大键的key
public Node max(Node x)获取子树x中最大键的结点
public Queue preOrder()使用前序遍历,获取整个树中的所有键
private void preOrder(Node x, Queue queue)前序遍历子树x中的所有key,并将key存入队列中
public Queue midOrder()使用中序遍历,获取整个树中的所有键
public void midOrder(Node x, Queue queue)使用中序遍历,获取子树x中的所有键,并存入队列
public Queue afterOrder()使用后序遍历,获取整个树中的所有键
public void afterOrder(Node x, Queue queue)使用后序遍历,获取子树x中的所有键,并存入队列
public Queue layerOrder()使用层序遍历,获取整个树中的所有键
public int maxDepth()计算整棵树的最大深度
private int maxDepth(Node x)计算子树x的最大深度
插入方法put实现思想:

1.如果当前树中没有任何一个结点,则直接把新结点当做根结点使用
2.如果当前树不为空,则从根结点开始:
2.1如果新结点的key小于当前结点的key,则继续找当前结点的左子结点;
2.2如果新结点的key大于当前结点的key,则继续找当前结点的右子结点;
2.3如果新结点的key等于当前结点的key,则树中已经存在这样的结点,替换该结点的value值即可。
在这里插入图片描述
在这里插入图片描述

 //向树中插入一个键值对
    public void put(Key key,Value value)
    {
        //向root树插入结点
        root = put(root, key, value);
    }

    //给子树x上,添加键一个键值对,并返回添加后的新树
    private Node put(Node x, Key key, Value val)
    {
        //如果树x为null,则将待添加的节点作为x树的根节点,完成真正添加动作
        if(x==null)
        {
            x = new Node(key, val, null, null);
            N++;
            return x;
        }
        int compare = key.compareTo(x.key);
        //如果key大于x.key,则将结点添加到x的右子树
        if(compare>0)
        {
            x.right = put(x.right, key, val);
        }
        //如果key小于x.key,则将结点添加到x的左子树
        else if(compare<0)
        {
            x.left = put(x.left,key,val);
        }
        //如果key等于x.key,则更新x的值
        else
        {
            x.value=val;
        }

        return x;
    }
查询方法get实现思想:

从根节点开始:
1.如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
2.如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
3.如果要查询的key等于当前结点的key,则树中返回当前结点的value。

//根据key,从树中找出对应的值
    public Value get(Key key)
    {
        // 从root树找到key的值
        return get(root,key);
    }

    //从子树x中,找出key对应的值
    private Value get(Node x, Key key)
    {
        //x树为null
        if(x==null)
            return null;
        //x树不为null
        int compare = key.compareTo(x.key);
        //如果key大于x.key,则将结点添加到x的右子树
        if(compare>0)
        {
            return get(x.right,key);
        }
        //如果key小于x.key,则将结点添加到x的左子树
        else if(compare<0)
        {
            return get(x.left,key);
        }
        //如果key等于x.key,则找到,完成真正找到动作,返回值即可
        else
        {
            return x.value;
        }
    }
删除方法delete实现思想:

1.找到被删除结点;
2.找到被删除结点右子树中的最小结点minNode
3.删除右子树中的最小结点
4.让被删除结点的左子树成为最小结点minNode的左子树,让被删除结点的右子树成为最小结点minNode的右子

5.让被删除结点的父节点指向最小结点minNode
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

 //根据key,删除树中对应的键值对
    public void delete(Key key)
    {
        // 删除root树中键为key的结点
        root = delete(root,key);
    }

    //删除子树x上的键为key的键值对,并返回删除后的新树
    private Node delete(Node x, Key key)
    {
        //1.如果x为null
        if(x==null)
            return null;
        //2.如果x不为null,寻找待删除的key在x的左子树还是右子树,或者就是当前结点
        int compare = key.compareTo(x.key);
        //2.1 如果待删除key小于x.key,则待删除key结点在右子树上,递归删除右子树上该结点
        if (compare<0)
        {
            x.left = delete(x.left,key);
        }
        //2.2 如果待删除key大于x.key,则待删除key结点在左子树上,递归删除右子树上该结点
        else if(compare>0)
        {
            x.right = delete(x.right,key);
        }
        //2.3 如果待删除key等于x.key,则待删除key结点就是当前结点,完成真正删除动作
        else
        {
            //让元素个数-1
            N--;

            // 如果当前结点右子树为null,则左子树即为删除后的新树,返回即可
            if (x.right==null)
                return x.left;
            // 如果当前节点左子树为null,则右子树即为删除后的新树,返回即可
            if (x.left==null)
                return x.right;
            // 找当前结点右子树的最小key值节点
            Node minNode=x.right;
            while (minNode.left!=null)
            {
                minNode=minNode.left;
            }
            //删除当前结点右子树的最小key值节点
            Node node = x.right;
            while (node.left!=null)
            {
                if (node.left.left!=null)
                {
                    node=node.left;
                }
                else
                {
                    //如果当前结点右子树的最小key值节点为叶子节点
                    if (node.left.right==null)
                        node.left=null;
                    //如果当前结点右子树的最小key值结点有右子树,让该右子树成为当前结点右子树的最小key值结点父结点的左子树
                    else
                        node.left=node.left.right;
                }
            }

            //让x的左子树成为minNode的左子树
            minNode.left=x.left;
            //让x的右子树成为minNode的右子树
            minNode.right=x.right;
            //让x的父结点成为minNode的父节点
            x=minNode;
        }
        return x;
    }
获取树中最小键思想

一直向结点左子树查找,直至结点没有左子树,则该结点就是最小键的结点。

 //获取树中最小键的key
    public Key min()
    {
        return min(root).key;
    }

    //获取子树x中最小键的节点
    public Node min(Node x)
    {
        //如果子树x的左子树不为null,则继续查找左子树,否则,查找完成
        if(x.left!=null)
            return min(x.left);
        else
            return x;
    }
获取树中最大键思想

一直向结点右子树查找,直至结点没有右子树,则该结点就是最大键的结点。

 //获取树中最大键的key
    public Key max()
    {
        return max(root).key;
    }

    //获取子树x中最大键的结点
    public Node max(Node x)
    {
        //判断结点x是否有右结点,若有则继续查找,否则,查找完成
        if (x.right!=null)
            return max(x.right);
        else
            return x;
    }
计算整棵树的最大深度思想

树的最大深度(树的根节点到最远叶子结点的最长路径上的结点数)

  • 实现步骤
    1.如果根结点为空,则最大深度为0;
    2.计算左子树的最大深度;
    3.计算右子树的最大深度;
    4.当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1
// 计算整棵树的最大深度
    public int maxDepth()
    {
        return maxDepth(root);
    }

    //计算子树x的最大深度
    private int maxDepth(Node x)
    {
        //如果结点x为null,则深度为0
        if(x==null)
            return 0;
        int max=0;//子树x的最大深度
        int maxL=0;//x左子树的最大深度
        int maxR=0;//x右子树的最大深度
        //如果左子树存在,则计算左子树的最大深度
        if (x.left!=null)
            maxL = maxDepth(x.left);
        //如果右子树存在,则计算右子树的最大深度
        if (x.right!=null)
            maxR=maxDepth(x.right);
        //比较左子树和右子树的最大深度,取较大的深度+1,即可
        max = maxL>maxR?maxL+1:maxR+1;
        return max;
    }

五、二叉查找树的遍历

很多情况下,我们可能需要像遍历数组数组一样,遍历树,从而拿出树中存储的每一个元素,由于树状结构和线性结构不一样,它没有办法从头开始依次向后遍历,所以存在如何遍历,也就是按照什么样的搜索路径进行遍历的问题。
我们把树简单的画作上图中的样子,由一个根节点、一个左子树、一个右子树组成,那么按照根节点什么时候被访问,我们可以把二叉树的遍历分为以下三种方式:
1.前序遍历;
先访问根结点,然后再访问左子树,最后访问右子树
2.中序遍历;
先访问左子树,中间访问根节点,最后访问右子树
3.后序遍历;
先访问左子树,再访问右子树,最后访问根节点
在这里插入图片描述
如果我们分别对下面的树使用三种遍历方式进行遍历,得到的结果如下:
在这里插入图片描述

前序遍历思想

1.把当前结点的key放入到队列中;
2.找到当前结点的左子树,如果不为空,递归遍历左子树
3.找到当前结点的右子树,如果不为空,递归遍历右子树

 //使用前序遍历,获取整个树中的所有键
    public Queue<Key> preOrder()
    {
        Queue<Key> queue = new Queue<>();
        preOrder(root,queue);
        return queue;
    }
    //前序遍历子树x中的所有key,并将key存入队列中
    private void preOrder(Node x, Queue<Key> queue)
    {
        //如果子树x为null,则结束
        if (x==null)
            return;
        //x入队
        queue.enqueue(x.key);
        //如果存在左子树,则递归遍历左子树
        if (x.left!=null)
            preOrder(x.left,queue);
        //如果存在右子树,则递归遍历右子树
        if (x.right!=null)
            preOrder(x.right,queue);
    }
中序遍历思想

1.找到当前结点的左子树,如果不为空,递归遍历左子树
2.把当前结点的key放入到队列中;
3.找到当前结点的右子树,如果不为空,递归遍历右子树

//使用中序遍历,获取整个树中的所有键
    public Queue<Key> midOrder()
    {
        Queue<Key> queue = new Queue<>();
        midOrder(root,queue);
        return queue;
    }
    //使用中序遍历,获取子树x中的所有键,并存入队列
    public void midOrder(Node x, Queue<Key> queue)
    {
        //如果x为null,则结束
        if (x==null)
            return;
        //如果存在左子树,则递归遍历x左子树
        if (x.left!=null)
            midOrder(x.left,queue);
        //x的key入队
        queue.enqueue(x.key);
        //如果存在右子树,则递归遍历x的右子树
        if (x.right!=null)
            midOrder(x.right,queue);
    }
后序遍历思想

1.找到当前结点的左子树,如果不为空,递归遍历左子树
2.找到当前结点的右子树,如果不为空,递归遍历右子树
3.把当前结点的key放入到队列中;

 //使用后序遍历,获取整个树中的所有键
    public Queue<Key> afterOrder()
    {
        Queue<Key> queue = new Queue<>();
        afterOrder(root,queue);
        return queue;
    }
    //使用后序遍历,获取子树x中的所有键,并存入队列
    public void afterOrder(Node x, Queue<Key> queue)
    {
        //如果x为null,则结束
        if (x==null)
            return;
        //如果x存在左子树,则递归遍历左子树
        if (x.left!=null)
            afterOrder(x.left,queue);
        //如果x存在右子树,则递归遍历右子树
        if (x.right!=null)
            afterOrder(x.right,queue);
        //x的key入队
        queue.enqueue(x.key);
    }
层序遍历思想

所谓的层序遍历,就是从根节点(第一层)开始,依次向下,获取每一层所有结点的值,有二叉树如下:
在这里插入图片描述
那么层序遍历的结果是:EBGADFHC

  • 实现步骤
    1.创建队列,存储每一层的结点;
    2.使用循环从队列中弹出一个结点:
    2.1获取当前结点的key;
    2.2如果当前结点的左子结点不为空,则把左子结点放入到队列中
    2.3如果当前结点的右子结点不为空,则把右子结点放入到队列中在这里插入图片描述
    在这里插入图片描述
//使用层序遍历,获取整个树中的所有键
    public Queue<Key> layerOrder()
    {
        //定义两个队列,一个存储key,一个存储结点
        Queue<Key> keys = new Queue<>();
        Queue<Node> nodes = new Queue<>();
        //将根结点入队nodes
        nodes.enqueue(root);
        //循环层序遍历,循环终止条件是结点队列中的元素为空
        while (!nodes.isempty())
        {
            //队列nodes进行弹出结点,并将弹出结点的键存入keys队列
            Node node = nodes.dequeue();
            keys.enqueue(node.key);
            //弹出结点如果有左子结点,则放入nodes中
            if (node.left!=null)
                nodes.enqueue(node.left);
            //弹出结点如果有右子结点,则放入nodes中
            if (node.right!=null)
                nodes.enqueue(node.right);
        }
        return keys;
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mekeater

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值