当前位置:首页 > 科技  > 软件

一文搞定双链表,让你彻底弄懂线性表的链式实现

来源: 责编: 时间:2023-11-08 09:11:06 426观看
导读前言前面有很详细的讲过线性表(顺序表和链表),当时讲的链表以单链表为主,但在实际应用中双链表有很多应用场景,例如大家熟知的LinkedList。双链表与单链表区别单链表和双链表都是线性表的链式实现,它们的主要区别在于节点

4ht28资讯网——每日最新资讯28at.com

前言

前面有很详细的讲过线性表(顺序表和链表),当时讲的链表以单链表为主,但在实际应用中双链表有很多应用场景,例如大家熟知的LinkedList。4ht28资讯网——每日最新资讯28at.com

双链表与单链表区别

单链表和双链表都是线性表的链式实现,它们的主要区别在于节点结构。单链表的节点包含数据字段 data 和一个指向下一个节点的指针 next,而双链表的节点除了 data 和 next,还包含指向前一个节点的指针 pre。这个区别会导致它们在操作上有些差异。4ht28资讯网——每日最新资讯28at.com

单链表:

单链表的一个节点,有储存数据的data,还有后驱节点next(指针)。单链表想要遍历的操作都得从前节点—>后节点4ht28资讯网——每日最新资讯28at.com

4ht28资讯网——每日最新资讯28at.com

双链表:

双链表的一个节点,有存储数据的data,也有后驱节点next(指针),这和单链表是一样的,但它还有一个前驱节点pre(指针)。4ht28资讯网——每日最新资讯28at.com

4ht28资讯网——每日最新资讯28at.com

双链表结构的设计

上一篇讲单链表的时候,当时设计一个带头结点的链表就错过了不带头结点操作方式,这里双链表就不带头结点设计实现。所以本文构造的这个双链表是:不带头节点、带尾指针(tail)的双向链表。4ht28资讯网——每日最新资讯28at.com

对于链表主体:

public class DoubleLinkedList<T> {    private Node<T> head;    private Node<T> tail;    private int size;    public DoubleLinkedList(){        this.head = null;        this.tail = null;        size = 0;    }    public void addHead(T data){}    public void add(T data, int index){}    public void addTail(T data){}    public void deleteHead(){}    public void delete(int index){}    public void deleteTail(int index){}    public T get(int index){}    public int getSize() {        return size;    }    private static class Node<T> {        T data;        Node<T> pre;        Node<T> next;        public Node() {        }        public Node(T data) {            this.data = data;        }    }}

具体操作分析

对于一个链表主要的操作还是增删,查询的话不做详细解释。4ht28资讯网——每日最新资讯28at.com

剖析增删其实可以发现大概有头插入、编号插入、末尾插入、头删除、编号删除、尾删除几种情况。然而这几种关于头尾操作的可能会遇到临界点比如链表为空时插入删除、或者删除节点链表为空。4ht28资讯网——每日最新资讯28at.com

这个操作是不带头结点的操作,所以复杂性会高一些!4ht28资讯网——每日最新资讯28at.com

头插入

头插入区分头为空和头不为空两种情况。4ht28资讯网——每日最新资讯28at.com

头为空:这种情况head和tail都指向新节点。4ht28资讯网——每日最新资讯28at.com

头不为空:4ht28资讯网——每日最新资讯28at.com

  • 新节点的next指向head
  • head的pre指向新节点
  • head指向新节点(认新节点为head)

4ht28资讯网——每日最新资讯28at.com

尾插入

尾插需要考虑tail为null和不为null的情况。流程和头插类似,需要考虑tail指针最后的指向。4ht28资讯网——每日最新资讯28at.com

tail为null:此时head也为null,head和tail指向新节点。4ht28资讯网——每日最新资讯28at.com

tail不为null:4ht28资讯网——每日最新资讯28at.com

  • 新节点的pre指向tail
  • tail的next指向新节点
  • tail指向新节点

编号插入

按编号插入分情况讨论,如果是头插或者尾插就直接调用对应的方法。普通方法的实现方式比较灵活,可以找到前驱节点和后驱节点,然后进行指针插入,但是往往很多时候只用一个节点完成表示和相关操作,就非常考验对表示的理解,这里假设只找到preNode节点。 index为0:调用头插。4ht28资讯网——每日最新资讯28at.com

index为size:调用尾插4ht28资讯网——每日最新资讯28at.com

index在(0,size):4ht28资讯网——每日最新资讯28at.com

  • 找到前驱节点preNode。
  • 新节点next指向nextNode(此时用preNode.next表示)。
  • nextNode(此时新节点.next和preNode.next都可表示)的pre指向新节点。
  • preNode的next指向新节点。
  • 新节点的pre指向preNode。

4ht28资讯网——每日最新资讯28at.com

头删除

头删除需要注意的就是删除不为空时候头删除只和head节点有关4ht28资讯网——每日最新资讯28at.com

head不为null:4ht28资讯网——每日最新资讯28at.com

  • head = head.next 表示头指针指向下一个节点
  • head 如果不为null(有可能就一个节点),head.pre = null 断掉与前一个节点联系 ;head如果为null,说明之前就一个节点head和pre都指向第一个节点,此时需要设置tail为null。

4ht28资讯网——每日最新资讯28at.com

尾删除

尾删除和头删除类似,考虑好tail节点情况4ht28资讯网——每日最新资讯28at.com

如果tail不为null:4ht28资讯网——每日最新资讯28at.com

  • tail = tail.pre。
  • 如果tail不为null,那么tail.next = null 表示删除最后一个,如果tail为null,说明之前head和tail都指向一个唯一节点,这时候需要head = null。

编号删除

编号删除和编号插入类似,先考虑是否为头尾操作,然后再进行正常操作。4ht28资讯网——每日最新资讯28at.com

index为0:调用头删4ht28资讯网——每日最新资讯28at.com

index为size:调用尾删4ht28资讯网——每日最新资讯28at.com

index在(0,size):4ht28资讯网——每日最新资讯28at.com

  • 找到待删除节点current。
  • 前驱节点(current.pre)的next指向后驱节点(current.next)。
  • 后驱节点的pre指向前驱节点。

4ht28资讯网——每日最新资讯28at.com

完整代码

根据上面的流程,实现一个不带头结点的双链表,在查找方面,可以根据靠头近还是尾近,选择从头或者尾开始遍历。4ht28资讯网——每日最新资讯28at.com

代码:4ht28资讯网——每日最新资讯28at.com

/* * 不带头节点的 */package code.linearStructure;/** * @date 2023.11.02 * @author bigsai * @param <T> */public class DoubleLinkedList<T> {    private Node<T> head;    private Node<T> tail;    private int size;    public DoubleLinkedList() {        this.head = null;        this.tail = null;        size = 0;    }    // 在链表头部添加元素    public void addHead(T data) {        Node<T> newNode = new Node<>(data);        if (head == null) {            head = newNode;            tail = newNode;        } else {            newNode.next = head;            head.pre = newNode;            head = newNode;        }        size++;    }    // 在指定位置插入元素    public void add(T data, int index) {        if (index < 0 || index > size) {            throw new IndexOutOfBoundsException("Index is out of bounds");        }        if (index == 0) {            addHead(data);        } else if (index == size) {            addTail(data);        } else {            Node<T> newNode = new Node<>(data);            Node<T> preNode = getNode(index-1);            //step 1 2 新节点与后驱节点建立联系            newNode.next = preNode;            preNode.next.pre = newNode;            //step 3 4 新节点与前驱节点建立联系            preNode.next = newNode;            newNode.pre = preNode;            size++;        }    }    // 在链表尾部添加元素    public void addTail(T data) {        Node<T> newNode = new Node<>(data);        if (tail == null) {            head = newNode;            tail = newNode;        } else {            newNode.pre = tail;            tail.next = newNode;            tail = newNode;        }        size++;    }    // 删除头部元素    public void deleteHead() {        if (head != null) {            head = head.next;            if (head != null) {                head.pre = null;            } else { //此时说明之前head和tail都指向唯一节点,链表删除之后head和tail都应该指向null                tail = null;            }            size--;        }    }    // 删除指定位置的元素    public void delete(int index) {        if (index < 0 || index >= size) {            throw new IndexOutOfBoundsException("Index is out of bounds");        }        if (index == 0) {            deleteHead();        } else if (index == size - 1) {            deleteTail();        } else {            Node<T> current = getNode(index);            current.pre.next = current.next;            current.next.pre = current.pre;            size--;        }    }    // 删除尾部元素    public void deleteTail() {        if (tail != null) {            tail = tail.pre;            if (tail != null) {                tail.next = null;            } else {//此时说明之前head和tail都指向唯一节点,链表删除之后head和tail都应该指向null                head = null;            }            size--;        }    }    // 获取指定位置的元素    public T get(int index) {        if (index < 0 || index >= size) {            throw new IndexOutOfBoundsException("Index is out of bounds");        }        Node<T> node = getNode(index);        return node.data;    }    // 获取链表的大小    public int getSize() {        return size;    }    private Node<T> getNode(int index) {        if (index < 0 || index >= size) {            throw new IndexOutOfBoundsException("Index is out of bounds");        }        if (index < size / 2) {            Node<T> current = head;            for (int i = 0; i < index; i++) {                current = current.next;            }            return current;        } else {            Node<T> current = tail;            for (int i = size - 1; i > index; i--) {                current = current.pre;            }            return current;        }    }    private static class Node<T> {        T data;        Node<T> pre;        Node<T> next;        public Node(T data) {            this.data = data;        }    }}

结语

在插入删除的步骤,很多人可能因为繁琐的过程而弄不明白,这个操作的写法可能是多样的,但本质操作都是一致的,要保证能成功表示节点并操作,这个可以画个图一步一步捋一下,看到其他不同版本有差距也是正常的。4ht28资讯网——每日最新资讯28at.com

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

在做数据结构与算法链表相关题的时候,不同题可能给不同节点去完成插入、删除操作。这种情况操作时候要谨慎先后顺序防止破坏链表结构。4ht28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-17665-0.html一文搞定双链表,让你彻底弄懂线性表的链式实现

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Spring Boot中实现购物车相关逻辑及示例代码

下一篇: RabbitMQ发送和接收消息的几种方式

标签:
  • 热门焦点
  • 天猫精灵Sound Pro体验:智能音箱没有音质?来听听我的

    这几年除了手机作为智能生活终端最主要的核心之外,第二个可以成为中心点的产品是什么?——是智能音箱。 手机在执行命令的时候有两种操作方式,手和智能语音助手,而智能音箱只
  • 六大权益!华为8月服务日开启:手机免费贴膜、维修免人工费

    8月5日消息,一年一度的华为开发者大会2023(Together)日前在松山湖拉开帷幕,与此同时,华为8月服务日也式开启,到店可享六大专属权益。华为用户可在华为商城Ap
  • 自动化在DevOps中的力量:简化软件开发和交付

    自动化在DevOps中扮演着重要角色,它提升了DevOps的效能。通过自动化工具和方法,DevOps团队可以实现以下目标:消除手动和重复性任务。简化流程。在整个软件开发生命周期中实现更
  • 一篇文章带你了解 CSS 属性选择器

    属性选择器对带有指定属性的 HTML 元素设置样式。可以为拥有指定属性的 HTML 元素设置样式,而不仅限于 class 和 id 属性。一、了解属性选择器CSS属性选择器提供了一种简单而
  • 一个注解实现接口幂等,这样才优雅!

    场景码猿慢病云管理系统中其实高并发的场景不是很多,没有必要每个接口都去考虑并发高的场景,比如添加住院患者的这个接口,具体的业务代码就不贴了,业务伪代码如下:图片上述代码有
  • 电视息屏休眠仍有网络上传 爱奇艺被质疑“薅消费者羊毛”

    记者丨宁晓敏 见习生丨汗青出品丨鳌头财经(theSankei) 前不久,爱奇艺发布了一份亮眼的一季报,不仅营收和会员营收创造历史最佳表现,其运营利润也连续6个月实现增长。自去年年初
  • 2天涨粉255万,又一赛道在抖音爆火

    来源:运营研究社作者 | 张知白编辑 | 杨佩汶设计 | 晏谈梦洁这个暑期,旅游赛道彻底火了:有的「地方」火了&mdash;&mdash;贵州村超旅游收入 1 个月超过 12 亿;有的「博主」火了&m
  • ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大&ldquo;烤&rdquo;之下的除了众生,还有各大企业发布的ESG报告。ESG是&ldquo;环境保
  • 国行版三星Galaxy Z Fold5/Z Flip5发布 售价7499元起

    2023年8月3日,三星电子举行Galaxy新品中国发布会,正式在国内推出了新一代折叠屏智能手机三星Galaxy Z Fold5与Galaxy Z Flip5,以及三星Galaxy Tab S9
Top