整体代码结构定义
public class LinkedListTest <E>{
}
节点数据存储定义
这样子定义只是为了可以方便地创建双向链表,循环链表,不影响单链表的创建,在创建链表的过程中,可以修改前驱指针或者后驱指针的使用或否。
// 创建每个数据的节点
private static class Node<E>{
// 节点元素值
E item;
// 前一个节点
Node<E> pre;
// 下一个节点
Node<E> next;
public Node(){
}
public Node(E item) {
this.item = item;
}
public Node(E item, Node<E> next) {
this.item = item;
this.next = next;
}
public Node(E item, Node<E> pre, Node<E> next) {
this.item = item;
this.pre = pre;
this.next = next;
}
public E getItem() {
return item;
}
public void setItem(E item) {
this.item = item;
}
public Node<E> getNext() {
return next;
}
public void setNext(Node<E> next) {
this.next = next;
}
public Node<E> getPre() {
return pre;
}
public void setPre(Node<E> pre) {
this.pre = pre;
}
}
头插法
实例代码(主讲第一种构造方法):
// 头插法
public void createNodeFirst(E e){
final Node<E> first = head;
final Node<E> newNode = new Node<>(e, null, first);
head = newNode;
if (first != null) {
first.pre = newNode;
}else {
tail = newNode;
}
size ++;
}
实例代码(第二种构造方法):
// 头插法
public void createNodeFirst(E e){
// final Node<E> first = head;
// final Node<E> newNode = new Node<>(e, null, first);
// head = newNode;
// if (first != null) {
// first.pre = newNode;
// }else {
// tail = newNode;
// }
// size ++;
final Node<E> first = head;
final Node<E> newNode = new Node<>(e, null);
if (first != null) {
newNode.next = head;
}
head = newNode;
size ++;
}
程序调用:
public static void main(String[] args) {
LinkedListTest<Character> listTest = new LinkedListTest<>();
String test = "abcdefg";
for (char str : test.toCharArray()) {
listTest.createNodeFirst(str);
}
System.out.println(listTest.size);
while (listTest.head != null){
System.out.print(listTest.head.item + " ");
listTest.head = listTest.head.next;
}
}
结果:
代码分析:
(第一次代码循环)第一步代码解读:首先定义一个前置节点 first 指向头结点head,此时的头结点 head 为null值。此时的head节点的构造可以如下所示:
使用IDEA自带的断点调试可以清楚看到每一个节点的数据变化,(不会打断点的恕我无能为力)如下图:
(第一次代码循环)第二步代码解读:调用节点的构造函数创建出一个有具体值的节点出来,命名为newNode,传入构造newNode节点的数据有具体的值 e变量,(使用泛型,支持定义为Integer,char等等基本的数据类型的传递)和前置节点 first 。此时的链表构造可以表示为:
注意: newNode节点中的next指针应当包含了head节点的全部数据,head节点为 null ,那么newNode 节点中的 next 指针也应当是 null, 示意图为了直观了当才这样子画的。
(第一次代码循环)第三步代码解读:主要的作用是将head节点往前移动,存放字符 a 的节点既是head 节点,同时也是 newNode 节点。这个时候的构造图可以这样子解读:
(第一次代码循环)第四步代码解读:首先存放的字符是 a , 这个时候链表只有一个字符,而且前置节点 first 在程序开头指向的是一个没有数据的那头结点 head ,那理所应当此时的 first 节点是一个 null 值的状态,也就是如上面第一步代码解读的结构一样。所以,这一步代码执行的是尾节点 tail 指向新的节点数据newNode。最后一步代码执行的是保存链表中有数据的长度,执行到这一步,链表中具有数据的长度为 1 ,此时的链表结构可以这样子表示:
第二次代码循环:当代码执行到第二次循环时,这个时候的前置节点 first 指向头结点 head,其链表结构与第一次代码循环中head的节点结构一样。
头结点在第一次循环中已经保存有数据了,这个时候创建一个新的节点数据 newNode ,其中保存的字符值为 b 。
head 节点往前移动,这个时候的整体链表结构可以这样子解读:
然后这个时候的 first 节点不为空,那么 first 节点的前节点保存新创建的节点newNode节点。
第三次以上的代码循环与第二次代码循环的分析一致,这里就不做多余的补充了。另外可以看出上面的构图,pre 节点均为空,也就是处于没有使用的状态,这个可以看作是单链表的头插法。
尾插法
实例代码(主讲第一种构造方法):
// 尾插法
public void createNodeLast(E e){
final Node<E> last = tail;
final Node<E> newNode = new Node<>(e, last, null);
tail = newNode;
if (last != null) {
last.next = newNode;
}else {
head = newNode;
}
size ++;
}
实例代码(第二种构造方法):
// 尾插法
public void createNodeLast(E e){
// final Node<E> last = tail;
// final Node<E> newNode = new Node<>(e, last, null);
// tail = newNode;
// if (last != null) {
// last.next = newNode;
// }else {
// head = newNode;
// }
// size ++;
final Node<E> last = tail;
final Node<E> newNode = new Node<>(e, null);
if(last != null){
tail.next = newNode;
tail = newNode;
}else {
head = newNode;
tail = head;
}
size ++;
}
程序调用:
public static void main(String[] args) {
LinkedListTest<Character> listTest = new LinkedListTest<>();
String test = "abcdefg";
for (char str : test.toCharArray()) {
listTest.createNodeLast(str);
}
System.out.println(listTest.size);
while (listTest.head != null){
System.out.println(listTest.head.item);
listTest.head = listTest.head.next;
}
}
结果:
代码分析:
(第一次代码循环)第一步代码解读:此时的链表尾节点 tail 指针为空,创建后置节点 last 指向 链表尾节点 tail 。此时的链表结构可以这样解读:
使用IDEA自带的断点调试可以清楚看到每一个节点的数据变化,(不会打断点的恕我无能为力)如下图:
(第一次代码循环)第二步代码解读:调用节点的构造函数创建出一个有具体值的节点出来,命名为newNode,传入构造newNode节点的数据有具体的值 e 变量和 后置节点 last 。此时的链表构造可以表示为:
由于指向新创建节点 newNode 节点的 last 节点或者是 tail 节点的值均为 null 所以 newNode 节点的前一个节点 pre 的值为 null 。
(第一次代码循环)第三步代码解读: 此时的 链表的尾节点 tail 指向新创建的节点 newNode ,具体的作用是 tail 节点往后移动,这个时候的链表结构可以这样表示:
(第一次代码循环)第四,五步代码解读: 这个时候已经进入到了 if else 语句了,很明显在上图展示的链表结构中last节点的值均为 null 值,这个时候头结点指向新创建的节点 newNode中,同时后面的存放链表有效长度加一(size ++),链表的结构可以这样子解读:
(第二次代码循环)当代码执行到第二次循环时,这个时候的后置节点 last 指向链表尾结点 tail。
尾结点在第一次循环中已经保存有数据了,这个时候创建一个新的节点数据 newNode ,其中保存的字符值为 b ,同时将后置节点 last 存放在新创建节点的前置指针区域,这个时候的链表结构可以这样子解读:
程序运行,这个时候来到 if else 语句中,这个时候可以从上图中看出 last 节点很明显不为空,将后置节点 last 的 下一个节点指向新创建的 newNode 节点,这个时候的链表结构可以这样子解读:
区别不大,也就是 next 节点不为空了,可以这样子理解 newNode 节点的 pre 指针存放了 tail 节点 ,last 节点和 head 节点,差不多就是你中有我,我中有你的样子,在下面的计算节点中展示数据会显示出死循环。
举一个简单的例子你就懂了:春娇与志明各有两颗糖,分别写着春娇吃和志明吃,现在春娇给一颗写着春娇吃的字样的糖给志明,然后志明再给一颗写着志明吃的字样的糖给春娇,问什么时候他们俩才能获得一模一样的糖?显然不可能的是吧?
代码奉上
/**
* @author: 随风飘的云
* @describe: 单链表、双链表、循环链表
* @date 2022/03/21 22:13
*/
public class LinkedListTest <E>{
// 创建每个数据的节点
private static class Node<E>{
// 节点元素值
E item;
// 前一个节点
Node<E> pre;
// 下一个节点
Node<E> next;
public Node(){
}
public Node(E item) {
this.item = item;
}
public Node(E item, Node<E> next) {
this.item = item;
this.next = next;
}
public Node(E item, Node<E> pre, Node<E> next) {
this.item = item;
this.pre = pre;
this.next = next;
}
public E getItem() {
return item;
}
public void setItem(E item) {
this.item = item;
}
public Node<E> getNext() {
return next;
}
public void setNext(Node<E> next) {
this.next = next;
}
public Node<E> getPre() {
return pre;
}
public void setPre(Node<E> pre) {
this.pre = pre;
}
}
private int size;
private Node<E> head;
private Node<E> tail;
/**
* 无参构造函数
*/
public LinkedListTest() {
}
// 头插法
public void createNodeFirst(E e){
final Node<E> first = head;
final Node<E> newNode = new Node<>(e, null, first);
head = newNode;
if (first != null) {
first.pre = newNode;
}else {
tail = newNode;
}
size ++;
}
// 尾插法
public void createNodeLast(E e){
final Node<E> last = tail;
final Node<E> newNode = new Node<>(e, last, null);
tail = newNode;
if (last != null) {
last.next = newNode;
}else {
head = newNode;
}
size ++;
}
public static void main(String[] args) {
// LinkedListTest<Character> listTest = new LinkedListTest<>();
// String test = "abcdefg";
// for (char str : test.toCharArray()) {
// listTest.createNodeFirst(str);
// }
// System.out.println(listTest.size);
// while (listTest.head != null){
// System.out.print(listTest.head.item + " ");
// listTest.head = listTest.head.next;
// }
LinkedListTest<Integer> listTest = new LinkedListTest<>();
for (int i = 0; i < 10; i++) {
listTest.createNodeLast(i);
}
System.out.println(listTest.size);
while (listTest.head != null){
System.out.print(listTest.head.item + " ");
listTest.head = listTest.head.next;
}
}
}