Java双向链表(数据结构及应用例子详解)

Java双向链表(数据结构及应用例子详解)-mikechen

双向链表的定义

双向链表也叫双面链表,它的每个节点由三部分组成:prev 指针指向前置节点,此节点的数据和 next 指针指向后置节点。

 

双向链表的数据结构

具体如下图所示:

Java双向链表(数据结构及应用例子详解)-mikechen

双向链表有如下特点:

1)可以使用一个head和一个tail分别指向头部和尾部的节点;
2)每个节点都由三部分组成:前一个节点的指针prev、保存的元素item、后一个节点的指针next;
3)双向链表的第一个节点的prev是null
4)双向链表的最后节点的next是null

 

为什么需要双向链表?

单向链表的遍历方向单一,只能从链头一直遍历到链尾。所以有一个比较大的缺点:当要查询某一个节点的前一个节点时,只能再次从头进行遍历查询,因此效率比较低。

Java双向链表(数据结构及应用例子详解)-mikechen

除此之外,还有两个缺点:

1)单向链表要查询某一个节点的前一个节点时,只能再次从头进行遍历查询,因此效率比较低;

2)单线链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除。

所以,这个时候双向链表就派上用场了,改进了单向链表的缺点。

 

双向链表的应用场景?

单向链表适用于解决一条道走到黑的访问场景,比如给正在排队测温的人测量,只需要测试一次就下一位,无需重复测试。

循环链表适用于具有环状数据结构的场景,比如丢手绢游戏,双向链表则适合需要提供多个方向访问功能的数据结构,比如老师在考场监考,可以来回走动:假设这个考场座位是顺序的。

 

双向链表的应用举例

双向链表的实现思路主要包含如下几个核心方法:

1)遍历:和单链表不一样的地方,在于不仅可以向前,还可以向后查找;

2)添加:先找到双向链表的最后这个节点,把数据添加到双向链表的最后;

3)修改:通过遍历找到该节点,然后直接吧数据替换掉就可以了,思路与单链表是一样的;

4)删除

下面是具体的代码示例:

public class DoubleLinkedList<E> {
    // 链表元素的数量
    private int size;
    // 声明头结点
    private Node first;
    // 声明尾节点
    private Node last;

    // 创建Node节点
    private class Node{
        // 存放内容
        public E data;
        // 指向链表的上一个节点
        public Node prev;
        // 指向下一个节点
        public Node next;
        // 构造方法
        
        public Node() {
            
        }
        
        public Node(Node prev, E data, Node next) {
            this.prev = prev;
            this.data = data;
            this.next = next;
        }

        @Override
        public String toString(){
            return data.toString();
        }
    }

    // 初始化头结点
    public DoubleLinkedList(){
        first = null;
        last = null;
        size = 0;
    }

    /***
     * 获取链表中的元素个数
     * @return
     */
    public int getSize(){
        return size;
    }

    /***
     * 返回链表是否为空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }

    /***
     * 根据链表的index位置添加新的元素e
     * @param index
     * @param data
     */
    public void add(int index, E data){
        // 调用方法
        rangeCheckAdd(index);

        if (index == size) { // 往最后面添加元素
            addLast(data);
        } else if(index == 0){
            addLast(data);
        } else {
            // 新添加节点下一个元素
            Node nextNode = node(index);
            // 新添加节点的上一个元素
            Node prevNode = nextNode.prev;
            // 新添加节点
            Node newNode = new Node (prevNode, data, nextNode);
            // next节点的上一个prev指向新节点
            nextNode.prev = newNode;
            // prevNode节点的下一个next指向新节点
            prevNode.next = newNode;
        }
        size++;
    }

    /***
     * 在链表头添加新的元素e
     * @param data
     */
    public void addFirst(E data){
        // 创建一个新节点
       Node newNode = new Node(null, data, null);
       if(isEmpty()){
           last = newNode; // last -> newNode
       }else {
           first.prev = newNode; // first.prev->newNode
       }
       newNode.next = first; // newNode.next -> first;
       first = newNode;
       size++;
    }

    /***
     * 在链表末尾添加新的元素e
     * @param data
     */
    public void addLast(E data){
        // 创建一个新节点
        Node newNode = new Node(null, data, null);
        if(isEmpty()){
            first = newNode; // first->newNode
        }else{
            last.next = newNode; // last指向的节点指向新节点
            newNode.prev = last; // 新节点的前驱指向last指针
        }
        last = newNode; // last指针指向新节点
        size++;
    }
    
    /**
     * 获取index位置对应的节点对象
     * @param index
     * @return
     */
    private Node node(int index) {
        rangeCheck(index);
        Node node;
        if (index < (size >> 1)) {
            node = first;
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
        } else {
            node = last;
            for (int i = size - 1; i > index; i--) {
                node = node.prev;
            }
        }
        return node;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("size=").append(size).append(", [");
        // 定义一个指针变量
        Node cur = first;
        while(cur != null){
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");
        res.append("]");
        return res.toString();
    }

    // 索引值检查范围方法
    private void rangeCheck(int index){
        if(index < 0 || index >=size){
            // 调用越界处理方法
            outOfBounds(index);
        }
    }

    // 添加方法索引检查范围
    private void rangeCheckAdd(int index){
        if(index < 0 || index >size){
            // 调用越界处理方法
            outOfBounds(index);
        }
    }
    // 数组索引越界处理
    private void outOfBounds(int index){
        throw new IndexOutOfBoundsException("index:" + index + ", Size:" + size);
    }
}

 

作者简介

陈睿|mikechen,10年+大厂架构经验,BAT资深面试官,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

👇阅读更多mikechen架构文章👇

阿里架构 |双11秒杀 |分布式架构 |负载均衡 |单点登录 |微服务 |云原生 |高并发 |架构师

以上

关注作者「mikechen」公众号,获取更多技术干货!

后台回复架构,即可获取《阿里架构师进阶专题全部合集》,后台回复面试即可获取《史上最全阿里Java面试题总结

评论交流
    说说你的看法