[数据机构与算法之树形结构(二分搜索树)]

本文详细介绍了树的概念,包括树的定义、结点分类、结点间的关系和树的层次。接着深入探讨了二叉树的定义、形态、特殊情况、性质及存储结构。重点讲解了二分搜索树,包括其定义、代码实现、查找、添加、删除元素的操作。最后,讨论了基于二分搜索树实现TreeSet和基于LinkedList实现LinkedSet的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.关于树的介绍

1.树的定义

所谓树(tree)就是指n(n>=0)个结点的有限集。当n为0时,我们称他为空树;在任意一颗非空树中:(1)有且仅有一个特定的成为gen(root)的结点;
(2)若是n>1,其余结点可以分成m(m>0)个互不相交的有限集,其中每个集合本身又是一颗树,成为树的字根。下图就是树;
在这里插入图片描述

2.树结点的分类

结点拥有的子树个数成为结点的度Degree。度为0的结点称为叶结点,也叫终端结点;度不为0的结点我们成为非终端结点,也叫分支结点。除根结点外,分支结点也称为内部结点。树的度时树内个结点的最大值。

3.结点间的关系(族谱)

结点的子树的根称为该结点的孩子(child),该结点称为孩子的双亲(parent)。同一个双亲的孩子之间互相称为兄弟(sibling)。结点的祖先是从根到该结点所经分支上的所有结点。以某个结点为根的子树中任一结点都是该结点的子孙。我们以下面的树为例进行分析:
在这里插入图片描述
A是B,C的双亲;B,C是兄弟关系;D是B的孩子;D和E间就是堂兄弟;而A,B,D是G的祖先;其他都是A的子孙。

4.树的层次

结点的层次是从根结点开始定义的,根是第一层,孩子为第二层,以此类推;若是某结点在第i层,那么它的子树就在第i+1层;树中结点的最大层次称为树的深度或者是高度。
树的存储方式是:双亲表示法,孩子表示法,孩子兄弟表示法。

二.二叉树概念

1.二叉树的定义

二叉树(Binary Tree) 是n(n>=0)个结点的有限集合,该集合或者是空集(空二叉树),或者由一个根结点和两颗互不相交的、分别称为根节点的左子树和右子树的二叉树组成。
特点
(1)每个结点最多有两棵子树;
(2)左子树和右子树是有顺序的;
(3)即使树中某个结点只有一颗子树,也要区分左右。

2.二叉树的形态

1.空二叉树;
2.只有一个根结点;
3.根结点只有左子树
4.根结点只有右子树;
5.根结点既有左子树也有右子树;

3.二叉树的特殊情况

3.1斜树

斜树分为左斜树和右斜树。所有结点都只有左子树的二叉树我们称为左子树;所有结点都只有右子树的二叉树我们称为右斜树。所以在极端情况下二叉树会退化成线性表(线性表是树的特殊表现形式),这种情况也叫做非平衡树。
在这里插入图片描述

3.2满二叉树

在一颗二叉树中,如果所有分支结点都存在左右子树,并且所有叶子结点都在同一层上,这种二叉树称为满二叉树,它是一种平衡二叉树。
在这里插入图片描述

3.3完全二叉树

对一颗具有n个结点的二叉树按层i序编号,如果编号i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这颗二叉树称为完全二叉树,它呢也是一种平衡二叉树,它只是倒数第二层的结点的子树是按从左到右连续拥有,倒数第二层后面的结点可以没有子树;但前面有子树必须是连续出现的;如下图:
在这里插入图片描述

4.二叉树的性质

(1).在二叉树的第i层上最多有2的i-1次方个结点(i>=1);
(2).深度为k的二叉树至多有2的k次方-1个结点(k>=1);
(3).对于任何一颗二叉树,如果终端结点数为n0,度为2的结点数为n2,则n0 = n2 + 1;
(4).具有n个结点的完全二叉树的深度为log以2为底n +1;
(5).如果对于一颗有n个结点的完全二叉树的结点按层序编号:
如果i=1,结点无双亲;如果i>1则双亲是i/2;
如果2i>n,则结点i无左孩子,否则其左孩子为2i;
如果2i+1>n,则结点i无右孩子,否则右孩子为2i+1;

5.二叉树的存储结构

5.1顺序存储结构

二叉树的顺序存储结构就是用以为数组存储树的结点,并且结点的存储位置也就是数组的下标要能体现出之间的逻辑关系。但顺序存储结构在极端情况下浪费空间,只适合平衡树的存储。如图:
在这里插入图片描述

5.2链式存储结构

二叉树每个结点最多有两个孩子,所以设计为一个数据域和两个指针域的结点进行链接,也叫二叉链表。
在这里插入图片描述

6.二叉树的遍历

二叉树的遍历分为两类:深度优先遍历DFS和广度优先遍历BFS
深度优先遍历:前序遍历、中序遍历、后序遍历;
广度优先遍历:层序遍历;
在这里插入图片描述

6.1前序遍历DLR

规则是二叉树为空,则空操作返回,否则先访问根结点,再前序遍历左子树,然后前序遍历右子树。
遍历上图结果为:ABDGHJCEIF。

6.2中序遍历LDR

规则是二叉树为空,则空操作返回,否则从根结点开始,中序遍历根结点的左子树,然后访问根结点,最后中序遍历右子树。
遍历上图结果为:GDJHBAEICF。

6.3后序遍历LRD

规则是二叉树为空,则空操作返回,否则从根结点开始,后序遍历根结点的左子树,然后后序遍历右子树,最后访问根结点。
遍历上图结果为:GJHDBIEFCA。

6.4层序遍历

规则是树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历;在同一层中,按从左到右的顺序对结点逐个访问。
遍历上图结果为:ABCDEFGHIJ。

三.二分搜索树(Binary Search Tree)

1.二分搜索树定义

它本身是二叉树,只不过呢对于二分搜索树的每个结点而言,大于其左结点的所有结点的值,小于其右子树的所有结点的值。同样,其子树也是一颗二分搜索树,那么该树中元素要具有可比性,不包含重复元素。如图:
在这里插入图片描述

2.代码实现

2.1结点类创建和二分搜索树的属性定义及其初始化

结点类拥有数据域E,两个指针域left,right表示左右孩子;创建一个结点初始是没有左右孩子的。对于二分搜索树则右属性root表示根结点,以及size表示元素个数。

package tree;
import java.util.Iterator;
import lianbiao.LinkedList;

public class BinarSearchTree<E extends Comparable<E>> implements Iterable<E> {
   
    class Node{
   
        public E e; //数据域
        public Node left; //左孩子
        public Node right; //右孩子
        public Node(E e){
   
            this.e = e;
            left = null;
            right = null;
        }
        @Override
        public String toString() {
   
            return e.toString();
        }
    }
    private Node root;//根节点的指针;
    private int size;//元素个数,(节点个数);
    public BinarSearchTree(){
   
        root = null;
        size = 0;
    }

2.2元素个数和判空操作的实现

    public int size(){
   
        return size;
    }
    public boolean isEmpty(){
   
        return root == null && size == 0;
    }

2.3添加元素(递归和迭代实现)

迭代实现:根据值创建结点,如果树是空树,则让root根指向创建的结点,元素个数加一;如果不空,则需要从根结点开始遍历,如果遍历的值比添加的元素小,则从右分支遍历,如果右孩子为空,则添加元素,否则就继续向下遍历;遍历的值比添加的元素大,则从左分支遍历,如果左孩子为空添加元素,否则继续向下遍历;否则就结束。
递归实现:
在以node为根的树中,插入元素e,并返回新树的根;
插入的值比根小则向左遍历,插入值比根大向右遍历,如果还有左右孩子,则继续向下遍历即可,直到遍历到空时,则将添加的元素作为上一个的孩子(左或右)向上返回;

    public void add(E e){
   
        //迭代
//        Node node = new Node(e);
//        if(isEmpty()){
   
//            root = node;
//            size++;
//        }
//        Node cur = root;
//        while(true){
   
//            //当前元素小,往右走
//            if(cur.e.compareTo(node.e)<0){
   
//                if(cur.right == null){
   
//                    cur.right = node;
//                    size++;
//                    break;
//                }else{
   
//                    cur = cur.right;
//                }
//                //当前元素大,往左走;
//            }else if(cur.e.compareTo(node.e)>0){
   
//                if(cur.left == null){
   
//                    cur.left = node;
//                    size++;
//                    break;
//                }else{
   
//                    cur = cur.left;
//                }
//            }else{
   
//                break;
//            }
//        }
        root = add(root,e);
    }
    //在以node为根的树中,插入元素e,并返回新树的根;
    private Node add(Node node,E e){
   
        if(node == null){
   
            size++;
            return new Node(e);
        }
        if(e.compareTo(node.e)<0){
   
            node.left = add(node.left,e);
        }else if(e.compareTo(node.e)>0){
   
            node.right = add(node.right,e);
        }
        return node;
    }

2.4判断是否包含元素(递归和迭代)

和添加元素相似,它是从根遍历,如果遍历的值比查找的元素大,则从左分支遍历,只要左分支不是空,就继续向下遍历,为空则不包含;如果遍历的值比查找的值小,则从右分支遍历,只要右分支不为空,继续向下遍历,为空,则不包含;如果遍历值和查找值相等则包含。
递归和迭代逻辑相似。

    public boolean contains(E e){
   
        Node cur = root;
        while (true) {
   
            if (e.compareTo(cur.e) < 0) {
   
                if (cur.left == null) {
   
                    return false;
                }
                cur = cur.left;
            } else if (e.compareTo(cur.e) > 0) {
   
                if (cur.right == null) {
   
                    return false;
                }
                cur = cur.right;
            } else {
   
                return true;
            }
        }
        //return contains(root,e);
    }

    private boolean contains(Node node, E e) {
   
        if(node == null){
   
            return false;
        }
        if(e.compareTo(node.e) < 0){
   
            return contains(node.left,e);
        }else if
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

心尘未泯

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

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

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

打赏作者

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

抵扣说明:

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

余额充值