原创公众号:bigsai

文章已收录在 全网都在关注的数据结构与算法学习仓库

前言

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

双链表与单链表区别

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

单链表:

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

双链表:

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

双链表结构的设计

上文讲单链表的时候,我们当时设计的是一个带头结点的链表就错过了不带头结点操作方式,这里双链表咱们就不带头结点设计实现。并且上文单链表实现的时候是没有尾指针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需要表示第一个节点故改变指向)

尾插:

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

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

按编号插入

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

1 新建插入节点node

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

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

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

整个流程的动态图为:

删除

只有单个节点删除

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

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

头删除

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

流程大致分为:

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

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

尾删除

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

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

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

普通删除

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

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

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

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

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

实现与测试

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

代码:



/*
* 不带头节点的
*/
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;
}
}

测试:

结语

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

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

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

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

图解双链表(Java实现)的更多相关文章

  1. JAVA 链表操作:单链表和双链表

    主要讲述几点: 一.链表的简介 二.链表实现原理和必要性 三.单链表示例 四.双链表示例 一.链表的简介 链表是一种比较常用的数据结构,链表虽然保存比较复杂,但是在查询时候比较便捷,在多种计算机语言都 ...

  2. java实现双链表(差点没写吐系列...)

    刚才把单链表写完了,现在又把双链表写了,双链表和单链表的区别就是每个节点有prior和next两个指针,不同于单链表的一个next指针,而且,正是因为有这两个指针,所以双链表可以前后两个方向去移动指针 ...

  3. 数组、单链表和双链表介绍 以及 双向链表的C/C++/Java实现

    概要 线性表是一种线性结构,它是具有相同类型的n(n≥0)个数据元素组成的有限序列.本章先介绍线性表的几个基本组成部分:数组.单向链表.双向链表:随后给出双向链表的C.C++和Java三种语言的实现. ...

  4. JAVA容器-模拟LinkedList实现(双链表)

    概述 LinkedList实质上就是双向链表的拓展的实现,我们将关注一下问题.LinkedList 1.双向链表怎么来实现插入.删除.查询? 2.利用二分法提高查询效率. 3.不同步,线程不安全,需要 ...

  5. 双链表算法原理【Java实现】(八)

    前言 前面两节内容我们详细介绍了ArrayList,一是手写实现ArrayList数据结构,而是通过分析ArrayList源码看看内置实现,关于集合内容一如既往,本节课我们继续学习集合LinkedLi ...

  6. Java双链表

    一.概述 二.英雄类 class HeroNode { //值域 public int id; public String name; public String nickName; //指针域 pu ...

  7. 《程序员代码面试指南》第二章 链表问题 在单链表和双链表中删除倒数第K个节点

    题目 在单链表和双链表中删除倒数第K个节点 java代码 /** * @Description:在单链表和双链表中删除倒数第K个节点 * @Author: lizhouwei * @CreateDat ...

  8. 深入理解Redis 数据结构—双链表

    在 Redis 数据类型中的列表list,对数据的添加和删除常用的命令有 lpush,rpush,lpop,rpop,其中 l 表示在左侧,r 表示在右侧,可以在左右两侧做添加和删除操作,说明这是一个 ...

  9. 数据结构图文解析之:数组、单链表、双链表介绍及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

随机推荐

  1. TensorFlow & Machine Learning

    TensorFlow & Machine Learning TensorFlow 实战 传统方式 规则 + 数据集 => 答案 无监督学习 机器学习 神经元网络 答案 + 数据集 =&g ...

  2. TypeScript Interface vs Types All In One

    TypeScript Interface vs Types All In One TypeScript https://www.typescriptlang.org/docs/handbook/adv ...

  3. Learning JavaScript with MDN (call, apply, bind)

    Learning JavaScript with MDN (call, apply, bind) call, apply, bind Object.prototype.toString() 检测 js ...

  4. js IdleDetector 检测用户是否处于活动状态API

    btn.addEventListener("click", async () => { try { const state = await Notification.requ ...

  5. Baccarat凭什么吸引做市商?2021年将如何发展?

    在过去的一年里,基于资金池的AMM自动化做市商几乎统治了所有DeFi活动,他们没有订单簿,而是根据算法曲线提供资产.尽管在流动性和交易方面取得了令人惊叹的成绩,但是其自身具有无常损失.多代币敞口以及低 ...

  6. VS Code使用Git可视化管理源代码详细教程

    前言: 随着VS Code的功能和插件的不断强大和完善,它已经成为了我们日常开发中一个必不可缺的伙伴了.在之前我曾经写过一篇SourceTree使用教程详解(一个git可视化管理神器,想要了解的话可以 ...

  7. 【Android初级】如何实现一个有动画效果的自定义下拉菜单

    我们在购物APP里面设置收货地址时,都会有让我们选择省份及城市的下拉菜单项.今天我将使用Android原生的 Spinner 控件来实现一个自定义的下拉菜单功能,并配上一个透明渐变动画效果. 要实现的 ...

  8. Power Query 导入多源数据

    导入方法: 导入数据库文件: 修改加载方式: 其他类型数据处理方式类似

  9. Hive实现自增序列及常见的Hive元数据问题处理

    Hive实现自增序列 在利用数据仓库进行数据处理时,通常有这样一个业务场景,为一个Hive表新增一列自增字段(比如事实表和维度表之间的"代理主键").虽然Hive不像RDBMS如m ...

  10. 后端程序员之路 20、python复习

    Welcome to Python.orghttps://www.python.org/ 怎么用最短时间高效而踏实地学习 Python? - 知乎https://www.zhihu.com/quest ...