相关历史文章(阅读本文之前,您可能需要先看下之前的系列?)
烦不烦,别再问我时间复杂度了:这次不色,女孩子进来吧 - 第281篇
「悟纤论:技术之路,比西天还远」
师傅:单链表、双链表,啥,你没有听过。
悟纤:... ...
师傅:技术之路,比西天还远,你以为你学完了,其实你还有很多不懂。坐下,听为师给你慢慢唠叨来。
一、链表
链表可以分为:单链表、循环链表、双向链表
(1)单链表:每个节点只保留一个引用,该引用指向当前节点的下一个节点,没有引用指向头结点,尾节点的next引用为null。
(2)循环链表:一种首尾相连的链表。
(3)双向链表:每个节点有两个引用,一个指向当前节点的上一个节点,另外一个指向当前节点的下一个节点。
先看下单链表:
单链表,左边格子表示数据,右边是指向下一结点的引用,以此连接整个链表。
双向链表呐:
双向:由中间的数据和两段的前后引用组成。有头结点和为结点,置于头节点是否存放数据根据具体作用和要求来定。
BTW:链表很重要的一个概念就是有一个引用域(Java叫引用,c语言是指针),如果在当前对象中只有一个引用下个对象,那么就是单链;如果有两个引用,引用上一个对象,又引用下个对象,那么就是双向链表。
师傅:是不是有点抽象,下面为师用一个例子来说明下双向链表。
悟纤:是有点不好理解。
师傅:在写例子之前,我们先理清下思路:
(1)双向链表:对于双向链表需要指向下个节点和指向上一个节点,那么就需要定义两个属性next,pre。
(2)链表头尾记录:需要使用两个引用记录头节点和尾节点。
(3)实现节点的添加、插入、删除、查询操作。
二、举个栗子
2.1 声明节点类
使用泛型,支持多种数据类型,定义一个节点,类名为:DuLinkedList
package com.kfit.node;
public class DuLinkedList<E> {
private E e;//数据.
public DuLinkedList() {
}
public DuLinkedList(E e) {
this.e = e;
}
}
2.2 双向链表的两个引用
定义双向链表的两个引用next和pre:
public DuLinkedList<E> next;//指向下个节点.
public DuLinkedList<E> pre;//指向上个节点。
2.3 构建链表的信息
需要储存头结点和尾结点,以此根据引用域找到某一位置的结点,还需要整型的size记录链表长度
private int size = 0;//保存该链表中已包含的节点数
public DuLinkedList<E> head;//指向头节点
public DuLinkedList<E> tail;//指向尾节点
2.4 添加节点的方法实现
添加节点的方法主要是:
(1)当添加第一个节点的时候,next和pre都是不需要的进行设置的,head和tail都是当前节点。
(2)当添加第(n>1)个节点的时候,此时对于新节点的pre需要指向上一个节点,也就是最后添加的tail节点,对于上一个节点,此时的next就是当前节点,并且tail需要指向新的节点。
/**
* 添加个节点
*/
public void add(E e) {
//构造一个节点.
DuLinkedList<E> node = new DuLinkedList<>(e);
/*
* 如果此节点是第一个节点的话
* 如果此节点不是第一个节点的话
*/
if(size == 0) {
head = node;
tail = node;
}else {
/*
* (1)对于新创建的节点 node:pre 指向上一个节点,next: 下个节点,未创建,不需要的指向。
* (2)上一次创建的节点,也就是tail的next需要指向这次的节点node,
* (3)尾节点需要指向当前新创建的节点。
*/
node.pre = tail;
tail.next = node;
tail = node;
}
size++;
}
2.5 查询节点
通过index查询节点,这个主要思路,就是从第一个节点开始往后寻找,第一个节点也就是head,然后for(index),通过node.next找到下个节点:
/**
* 查找第几个的元素的node
* @param index
* @return
*/
private DuLinkedList<E> query(int index) {
if(index>=size) {
throw new IndexOutOfBoundsException("索引越界,最大值为="+(size-1));
}
DuLinkedList<E> node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
2.6 查询元素
获取到节点之后,获取到元素就很简单了,只需要node.e就是当前索引的元素了:
/**
* 查找第几个的元素的node的value
* @param index
* @return
*/
public E get(int index) {
return query(index).e;
}
2.7 插入元素
在已有节点AB之间插入一个C的节点,那么就会得到ACB节点,具体需要的操作就是:
(1)A的next 需要指向C;
(2)B的pre需要指向C;
(3)C的pre需要指向A;
(4)C的next需要指向B;
BTW:这里是为了演示,并没有考虑其它一些情况,比如:没有的节点的时候调用,在最后一个节点插入的时候,这时候又不一样了。
看一下上面这个思路的代码:
/**
* 插入一个节点
* 【此代码只是演示,代码待优化】
* @param index
* @param e
*/
public void insert(int index ,E e) {
if(size == 0) {
add(e);
}else {
DuLinkedList<E> newNode = new DuLinkedList<>(e);
/*
* 假设在AB之间插入一个C的节点:
*
* A的next就需要指向C
* B的pre就需要指向C
* C的pre指向A
* C的next指向B
*
*/
//通过索引查找指定index的节点,比如:A
DuLinkedList<E> node = query(index);
// node.next 即为B
newNode.next = node.next;//C的next指向B
//node 本身就是A
newNode.pre = node;//C的pre指向A
node.next.pre = newNode;//B的pre就需要指向C
node.next = newNode;//A的next就需要指向C
}
size++;
}
上面思路的最终代码如下↓↓↓:
package com.kfit.node;
public class DuLinkedList<E> {
private E e;//数据.
public DuLinkedList<E> next;//指向下个节点.
public DuLinkedList<E> pre;//指向上个节点。
private int size = 0;//保存该链表中已包含的节点数
public DuLinkedList<E> head;//指向头节点
public DuLinkedList<E> tail;//指向尾节点
public DuLinkedList() {
}
public DuLinkedList(E e) {
this.e = e;
}
/**
* 添加个节点
*/
public void add(E e) {
//构造一个节点.
DuLinkedList<E> node = new DuLinkedList<>(e);
/*
* 如果此节点是第一个节点的话
* 如果此节点不是第一个节点的话
*/
if(size == 0) {
head = node;
tail = node;
}else {
/*
* (1)对于新创建的节点 node:pre 指向上一个节点,next: 下个节点,未创建,不需要的指向。
* (2)上一次创建的节点,也就是tail的next需要指向这次的节点node,
* (3)尾节点需要指向当前新创建的节点。
*/
node.pre = tail;
tail.next = node;
tail = node;
}
size++;
}
/**
* 插入一个节点
* 【此代码只是演示,代码待优化】
* @param index
* @param e
*/
public void insert(int index ,E e) {
if(size == 0) {
add(e);
}else {
DuLinkedList<E> newNode = new DuLinkedList<>(e);
/*
* 假设在AB之间插入一个C的节点:
*
* A的next就需要指向C
* B的pre就需要指向C
* C的pre指向A
* C的next指向B
*
*/
//通过索引查找指定index的节点,比如:A
DuLinkedList<E> node = query(index);
// node.next 即为B
newNode.next = node.next;//C的next指向B
//node 本身就是A
newNode.pre = node;//C的pre指向A
node.next.pre = newNode;//B的pre就需要指向C
node.next = newNode;//A的next就需要指向C
}
size++;
}
/**
* 查找第几个的元素的node
* @param index
* @return
*/
private DuLinkedList<E> query(int index) {
if(index>=size) {
throw new IndexOutOfBoundsException("索引越界,最大值为="+(size-1));
}
DuLinkedList<E> node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
/**
* 查找第几个的元素的node的value
* @param index
* @return
*/
public E get(int index) {
return query(index).e;
}
}
2.8 测试下结果
public class Test {
public static void main(String[] args) {
DuLinkedList<MeiMei> duLinkedList = new DuLinkedList<MeiMei>();
MeiMei meimei1 = new MeiMei();
meimei1.setId(1);
meimei1.setName("1号技师");
meimei1.setCup("36C");
duLinkedList.add(meimei1);
MeiMei meimei2 = new MeiMei();
meimei2.setId(2);
meimei2.setName("2号技师");
meimei2.setCup("38E");
duLinkedList.add(meimei2);
MeiMei meimei3 = new MeiMei();
meimei3.setId(3);
meimei3.setName("3号技师");
meimei3.setCup("40F");
//在index=0的后面插入id=3的数据;
//此时的数据顺序就是1,3,2
duLinkedList.insert(0,meimei3);
//获取到2号美眉
MeiMei meimei = duLinkedList.get(0);
System.out.println(meimei);
meimei = duLinkedList.get(1);
System.out.println(meimei);
meimei = duLinkedList.get(2);
System.out.println(meimei);
}
}
运行下测试代码:
MeiMei [id=1, name=1号技师, cup=36C, age=10]
MeiMei [id=3, name=3号技师, cup=40F, age=10]
MeiMei [id=2, name=2号技师, cup=38E, age=10]
悟纤:厉害了我的师傅。
师傅:悟纤呐,这是要活到老,学到老,还有好多没学了。
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
学院中有Spring Boot相关的课程:
à悟空学院:https://t.cn/Rg3fKJD
SpringBoot视频:http://t.cn/A6ZagYTi
Spring Cloud视频:http://t.cn/A6ZagxSR
SpringBoot Shiro视频:http://t.cn/A6Zag7IV
SpringBoot交流平台:https://t.cn/R3QDhU0
SpringData和JPA视频:http://t.cn/A6Zad1OH
SpringSecurity5.0视频:http://t.cn/A6ZadMBe
Sharding-JDBC分库分表实战:http://t.cn/A6ZarrqS
分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr