Java 八大数据结构之二叉树和红黑树

一:介绍
数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
image.png
二:二叉树
节点:一般代表一些实体,在Java面向对象编程中,节点一般代表对象
边:一般从一个节点到另一个节点的唯一方法就是沿着一条顺着有边的道路前进。在Java当中通常表示引用。
image.png
1.路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
2.根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
3.父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是D的父节点。
4.子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点。
5.兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点。
6.叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的H、E、F、G都是叶子节点
7.子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
8.节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
9.深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
10.高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;

二叉树的特点:
二叉树:树的每个节点最多只能有两个子节点
二叉搜索树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
image.png
中序遍历:左子树——》根节点——》右子树(上图:4,5,8,9,10,11,15,20)
前序遍历:根节点——》左子树——》右子树(上图:10,8,4,5,9,15,11,20)
后序遍历:左子树——》右子树——》根节点(上图:5,4,9,8,11,20,15,10)

实现二叉树的增加,查找,排序等


public class MyTree {
    //表示根节点
    private Node root;

    //内部类实现节点数据
    public class Node {
         int data;//节点数据
        private Node leftChild;//左子树的引用
        private Node rightChild;//右子节点的引用

        public Node(int data) {
            this.data = data;
        }

        //打印节点内容
        public void display() {
            System.out.println(data);
        }
    }


    //查找节点
    //查找某个节点,我们必须从根节点开始变量,1查找值比当前节点大,则搜索右子树,查找值等于当前节点值,停止搜索,查找值小于当前节点值,则搜索左子树;
    public Node find(int key) {
        Node current = root;
        while (current != null) {
            if (current.data > key) {//当前值比查找值大,搜索左子树
                current = current.leftChild;
            } else if (current.data < key) {//当前值比查找值小,搜索右子树
                current = current.rightChild;
            } else {
                return current;
            }
        }
        return null;//遍历完整个树没找到,返回null
    }

    //插入节点
    public boolean insert(int data) {
        Node newNode = new Node(data);
        if (root == null) {//当前树为空树,没有任何节点
            root = newNode;
            return true;
        } else {
            Node current = root;
            Node parentNode = null;
            while (current != null) {
                parentNode = current;
                if (current.data > data) {//当前值比插入值大,搜索左子节点
                    current = current.leftChild;
                    if (current == null) {//左子节点为空,直接将新值插入到该节点
                        parentNode.leftChild = newNode;
                        return true;
                    }
                } else {
                    current = current.rightChild;
                    if (current == null) {//右子节点为空,直接将新值插入到该节点
                        parentNode.rightChild = newNode;
                        return true;
                    }
                }
            }
        }
        return false;
    }
    //中序遍历
    public void infixOrder(Node current){
        if(current != null){
            infixOrder(current.leftChild);
            System.out.print(current.data+" ");
            infixOrder(current.rightChild);
        }
    }

    //前序遍历
    public void preOrder(Node current){
        if(current != null){
            System.out.print(current.data+" ");
            preOrder(current.leftChild);
            preOrder(current.rightChild);
        }
    }

    //后序遍历
    public void postOrder(Node current){
        if(current != null){
            postOrder(current.leftChild);
            postOrder(current.rightChild);
            System.out.print(current.data+" ");
        }
    }

    //找到最大值
    public Node findMax(){
        Node current = root;
        Node maxNode = current;
        while(current != null){
            maxNode = current;
            current = current.rightChild;
        }
        return maxNode;
    }
    //找到最小值
    public Node findMin(){
        Node current = root;
        Node minNode = current;
        while(current != null){
            minNode = current;
            current = current.leftChild;
        }
        return minNode;
    }
}

调用

     MyTree myTree=new MyTree();
                //二叉搜索树,插入数据左子树小于节点数据,右子树大于节点数据
                myTree.insert(10);
                myTree.insert(8);
                myTree.insert(15);
                myTree.insert(4);
                myTree.insert(9);
                myTree.insert(11);
                myTree.insert(20);
                myTree.insert(5);
                //中序遍历
               myTree.infixOrder(myTree.find(10));
                //前序遍历
                myTree.preOrder(myTree.find(10));
                //后序遍历
                myTree.postOrder(myTree.find(10));
                //获取最大值
                System.out.println(myTree.findMax().data);
                //获取最小值
                System.out.println(myTree.findMin().data);
                //获取数值为9的节点
                System.out.println(myTree.find(9));
                
                
                //结果
                //中序遍历
                System.out: 4 5 8 9 10 11 15 20
                //前序遍历
                System.out: 10 8 4 5 9 15 11 20 
                //后序遍历
                System.out: 5 4 9 8 11 20 15 10 
                //最大值
                System.out: 20
                //最小值
                System.out: 4
                //获取数值为9的节点,引用
                com.ruan.mygitignore.MyTree$Node@c115983

三:红黑树
红黑树,Red-Black Tree 「RBT」是一个自平衡(不是绝对的平衡)的二叉查找树(BST),树上的每个节点都遵循下面的规则:
1.每个节点都有红色或黑色
2.树的根始终是黑色的 (黑土地孕育黑树根, )
3.所有叶子都是黑色。(叶子是NIL结点)
4.每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
5.从节点(包括根)到其任何后代NULL节点(叶子结点下方挂的两个空节点,并且认为他们是黑色的)的每条路径都具有相同数量的黑色节点
image.png
当我们向原有的红黑树中添加数据,可能会破坏红黑树的规则
(1)向原红黑树掺入值为14的新节点:
image.png
(2)向原红黑树插入值为21的新节点:
image.png
由于父节点22是红色节点,因此这种情况打破了红黑树的规则4(每个红色节点的两个子节点都是黑色),必须进行调整,使之重新符合红黑树的规则。
调整的方式有:变色左旋转右旋转
变色:
为了重新符合红黑树的规则,尝试把红色节点变为黑色,或者把黑色节点变为红色。
下图所表示的是红黑树的一部分,需要注意节点25并非根节点。因为节点21和节点22连续出现了红色,不符合规则4,所以把节点22从红色变成黑色:
image.png
但这样并不算完,因为凭空多出的黑色节点打破了规则5,所以发生连锁反应,需要继续把节点25从黑色变成红色:
image.png
此时仍然没有结束,因为节点25和节点27又形成了两个连续的红色节点,需要继续把节点27从红色变成黑色:
image.png
左旋转:
逆时针旋转红黑树的两个节点,使得父节点被自己的右孩子取代,而自己成为自己的左孩子。说起来很怪异,大家看下图:
image.png
图中,身为右孩子的Y取代了X的位置,而X变成了自己的左孩子。此为左旋转。
右旋转:
顺时针旋转红黑树的两个节点,使得父节点被自己的左孩子取代,而自己成为自己的右孩子。大家看下图:
image.png
图中,身为左孩子的Y取代了X的位置,而X变成了自己的右孩子。此为右旋转。
我们以刚才插入节点21的情况为例:
首先,我们需要做的是变色,把节点25及其下方的节点变色:
image.png
此时节点17和节点25是连续的两个红色节点,那么把节点17变成黑色节点?恐怕不合适。这样一来不但打破了规则4,而且根据规则2(根节点是黑色),也不可能把节点13变成红色节点。
变色已无法解决问题,我们把节点13看做X,把节点17看做Y,像刚才的示意图那样进行左旋转:
image.png
image.png
由于根节点必须是黑色节点,所以需要变色,变色结果如下:
image.png
这样就结束了吗?并没有。因为其中两条路径(17 -> 8 -> 6 -> NIL)的黑色节点个数是4,其他路径的黑色节点个数是3,不符合规则5。
这时候我们需要把节点13看做X,节点8看做Y,像刚才的示意图那样进行右旋转:
image.png
image.png
最后根据规则来进行变色:
image.png
如此一来,我们的红黑树变得重新符合规则。这一个例子的调整过程比较复杂,经历了如下步骤:
变色 -> 左旋转 -> 变色 -> 右旋转 -> 变色
我觉得可以这样也能实现:
image.png
参考博客:https://blog.csdn.net/qq_3661...
四:哈希表
Hash表也称散列表,也有直接译作哈希表,Hash表是一种根据关键字值(key - value)而直接进行访问的数据结构。它基于数组,通过把关键字映射到数组的某个下标来加快查找速度,但是又和数组、链表、树等数据结构不同,在这些数据结构中查找某个关键字,通常要遍历整个数据结构,也就是O(N)的时间级,但是对于哈希表来说,只是O(1)的时间级。
注意,这里有个重要的问题就是如何把关键字转换为数组的下标,这个转换的函数称为哈希函数(也称散列函数),转换的过程称为哈希化。
五:堆Heap
1.它是完全二叉树,除了树的最后一层节点不需要是满的,其它的每一层从左到右都是满的。注意下面两种情况,第二种最后一层从左到右中间有断隔,那么也是不完全二叉树。
image.png
2.它通常用数组来实现
image.png
这种用数组实现的二叉树,假设节点的索引值为index,那么:

  • 节点的左子节点是 2*index+1,
  • 节点的右子节点是 2*index+2,
  • 节点的父节点是 (index-1)/2。

3.堆中的每一个节点的关键字都大于(或等于)这个节点的子节点的关键字。

六:图
image.png
邻接:
如果两个顶点被同一条边连接,就称这两个顶点是邻接的,如上图 I 和 G 就是邻接的,而 I 和 F 就不是。有时候也将和某个指定顶点邻接的顶点叫做它的邻居,比如顶点 G 的邻居是 I、H、F。
路径:
路径是边的序列,比如从顶点B到顶点J的路径为 BAEJ,当然还有别的路径 BCDJ,BACDJ等等。
连通图和非连通图:
如果至少有一条路径可以连接起所有的顶点,那么这个图称作连通的;如果假如存在从某个顶点不能到达另外一个顶点,则称为非联通的。
image.png
有向图和无向图:
如果图中的边没有方向,可以从任意一边到达另一边,则称为无向图;比如双向高速公路,A城市到B城市可以开车从A驶向B,也可以开车从B城市驶向A城市。但是如果只能从A城市驶向B城市的图,那么则称为有向图。
有权图和无权图:
图中的边被赋予一个权值,权值是一个数字,它能代表两个顶点间的物理距离,或者从一个顶点到另一个顶点的时间,这种图被称为有权图;反之边没有赋值的则称为无权图。
END:给时光以生命,给岁月以文明


Rocky_ruan
57 声望5 粉丝

不积跬步,无以至千里