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

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

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

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

前言

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

双链表与单链表区别

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

单链表:

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

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

双链表:

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

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

双链表结构的设计

上一篇讲单链表的时候,当时设计一个带头结点的链表就错过了不带头结点操作方式,这里双链表就不带头结点设计实现。所以本文构造的这个双链表是:不带头节点、带尾指针(tail)的双向链表。r7h28资讯网——每日最新资讯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;        }    }}

具体操作分析

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

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

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

头插入

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

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

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

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

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

尾插入

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

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

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

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

编号插入

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

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

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

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

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

头删除

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

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

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

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

尾删除

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

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

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

编号删除

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

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

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

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

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

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

完整代码

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

代码:r7h28资讯网——每日最新资讯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;        }    }}

结语

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

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

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

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

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

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

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

标签:
  • 热门焦点
  • K8S | Service服务发现

    一、背景在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;图片对于测试「Tes」环境或者
  • 一年经验在二线城市面试后端的经验分享

    忠告这篇文章只适合2年内工作经验、甚至没有工作经验的朋友阅读。如果你是2年以上工作经验,请果断划走,对你没啥帮助~主人公这篇文章内容来自 「升职加薪」星球星友 的投稿,坐
  • 量化指标是与非:挽救被量化指标扼杀的技术团队

    作者 | 刘新翠整理 | 徐杰承本文整理自快狗打车技术总监刘新翠在WOT2023大会上的主题分享,更多精彩内容及现场PPT,请关注51CTO技术栈公众号,发消息【WOT2023PPT】即可直接领取
  • 一个注解实现接口幂等,这样才优雅!

    场景码猿慢病云管理系统中其实高并发的场景不是很多,没有必要每个接口都去考虑并发高的场景,比如添加住院患者的这个接口,具体的业务代码就不贴了,业务伪代码如下:图片上述代码有
  • 使用AIGC工具提升安全工作效率

    在日常工作中,安全人员可能会涉及各种各样的安全任务,包括但不限于:开发某些安全工具的插件,满足自己特定的安全需求;自定义github搜索工具,快速查找所需的安全资料、漏洞poc、exp
  • 中国家电海外掘金正当时|出海专题

    作者|吴南南编辑|胡展嘉运营|陈佳慧出品|零态LT(ID:LingTai_LT)2023年,出海市场战况空前,中国创业者在海外纷纷摩拳擦掌,以期能够把中国的商业模式、创业理念、战略打法输出海外,他们依
  • 东方甄选单飞:有些鸟注定是关不住的

    文/彭宽鸿编辑/罗卿东方甄选创始人俞敏洪带队的&ldquo;7天甘肃行&rdquo;直播活动已在近日顺利收官。成立后一年多时间里,东方甄选要脱离抖音自立门户的传闻不绝于耳,&ldquo;7
  • OPPO K11采用全方位护眼屏:三大护眼能力减轻视觉疲劳

    日前OPPO官方宣布,全新的OPPO K11将于7月25日正式发布,将主打旗舰影像,和同档位竞品相比,其最大的卖点就是将配备索尼IMX890主摄,堪称是2000档位影像表
  • 利用职权私自解除被封帐号 Meta开除20多名员工

    11月18日消息,据外媒援引知情人士表示,过去一年时间内,Facebook母公司Meta解雇或处罚了20多名员工以及合同工,指控这些人通过内部系统以不当方式重置用户帐号,其
Top