JAVA 系列——>数据结构

点进来的朋友先点个赞再点个关注好不好,求支持!一直往后更新。

数据结构有什么用?

   当你用着java里面的容器类很爽的时候,你有没有想过,怎么ArrayList就像一个无限扩充的数组,也好像链表之类的。好用吗?好用,这就是数据结构的用处,只不过你在不知不觉中使用了。 
   现实世界的存储,我们使用的工具和建模。每种数据结构有自己的优点和缺点,想想如果Google的数据用的是数组的存储,我们还能方便地查询到所需要的数据吗?而算法,在这么多的数据中如何做到最快的插入,查找,删 除,也是在追求更快。 
   我们java是面向对象的语言,就好似自动档轿车,C语言好似手动档吉普。数据结构呢?是变速箱的工作原理。你完全可以不知道变速箱怎样工作,就把自动档的车子从 A点 开到 B点,而且未必就比懂得的人慢。写程序这件事, 和开车一样,经验可以起到很大作用,但如果你不知道底层是怎么工作的,就永远只能开车,既不会修车,也不能 造车。
   当然了,数据结构内容比较多,细细的学起来也是相对费功夫的,不可能达到一蹴而就。我们将常见的数据 结构:堆栈、队列、数组、链表和红黑树 这几种给大家介绍一下,作为数据结构的入门,了解一下它们的特点即可。

常见的数据结构

数据存储的常用结构有:栈、队列、数组、链表和红黑树。我们分别来了解一下:

stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
更准确的说,一般线性表的插入、删除运算不受限制,而栈和队列上的插入删除运算均受某种特殊限制。
在这里插入图片描述

简单的说:采用该结构的集合,对元素的存取有如下的特点:

先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。
例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的 子弹。 栈的入口、出口的都是栈的顶端位置。
在这里插入图片描述
这里两个名词需要注意:
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

栈的顺序存储结构实现

引用:https://blog.csdn.net/xmxkf/article/details/82465726

通常栈可以用顺序的方式存储,分配一块连续的存储区域存放栈中的元素,并用一个变量指向当前的栈顶。采用顺序存储的栈称为顺序栈,Java util包下的Stack就是顺序栈。

顺序栈的操作示意图如下:

在这里插入图片描述

顺序栈的实现如下:

package test;

import java.util.Arrays;
import java.util.EmptyStackException;

public class Test<T>{

    private int top = -1; //栈顶指针,-1代表空栈
    private int capacity = 10;  //默认容量
    private int capacityIncrement = 5;  //容量增量
    private T[] datas;    //元素容器

    public Test(int capacity){
        datas = (T[])new Object[capacity];
    }
    public Test(){
        datas = (T[])new Object[capacity];
    }

    /**溢出扩容 */
    private void ensureCapacity() {
        int oldCapacity = datas.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                capacityIncrement : oldCapacity);
        /**
         * Arrays的copyOf()方法传回的数组是新的数组对象,改变传回数组中的元素值,不会影响原来的数组。
         * copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值,
         * */
        datas = Arrays.copyOf(datas, newCapacity);
    }

    /**进栈,将元素添加到栈顶*/
    public synchronized void push(T item) {
        //容量不足时扩容
        if(top>=datas.length-1)
            ensureCapacity();
        datas[++top] = item;
    }

    /**出栈,将栈顶的元素移除并返回该元素*/
    public synchronized T pop() {
        if(top<0)
            new EmptyStackException();
        T t = datas[top];
        datas[top] = null;
        top--;
        return t;
    }
    /**清空栈*/
    public synchronized void clear() {
        if(top<0)
            return;
        for(int i = top; i>=0; i--)
            datas[i] = null;
        top = -1;
    }
    /**返回栈顶元素*/
    public T peek() {
        if(top<0)
            new EmptyStackException();
        return datas[top];
    }
    /**获取栈中元素个数*/
    public int getLength() {
        return top+1;
    }
    /**栈是否为空栈*/
    public boolean empty() {
        return top<0;
    }

    /**获取元素到栈顶的距离 */
    public int search(T data) {
        int index = -1;
        if(empty())
            return index;
        if (data == null) {
            for (int i = top; i >= 0; i--)
                if (datas[i]==null){
                    index = i;
                    break;
                }
        } else {
            for (int i = top; i >= 0; i--)
                if (data.equals(datas[i])) {
                    index = i;
                    break;
                }
        }
        if (index >= 0) {
            return top - index;
        }
        return index;
    }

    @Override
    public String toString() {
        if(empty())
            return "[]";
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        for(int i = 0; i<=top; i++){
            buffer.append(datas[i]+", ");
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }
}

栈的链式存储结构实现

 采用链式存储结构的栈称为链栈,在链栈中规定所有操作都是在链表表头进行的,链栈的优点是不存在栈满上溢的情况。上述顺序栈的实现过程中增加了扩容的功能,所以也能实现不上溢。

在这里插入图片描述

链栈的实现如下:

public class StackByLink<T>{

    public LNode<T> top;   //栈顶指针,指向栈顶结点
    private int size = 0;

    /**进栈,将元素添加到栈顶*/
    public synchronized void push(T item) {
        LNode temp = new LNode();
        temp.next = top;   //添加的结点的指针域指向当前栈顶结点
        top = temp;        //更新栈顶结点
        size ++;
    }

    /**出栈,将栈顶的元素移除并返回该元素*/
    public synchronized T pop() {
        if(top==null)
            new EmptyStackException();
        LNode < T> next = top.next;
        T data = top.data;
        top.next = null;
        top.data=null;
        top = next;
        size--;
        return data;
    }
    /**清空栈*/
    public synchronized void clear() {
        LNode<T> next;
        while(top!=null){
            next = top.next;
            top.data = null;
            top.next = null;
            top = next;
        }
        size = 0;
    }
    /**返回栈顶元素*/
    public T peek() {
        if(top==null)
            new EmptyStackException();
        return top.data;
    }
    /**获取栈中元素个数*/
    public int getLength() {
        return size;
    }
    /**栈是否为空栈*/
    public boolean empty() {
        return top==null;
    }

    /**获取元素到栈顶的距离 */
    public int search(T data) {
        int dis = -1;
        if(empty())
            return dis;
        LNode<T> node = top;
        if (data == null) {
            while(node!=null){
                dis ++;
                if (node.data == null)
                    return dis;
            }
        } else {
            while(node!=null){
                dis ++;
                if (node.data.equals(data))
                    return dis;
            }
        }
        return dis;
    }

    @Override
    public String toString() {
        if(empty())
            return "[]";
        LNode node = top;
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        while(node != null){
            buffer.append(node.data+", ");
            node = node.next;
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }
}


两种栈的效率分析

顺序栈和链式栈中,主要的操作算法push(T item)、pop()、 peek()的时间复杂度和空间复杂度都是O(1),从效率上都是一样的,因为栈只对一端进行操作。顺序栈和链式栈实现起来最大的区别就是链式栈不用考虑容量问题,而顺序栈会有上溢的情况,如果加上自动扩容,则push(T item)的时间复杂度将变为O(n)。所以链式栈相比会更加灵活一点。

队列

queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入, 而在表的另一端进行删除
队列的顺序存储结构需要使用一个数组和两个整数型变量来实现, 利用数组顺序存储队列中的所有元素,利用两个整数变量分别存储队首元素和队尾元素的下标位置,分别称为队首指针和队尾指针。
在这里插入图片描述
 假设队列元素个数最大不超过MaxSize,当队列为初始状态有front-rear,该条件可作为队列空的条件。那么能不能用rearMaxSize-1作为队满的条件呢?显然不能,如果继续往上图的队中添加元素时出现“上溢出”,这种溢出并不是真正的溢出,因为数组中还存在空位置,所以这是一种假溢出。
为了能充分的使用数组中存储空间,把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列。如下如所示:
在这里插入图片描述
上图对应步骤解析如下:
初始化队列时,队首front和队尾rear都指向0;
a入队时,队尾rear=rear+1指向1;
bc入队后,rear=3,此时队已满。其实此时队中还有1个空位,如果这个空位继续放入一个元素,则rear=front=0,这和rear=front时队为空冲突,所以为了算法设计方便,此处空出一个位。所以判断队满的条件是(rear+1)%MaxSize == front
ab出队,队首front=2;
de入队,此时rear=1;满足(rear+1)%MaxSize == front,所以队满
cde出队,此时rear=front=1,队空

通过上述分析,我们可以得出,出队和入队操作会使得队首front队尾rear指向新的索引,由于数组为环状,可通过求余%运算来实现:

入队(队尾指针进1):rear = (rear+1)%MaxSize
出队(队首指针进1):front = (front+1)%MaxSize
当满足 rear==front时,队列为空,我们可以在出队操作后,判断此条件,如果满足则说明队列为空了,可以将rear和front重新指向0;
当需要入队操作时,首先通过(rear+1)%MaxSize == front判断是否队满,如果队满,则需要空充容量,否则会溢出。
public class QueueByArray<T>{

    private int front = 0; //队首指针(出队)
    private int rear = 0;  //队尾指针(入队)
    private int size;      //元素个数

    private int capacity = 10;  //默认容量
    private int capacityIncrement = 5;  //容量增量
    private T[] datas;    //元素容器

    public QueueByArray(int capacity){
        datas = (T[])new Object[capacity];
    }
    public QueueByArray(){
        datas = (T[])new Object[capacity];
    }

    public int getFront() {
        return front;
    }

    public void setFront(int front) {
        this.front = front;
    }

    public int getRear() {
        return rear;
    }

    public void setRear(int rear) {
        this.rear = rear;
    }

    /**获取队中元素个数*/
    public int getSize() {
        return rear - front;
//        return size;
    }

    /**是否为空队*/
    public boolean isEmpty() {
        return rear==front;
    }

    /**入队*/
    public synchronized boolean enQueue(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");
        //判断是否满队
        if((rear+1) % datas.length == front){
            //满队时扩容
            ensureCapacity();
        }
        //添加data
        datas[rear] = item;
        //更新rear指向下一个空元素的位置
        rear = (rear+1) % datas.length;
        size++;
        return true;
    }

    /**双端队列  从队首入队*/
    public synchronized boolean enQueueFront(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");
        //判断是否满队
        if((rear+1) % datas.length == front){
            //满队时扩容
            ensureCapacity();
        }
        //使队首指针指向上一个空位
        front = (front-1+datas.length)%datas.length;
        //添加data
        datas[front] = item;
        size++;
        return true;
    }

    /**溢出扩容 */
    private void ensureCapacity() {
        int oldCapacity = datas.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                capacityIncrement : oldCapacity);
        T[] oldDatas = datas;
        datas = (T[])new Object[newCapacity];
        int j = 0;
        //将原数组中元素拷贝到新数组
        for (int i = front; i!=this.rear ; i = (i+1) % datas.length) {
            datas[j++] = oldDatas[i];
        }
        //front指向新数组0的位置
        front = 0;
        //rear指向新数组最后一个元素位置
        rear = j;
    }

    /**出队*/
    public synchronized T deQueue() {
        if(isEmpty())
            return null;
        T t = datas[front];
        datas[front] = null;
        //front指向新的队首元素
        front = (front+1) % datas.length;
        size --;
        return t;
    }

    /**双端队列  从队尾出队*/
    public synchronized T deQueueRear() {
        if(isEmpty())
            return null;
        //队尾指针指向上一个元素位置
        rear = (rear-1+datas.length)%datas.length;
        T t = datas[rear];
        datas[rear] = null;
        size --;
        return t;
    }

    /**清空队列*/
    public synchronized void clear() {
        while(!isEmpty()){
            deQueue();
        }
        front = rear = 0;
    }

    /**返回队首元素*/
    public T peek() {
        if(isEmpty())
            return null;
        return datas[front];
    }
    public T getElement(int index) {
        if(isEmpty() || index>=datas.length)
            return null;
        T t = datas[index];
        return t;
    }
    @Override
    public String toString() {
        if(isEmpty())
            return "[]";
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        int mFront = front;
        while(mFront!=rear){
            buffer.append(datas[mFront]+", ");
            mFront = (mFront+1) % datas.length;
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }
}

队列的链式存储结构实现

链式存储结构有单链表和双链表,对于链队的实现,使用单链表就足以满足需求(双链表由于多了前驱指针,存储密度不如单链表,造成空间浪费)。下面我们使用单链表实现链队,操作示意图如下:
在这里插入图片描述
从上图中,我们可以知道如下几点:

使用front指向队首结点,rear指向队尾结点;
当队为空时,front = rear = null,并约定以此作为队列为空的判断条件;
入队时,使当前队尾结点的后继指针指向新结点rear.next = newNode,然后使队尾指针rear指向新结点
出队时,删除队首结点,使front = front.next
链队不会出现溢出的情况,这比使用数组实现的顺序队列更加灵活

链队的分析就到这里,接下来我们实现链队的基本操作算法:

public class QueueByLink<T>{

    private LNode<T> front; //队首指针
    private LNode<T> rear;  //队尾指针

    private int size;      //元素个数

    /**获取队中元素个数*/
    public int getSize() {
        return size;
    }

    /**队列是否为空*/
    public boolean isEmpty() {
        return front==null && rear==null;
    }

    /**入队*/
    public synchronized boolean enQueue(T item) {
        if(item==null)
            throw new NullPointerException("item data is null");
        LNode<T> newNode = new LNode();
        newNode.data = item;
        if (front == null) {
            //向空队中插入,需要将front指针指向第一个结点
            front = newNode;
        } else {
            //非空队列,队尾结点的后继指针指向新结点
            rear.next = newNode;
        }
        rear = newNode;   //队尾指针指向新结点
        size ++;
        return true;
    }

    /**出队*/
    public synchronized T deQueue() {
        if(isEmpty())
            return null;
        T t = front.data;
        front = front.next;
        if (front == null)   //如果出队后队列为空,重置rear
            rear = null;
        size--;
        return t;
    }

    /**返回队首元素*/
    public T peek() {
        return isEmpty()? null:front.data;
    }

    /**清空队列*/
    public synchronized void clear() {
        while(!isEmpty()){
            deQueue();
        }
        front = rear = null;
        size = 0;
    }

    @Override
    public String toString() {
        if(isEmpty())
            return "[]";
        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        LNode mFront = front;
        while(mFront!=null){
            buffer.append(mFront.data+", ");
            mFront = mFront.next;
        }
        return buffer.subSequence(0, buffer.lastIndexOf(", "))+"]";
    }

}


简单的说,采用该结构的集合,对元素的存取有如下的特点:

先进先出(即,存进去的元素,要在后,它前面的元素依次取出后,才能取出该元素)。例如,小火车过山 洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
在这里插入图片描述

数组

Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出 租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

简单的说,采用该结构的集合,对元素的存取有如下的特点: 查找元素快:通过索引,可以快速访问指定位置的元素
在这里插入图片描述
增删元素慢
指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
在这里插入图片描述
指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位 置,原数组中指定索引位置元素不复制到新数组中。如下图
在这里插入图片描述

链表

linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每 个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的 链表结构有单向链表双向链表,那么这里给大家介绍的是单向链表
在这里插入图片描述
简单的说,采用该结构的集合,对元素的存取有如下的特点:
多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次 类推,这样多个人就连在一起了。
在这里插入图片描述
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
增删元素快
增加元素:只需要修改连接下个元素的地址即可。
在这里插入图片描述
删除元素:只需要修改连接下个元素的地址即可。
在这里插入图片描述

链表的实现原理

1 创建一个节点类,其中节点类包含两个部分,第一个是数据域(你到时候要往节点里面储存的信息),第二个是引用域(相当于指针,单向链表有一个指针,指向下一个节点;双向链表有两个指针,分别指向下一个和上一个节点)
2 创建一个链表类,其中链表类包含三个属性:头结点、尾节点和大小,方法包含添加、删除、插入等等方法。

单向链表的节点类:

public class Node {
    public Object data;
    public Node next;
    
    public Node(Object e){
        this.data = e;
    }
}

双向链表的节点类:

public class Node {
    public Object e;
    public Node next;
    public Node pre;
    public Node(){

    }
    public Node(Object e){
        this.e = e;
        next = null;
        pre = null;
    }
}

如何自己写出一个链表?
代码如下(以双向链表为例,有详细的注释,单向链表同理):
首先创建了一个节点类

package MutuLink;

public class Node {
    public Object e;
    public Node next;
    public Node pre;
    public Node(){

    }
    public Node(Object e){
        this.e = e;
        next = null;
        pre = null;
    }
}

然后创建了一个链表类

package MutuLink;

public class MyList {
    private Node head;
    private Node tail;
    private int size = 0;

    public MyList() {
        head = new Node();
        tail = new Node();
        head.next =null;
        tail.pre = null;
    }

    public boolean empty() {
        if (head.next == null)
            return true;
        return false;
    }
    //找到所找下标节点的前一个节点
    public Node findpre(int index){
        Node rnode = head;
        int dex = -1;
        while(rnode.next != null){
            //找到了插入节点的上一个节点
            if( dex== index - 1){
                return rnode;
            }
            rnode = rnode.next;
            dex++;
        }
        return null;
    }
    public Node findthis(int index){
        Node rnode = head;
        //把rnode想象为指针,dex为指向的下标,这个地方很容易错,因为当指向最后一个节点时没有判断IF就跳出循环了
        int dex = -1;
        while(rnode.next != null){
            if(dex == index)
            return rnode;
            rnode = rnode.next;
            dex++;
        }
        if(dex == size - 1){
            return rnode;
        }
//        Node test = new Node(new Students("haha",1,2));
        return null;
    }

    // 往链表末尾加入节点
    public void add(Object e) {
        Node node = new Node(e);
        Node rnode = head;
        //如果是空链表的话插入一个节点,这个节点的pre不能指向上一个节点,必须指空
        if (this.empty()) {
            rnode.next = node;
            rnode.next.pre = null;
            tail.pre = node;
            size++;
        } else {
            while (rnode.next != null)
                rnode = rnode.next;
            rnode.next = node;
            node.pre = rnode;
            tail.pre = node;
            size++;
        }
    }
    //往链表的某一个标插入一个节点
    public boolean add(int index,Object e){
        if(index <0||index>=size)
            return false;
        Node node = new Node(e);
        Node prenode = this.findpre(index);
        node.next = prenode.next;
        prenode.next.pre = node;
        prenode.next = node;
        node.pre = prenode;
        size++;
        return true;
    }
    public boolean add(int index,MyList myl){
        if(index <0 || index >= size)
            return false;
        Node prenode = this.findpre(index);
//        myl.tail.pre.next = prenode.next;
//        prenode.pre = myl.tail.pre;
//        tail.pre = null;
//        prenode.next = myl.head.next;
//        myl.head.next.pre = prenode;
//        head.next = null;
        myl.tail.pre.next = prenode.next;
        prenode.next.pre = myl.tail.pre.pre;
        myl.head.next.pre = prenode.pre;
        prenode.next = myl.head.next;
        myl.head = null;
        myl.tail = null;
        size+=myl.size;
        return true;
    }
    
    public Object remove(int index){
        Object ob= this.get(index);
        if(index <0 || index >= size)
            return null;
        //特殊情况,当移除节点是最后一个节点的时候
        //较为复杂通过画图来写代码
        if(index == size - 1){
            Node prenode = this.findpre(index);
            this.tail.pre = this.tail.pre.pre;
            this.tail.pre.next.pre = null;
            this.tail.pre.next =null;
            size--;
            return ob;
        }
        //比较复杂,通过画图解决
        else{
            Node prenode = this.findpre(index);
            prenode.next = prenode.next.next;
            prenode.next.pre.next = null;
            prenode.next.pre = prenode.next.pre.pre;
            size--;
            return ob;
        }
    }
    
    
    public Object get(int index){
        Node thisnode = this.findthis(index);
        return thisnode.e;
    }
    public int size(){
        return size;
    }
}

最后测试

package MutuLink;

import java.util.Random;

public class manage {
    public static void main(String[] args) {
        String name = "";
        int credit;
        int age;
        int size;
        MyList myl = new MyList();
        Random random = new Random();
        size = random.nextInt(5) + 1;
        for (int i = 0; i < size; i++) {
            credit = random.nextInt(5);
            age = random.nextInt(5) + 18;
            for (int j = 0; j < 4; j++) {
                name += (char) (random.nextInt(26) + 97);
            }
            Students stu = new Students(name, credit, age);
            myl.add(stu);
            name = "";
        }

        System.out.println("Size of myl1 is "+ myl.size());
        for(int i = 0; i < myl.size() ;i++){
            Students stu2 = (Students) myl.get(i);
            stu2.show();
        }
//        //测试能否在链表末尾加入节点(成功)
//        for(int i = 0; i < myl.size() ;i++){
//            Students stu2 = (Students) myl.get(i);
//            stu2.show();
//        }
//        //测试能否通过下标加入一个节点(成功)
//        Students stu3 = new Students("cyt",5,18);
//        myl.add(1, stu3);
//        System.out.println("Size is "+ myl.size());
//        for(int i = 0; i < myl.size() ;i++){
//            Students stu2 = (Students) myl.get(i);
//            stu2.show();
//        }

        MyList myl2 = new MyList();
        size = random.nextInt(5) + 1;
        for (int i = 0; i < size; i++) {
            credit = random.nextInt(5);
            age = random.nextInt(5) + 18;
            for (int j = 0; j < 4; j++) {
                name += (char) (random.nextInt(26) + 97);
            }
            Students stu2 = new Students(name, credit, age);
            myl2.add(stu2);
            name = "";
        }
        System.out.println("Size is of myl2 "+ myl2.size());
        for(int i = 0; i < myl2.size() ;i++){
            Students stu2 = (Students) myl2.get(i);
            stu2.show();
        }


        
        myl.add(1, myl2);
        System.out.println("Size is of myl1 "+ myl.size());
        for(int i = 0; i < myl.size() ;i++){
            Students stu2 = (Students) myl.get(i);
            stu2.show();
        }
        
        
    }

}

输出结果:
在这里插入图片描述

红黑树

二叉树:binary tree ,是每个结点不超过2有序树(tree)。
简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。 如图:
在这里插入图片描述
我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然 是一颗二叉查找树。也就意味着,树的键值仍然是有序的。

红黑树的约束:

  1. 节点可以是红色的或者黑色的
  2. 根节点是黑色的
  3. 叶子节点(特指空节点)是黑色的
  4. 每个红色节点的子节点都是黑色的
  5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同

红黑树的特点: 速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍

发布了45 篇原创文章 · 获赞 29 · 访问量 5898

猜你喜欢

转载自blog.csdn.net/qq_16397653/article/details/103956855
今日推荐