【手写数据结构】双链表最详细图解

简介: 前面有很详细的讲过线性表(顺序表和链表),当时讲的链表以但链表为主,但实际上在实际应用中双链表的应用多一些就比如LinkedList。

前言



前面有很详细的讲过线性表(顺序表和链表),当时讲的链表以但链表为主,但实际上在实际应用中双链表的应用多一些就比如LinkedList。


20190809000824266.png


双链表与单链表区别


逻辑上它们均是线性表的链式实现,主要的区别是节点结构上的构造有所区别,这个区别从而引起操作的一些差异。


单链表:


单链表的一个节点,有储存数据的data,还有后驱节点next(指针)。也就是这个单链表想要一些遍历的操作都得通过前节点—>后节点


d7c7a19ea20eef092f111b8c78a4e2f6.png


双链表:


双链表的一个节点,有存储数据的data,也有后驱节点next(指针),这和单链表是一样的,但它还有一个前驱节点pre(指针)。


f665828c34e3a6a3bf0aaee3c357558a.png


双链表结构的设计


上文讲单链表的时候,我们当时设计的是一个带头结点的链表就错过了不带头结点操作方式,这里双链表咱们就不带头结点设计实现。并且上文单链表实现的时候是没有尾指针tail的,在这里我们设计的双链表带尾指针


所以我们构造的这个双链表是:不带头节点、带尾指针(tail)、双向链表。


对于node节点:


class node<T> {
  T data;
  node<T> pre;
  node<T> next;
  public node() {
  }
  public node(T data) {
    this.data = data;
  }
}


对于链表:


public class doubleList<T> {
  private node<T> head;// 头节点
  private node<T> tail;// 尾节点
  private int length;
    //各种方法  
}


具体操作分析



对于一个链表主要的操作还是增删。增删的话不光需要考虑链表是否带头节点,还有头插、尾插、中间插等多种插入删除形式,其中的一些细节处理也是比较重要的(防止链表崩掉出错),如果你对这块理解不够深入很容易写错也很难排查出来。当然,链表的查找、按位修改操作相比增删操作还是容易很多。


初始化


双链表在最初的时候头指针指向为null。那么对于这个不带头节点的双链表而言。它的head始终指向第一个真实有效的节点tail也指向最后一个有效的节点。在最初的时候head=null,并且tail=head,此时链表为空,等待节点插入。


public doubleList() {
  head = null;
  tail = head;
  length = 0;
  }


插入


空链表插入


  • 对于空链表来说。增加第一个元素可以特殊考虑。因为在链表为空的时候headtail均为null。但head和tail又需要实实在在指向链表中的真实数据(带头指针就不需要考虑)。所以这时候就新建一个nodehead、tail等于它


node<T> teamNode = new node(data);
if (isEmpty()) {
  head = teamNode;
  tail = teamNode;  
}


头插


对于头插入来说。步骤很简单,只需考虑head节点的变化。

  1. 新建插入节点node
  2. head前驱指向node
  3. node后驱指向head
  4. head指向node。(这时候head只是表示第二个节点,而head需要表示第一个节点故改变指向)


image.gif


尾插:

对于尾插入来说。只需考虑尾节点tail节点的变化。

  1. 新建插入节点node
  2. node前驱指向tail
  3. tail后驱指向node
  4. tail指向node。(这时候tail只是表示倒数第二个节点,而tail需要表示最后节点故指向node)


image.gif


按编号插入


对于编号插入来说。要考虑查找和插入两部,而插入既和head无关也和tail无关。

1 新建插入节点node

2 找到欲插入node的前一个节点preNode。和后一个节点nextNode

3 node后驱指向nextNode,nextNode前驱指向node(此时node和后面与链表已经联立,但是和前面处理分离状态)


c0b6f52cac971d29cdaea243527cd3de.png


4 preNode后驱指向node。node前驱指向preNode(此时插入完整操作完毕)


image.png


整个流程的动态图为:


image.gif


删除


只有单个节点删除


无论头删还是尾删,遇到单节点删除的需要将链表从新初始化!


if (length == 1)// 只有一个元素
{
  head = null;
  tail = head;
  length--;
}


头删除

头删除需要注意的就是删除不为空时候头删除只和head节点有关


流程大致分为:


1 head节点的后驱节点前指针pre改为null。(head后面节点本指向head但是要删除第一个先让后面那个和head断绝关系)


image.png


2 head节点指向head.next(这样head就指向我们需要的第一个节点了,前面节点就被删除成功,如果有c++等语言第一个被孤立的节点删除释放即可,但Java会自动释放)


image.png


尾删除

尾删除需要注意的就是删除不为空时候尾删除只和tail节点有关。记得在普通链表中,我们删除尾节点需要找到尾节点的前驱节点。需要遍历整个表,而双向链表可以直接从尾节点遍历到前面。


尾删除就是删除双向链表中的最后一个节点,也就是尾指针所指向的那个节点,思想和头删除的思想一致,具体步骤为:


  1. tail.pre.next=null尾节点的前一个节点(pre)的后驱节点等于null
  2. tail=tail.pre尾节点指向它的前驱节点,此时尾节点由于步骤1next已经为null。完成删除


20190810102240135.gif


普通删除


普通删除需要重点掌握,普通删除要妥善处理好待删除节点的前后关系,具体流程如下:

1:找打待删除节点node的前驱节点prenode(prenode.next是要删除的节点)

2 : prenode.next.next.pre=prenode.(将待删除node的后驱节点aftnode的pre指针指向prenode,等价于aftnode.pre=prenode


image.png


3: prenode.next=prenode.next.next;此时node被跳过成功删除。




完成删除整个流程的动态图为:


20190810112617911.gif


实现与测试


通过上面的思路简单的实现一下双链表,当然有些地方命名不太规范,实现效率有待提升,主要目的还是带着大家理解。


代码:


/*
 * 不带头节点的
 */
public class doubleList<T> {
    class node<T> {
        T data;
        node<T> pre;
        node<T> next;
        public node() {
        }
        public node(T data) {
            this.data = data;
        }
    }
    private node<T> head;// 头节点
    private node<T> tail;// 尾节点
    private int length;
    public doubleList() {
        head = null;
        tail = head;
        length = 0;
    }
    boolean isEmpty() {
        return length == 0 ? true : false;
    }
    void addFirst(T data) {
        node<T> teamNode = new node(data);
        if (isEmpty()) {
            head = teamNode;
            tail = teamNode;
        } else {
            teamNode.next = head;
            head = teamNode;
        }
        length++;
    }
    void add(T data)// 默认尾节点插入
    {
        node<T> teamNode = new node(data);
        if (isEmpty()) {
            head = teamNode;
            tail = teamNode;
        } else {
            tail.next = teamNode;
            teamNode.pre=tail;
            tail = teamNode;
        }
        length++;
    }
    int length()
    {
        return length;
    }
    T getElum(int index)//为了简单统一从头找
    {
        node<T> team=head;
        for(int i=0;i<index;i++)//不带头节点  遍历次数-1
        {
            team=team.next;
        }
        return team.data;
    }
    void add(int index, T data)// 编号插入
    {
        if (index == 0) {
            addFirst(data);
        } else if (index == length) {
            add(data);
        } else {// 重头戏
            node teampre = head;// 为插入的前驱
            // 无头节点,index-1位置找到前驱节点
            for (int i = 0; i < index -1; i++)
            {
                teampre = teampre.next;
            }
            node<T> team = new node(data);// a c 中插入B 找打a
            team.next = teampre.next;// B.next=c
            teampre.next.pre = team;// c.pre=B
            team.pre = teampre;// 关联a B
            teampre.next = team;
            length++;
        }
    }
    void deleteFirst()// 头部删除
    {
        if (length == 1)// 只有一个元素
        {
            head = null;
            tail = head;
            length--;
        } else {
            head = head.next;
            length--;
        }
    }
    void deleteLast() {
        if(length==1)
        {
            head=null;
            tail=head;
            length--;
        }
        else {
            tail.pre.next=null;
            tail=tail.pre;
            length--;
        }
    }
    void delete(int index)
    {
        if(index==0)deleteFirst();
        else if (index==length-1) {
            deleteLast();
        }
        else {//删除 为了理解统一从头找那个节点
            node<T>team=head;
            for(int i=0;i<index-1;i++)
            {
                team=team.next;
            }
            //team 此时为要删除的前节点  a  c   插入B  a为team
            team.next.next.pre=team;//c的前驱变成a
            team.next=team.next.next;//a的后驱变成c
            length--;
        }
    }
    void set(int index,T data)
    {
        node<T>team=head;
        for(int i=0;i<index-1;i++)
        {
            team=team.next;
        }
        team.data=data;
    }
    @Override
    public String toString() {
        node<T> team = head;
        String vaString = "";
        while (team != null) {
            vaString += team.data + " ";
            team = team.next;
        }
        return vaString;
    }
}


测试:


image.png


结语



在插入删除的步骤,很多人可能因为繁琐的过程而弄不明白,但实际上这个操作的写法可能是多样的,但本质操作都是一致的,所以看到其他不同版本有差距也是正常的。


还有很多人可能对一堆next.next搞不清楚,那我教你一个技巧,如果在等号右侧,那么它表示一个节点,如果在等号左侧,那么除了最后一个.next其他的表示节点。例如node.next.next.next可以看成(node.next.next).next。


在做数据结构与算法链表相关题的时候,不同题可能给不同节点去完成插入、删除操作。这种情况操作时候要谨慎先后顺序防止破坏链表结构。


代码操作可能有些优化空间,还请各位大佬指正!记得一键三连支持一下!


目录
相关文章
|
1月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
53 4
|
23天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
44 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
83 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
48 0
|
2月前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
29 7
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
27 3
|
2月前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
21 0
数据结构与算法学习五:双链表的增、删、改、查
【数据结构】——双向链表详细理解和实现
【数据结构】——双向链表详细理解和实现