双向链表,比西天还远? - 第282篇

相关历史文章(阅读本文之前,您可能需要先看下之前的系列?

色谈Java序列化:女孩子慎入 - 第280篇

烦不烦,别再问我时间复杂度了:这次不色,女孩子进来吧 - 第281篇

 

悟纤论:技术之路,比西天还远」

师傅:单链表、双链表,啥,你没有听过。

悟纤:... ...

师傅:技术之路,比西天还远,你以为你学完了,其实你还有很多不懂。坐下,听为师给你慢慢唠叨来。

一、链表

       链表可以分为:单链表、循环链表、双向链表

 

 

(1)单链表:每个节点只保留一个引用,该引用指向当前节点的下一个节点,没有引用指向头结点,尾节点的next引用为null。

(2)循环链表:一种首尾相连的链表。

(3)双向链表:每个节点有两个引用,一个指向当前节点的上一个节点,另外一个指向当前节点的下一个节点。

       先看下单链表:

 

单链表,左边格子表示数据,右边是指向下一结点的引用,以此连接整个链表。

       双向链表呐:

双向:由中间的数据和两段的前后引用组成。有头结点和为结点,置于头节点是否存放数据根据具体作用和要求来定。

BTW:链表很重要的一个概念就是有一个引用域(Java叫引用,c语言是指针),如果在当前对象中只有一个引用下个对象,那么就是单链;如果有两个引用,引用上一个对象,又引用下个对象,那么就是双向链表。

 

       师傅:是不是有点抽象,下面为师用一个例子来说明下双向链表。

       悟纤:是有点不好理解。

       师傅:在写例子之前,我们先理清下思路:

(1)双向链表:对于双向链表需要指向下个节点和指向上一个节点,那么就需要定义两个属性next,pre。

(2)链表头尾记录:需要使用两个引用记录头节点和尾节点。

(3)实现节点的添加、插入、删除、查询操作。

 

二、举个栗子

2.1 声明节点类

       使用泛型,支持多种数据类型,定义一个节点,类名为:DuLinkedList

 

package com.kfit.node;

public class DuLinkedList<E> {
    private E e;//数据.

    public DuLinkedList() {

    }

    public DuLinkedList(E e) {
        this.e = e;
    }

}

 

2.2 双向链表的两个引用

       定义双向链表的两个引用next和pre:

 

public DuLinkedList<E> next;//指向下个节点.
public DuLinkedList<E> pre;//指向上个节点。

 

2.3 构建链表的信息

需要储存头结点和尾结点,以此根据引用域找到某一位置的结点,还需要整型的size记录链表长度

 

private int size = 0;//保存该链表中已包含的节点数
public DuLinkedList<E> head;//指向头节点
public DuLinkedList<E> tail;//指向尾节点

 

2.4 添加节点的方法实现

       添加节点的方法主要是:

(1)当添加第一个节点的时候,next和pre都是不需要的进行设置的,head和tail都是当前节点。

(2)当添加第(n>1)个节点的时候,此时对于新节点的pre需要指向上一个节点,也就是最后添加的tail节点,对于上一个节点,此时的next就是当前节点,并且tail需要指向新的节点。

    /**
     * 添加个节点
     */
    public void add(E e) {
        //构造一个节点.
        DuLinkedList<E> node = new DuLinkedList<>(e);
        /*
         * 如果此节点是第一个节点的话
         * 如果此节点不是第一个节点的话
         */
        if(size == 0) {
            head = node;
            tail = node;
        }else {
            /*
             * (1)对于新创建的节点 node:pre 指向上一个节点,next: 下个节点,未创建,不需要的指向。
             * (2)上一次创建的节点,也就是tail的next需要指向这次的节点node,
             * (3)尾节点需要指向当前新创建的节点。
             */
            node.pre = tail;
            tail.next = node;
            tail = node;
        }
        size++;
    }

 

2.5 查询节点

       通过index查询节点,这个主要思路,就是从第一个节点开始往后寻找,第一个节点也就是head,然后for(index),通过node.next找到下个节点:

 

    /**
     * 查找第几个的元素的node
     * @param index
     * @return
     */
    private DuLinkedList<E> query(int index) {
        if(index>=size) {
            throw new IndexOutOfBoundsException("索引越界,最大值为="+(size-1));
        }
        DuLinkedList<E> node =  head;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

 

2.6 查询元素

       获取到节点之后,获取到元素就很简单了,只需要node.e就是当前索引的元素了:

 

    /**
     * 查找第几个的元素的node的value
     * @param index
     * @return
     */
    public E get(int index) {
        return query(index).e;
    }

 

2.7 插入元素

       在已有节点AB之间插入一个C的节点,那么就会得到ACB节点,具体需要的操作就是:

(1)A的next 需要指向C;

(2)B的pre需要指向C;

(3)C的pre需要指向A;

(4)C的next需要指向B;

BTW:这里是为了演示,并没有考虑其它一些情况,比如:没有的节点的时候调用,在最后一个节点插入的时候,这时候又不一样了。

       看一下上面这个思路的代码:

 

/**
     * 插入一个节点
     * 【此代码只是演示,代码待优化】
     * @param index
     * @param e
     */
    public void insert(int index ,E e) {
        if(size == 0) {
            add(e);
        }else {
            DuLinkedList<E> newNode = new DuLinkedList<>(e);
            /*
             * 假设在AB之间插入一个C的节点:
             * 
             * A的next就需要指向C
             * B的pre就需要指向C
             * C的pre指向A
             * C的next指向B
             * 
             */
            //通过索引查找指定index的节点,比如:A
            DuLinkedList<E> node = query(index);

            // node.next 即为B
            newNode.next = node.next;//C的next指向B
            //node 本身就是A
            newNode.pre = node;//C的pre指向A
            node.next.pre = newNode;//B的pre就需要指向C
            node.next = newNode;//A的next就需要指向C
        }


        size++;
    }

上面思路的最终代码如下↓↓↓:

package com.kfit.node;

public class DuLinkedList<E> {
    private E e;//数据.
    public DuLinkedList<E> next;//指向下个节点.
    public DuLinkedList<E> pre;//指向上个节点。

    private int size = 0;//保存该链表中已包含的节点数
    public DuLinkedList<E> head;//指向头节点
    public DuLinkedList<E> tail;//指向尾节点

    public DuLinkedList() {

    }

    public DuLinkedList(E e) {
        this.e = e;
    }

    /**
     * 添加个节点
     */
    public void add(E e) {
        //构造一个节点.
        DuLinkedList<E> node = new DuLinkedList<>(e);
        /*
         * 如果此节点是第一个节点的话
         * 如果此节点不是第一个节点的话
         */
        if(size == 0) {
            head = node;
            tail = node;
        }else {
            /*
             * (1)对于新创建的节点 node:pre 指向上一个节点,next: 下个节点,未创建,不需要的指向。
             * (2)上一次创建的节点,也就是tail的next需要指向这次的节点node,
             * (3)尾节点需要指向当前新创建的节点。
             */
            node.pre = tail;
            tail.next = node;
            tail = node;
        }
        size++;
    }

    /**
     * 插入一个节点
     * 【此代码只是演示,代码待优化】
     * @param index
     * @param e
     */
    public void insert(int index ,E e) {
        if(size == 0) {
            add(e);
        }else {
            DuLinkedList<E> newNode = new DuLinkedList<>(e);
            /*
             * 假设在AB之间插入一个C的节点:
             * 
             * A的next就需要指向C
             * B的pre就需要指向C
             * C的pre指向A
             * C的next指向B
             * 
             */
            //通过索引查找指定index的节点,比如:A
            DuLinkedList<E> node = query(index);

            // node.next 即为B
            newNode.next = node.next;//C的next指向B
            //node 本身就是A
            newNode.pre = node;//C的pre指向A
            node.next.pre = newNode;//B的pre就需要指向C
            node.next = newNode;//A的next就需要指向C
        }


        size++;
    }

    /**
     * 查找第几个的元素的node
     * @param index
     * @return
     */
    private DuLinkedList<E> query(int index) {
        if(index>=size) {
            throw new IndexOutOfBoundsException("索引越界,最大值为="+(size-1));
        }
        DuLinkedList<E> node =  head;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    /**
     * 查找第几个的元素的node的value
     * @param index
     * @return
     */
    public E get(int index) {
        return query(index).e;
    }
}

 

 

2.8 测试下结果

 

public class Test {
    public static void main(String[] args) {
        DuLinkedList<MeiMei> duLinkedList = new DuLinkedList<MeiMei>();

        MeiMei meimei1 = new MeiMei();
        meimei1.setId(1);
        meimei1.setName("1号技师");
        meimei1.setCup("36C");
        duLinkedList.add(meimei1);

        MeiMei meimei2 = new MeiMei();
        meimei2.setId(2);
        meimei2.setName("2号技师");
        meimei2.setCup("38E");
        duLinkedList.add(meimei2);

        MeiMei meimei3 = new MeiMei();
        meimei3.setId(3);
        meimei3.setName("3号技师");
        meimei3.setCup("40F");
        //在index=0的后面插入id=3的数据;
        //此时的数据顺序就是1,3,2
        duLinkedList.insert(0,meimei3);

        //获取到2号美眉
        MeiMei meimei = duLinkedList.get(0);
        System.out.println(meimei);
        meimei = duLinkedList.get(1);
        System.out.println(meimei);
        meimei = duLinkedList.get(2);
        System.out.println(meimei);
    }
}

运行下测试代码:

 

MeiMei [id=1, name=1号技师, cup=36C, age=10]
MeiMei [id=3, name=3号技师, cup=40F, age=10]
MeiMei [id=2, name=2号技师, cup=38E, age=10]

悟纤:厉害了我的师傅。

师傅:悟纤呐,这是要活到老,学到老,还有好多没学了。

我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。

学院中有Spring Boot相关的课程:

à悟空学院:https://t.cn/Rg3fKJD

SpringBoot视频:http://t.cn/A6ZagYTi

Spring Cloud视频:http://t.cn/A6ZagxSR

SpringBoot Shiro视频:http://t.cn/A6Zag7IV

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringData和JPA视频:http://t.cn/A6Zad1OH

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

Sharding-JDBC分库分表实战:http://t.cn/A6ZarrqS

分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

悟纤

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

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

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

打赏作者

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

抵扣说明:

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

余额充值