本文主要内容:
1、单链表理解的最痛点
2、单链表编写的最痛点
3、单链表实现代码
1、单链表理解的最痛点
所谓的临时节点,又有人称为可以移动的节点。其实这些名字都不形象也不利于让别人理解。
我们以在链表中添加元素为例子来理解,如下代码:(并不是真正链表,用来实验)
方案一:
class Solution {
public static void main(String[] args) {
Node node= new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
node.next = node2;
node2.next = node3;
node3.next = node4;
//在node的尾部增加一个值为10的节点
//方案一:
while (node.next != null) {
node = node.next;
}
node.next = new Node(10);
}
}
class Node<T> {
Node next;
T t;
Node(T t) {
this.t = t;
}
}
方案一是没有办法实现在node的尾部增加一个值为10的节点
这个要求的,方案一代码运行结果:
我们看到node不但没有实现新增,最终node变成了只有2个节点了。
这就是没有用临时节点temp的原因(我不喜欢临时节点这个名字,暂且这么叫它),在方案一中,一开始node有4个节点:node,node2,node3,node4
,每一个节点都是一个Node
的对象。我们知道对象是引用,即 Node node= new Node(1);
node
是一个变量名,引用类型的变量名是存储数据值的内存位置的名字,在这里变量名和内存位置进行映射,内存位置和数据值映射。当while
循环里进行node = node.next;
这个代码时,第一次循环是把node2
变量名映射的内存位置赋值给node变量名映射的内存位置,通俗一点就是把node2
赋给node
,以此类推,当跳出while循环的时候,只有一个node4
,因为node4.next =null
。
方案二:
class Solution {
public static void main(String[] args) {
Node node= new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
node.next = node2;
node2.next = node3;
node3.next = node4;
Node temp = node;
while (temp.next != null) {
temp = temp.next;
}
temp.next = new Node(10);
}
}
class Node<T> {
Node next;
T t;
Node(T t) {
this.t = t;
}
}
方案二解决了上述的问题,用一个临时节点Node temp = node;
,在未进入while循环时,temp和node变量名都会映射到node的内存位置,每一个节点都有自己的引用地址(和内存位置是一个概念,这里用引用地址更通俗),即node,node2,node3,node4,temp
都有自己的引用地址,这些地址指向了数据值存储地址的首地址。我们用while
在循环temp
时,即temp
的引用地址从和node
的引用地址相同,到和node2
的引用地址相同,最终循环到和node4
的引用地址相同,且修改了node4
的next
。这个过程不会影响到node
,因为node
指向的地址的值并没有被改变,改变的是node4
的。
2、单链表编写的最痛点
我们运行一下代码一和代码二,请对比一下结果,代码的逻辑似乎没有问题,结果却不一样。请思考一下原因。
代码一:
class Solution {
public static void main(String[] args) {
Node node= new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
node.next = node2;
node2.next = node3;
node3.next = node4;
Node temp = node;
//不同之处
while (temp.next != null) {
temp = temp.next;
}
temp.next = new Node(10);
System.out.println(node.next.next.next.next.val);
}
}
class Node<T> {
Node next;
T t;
Node(T t) {
this.t = t;
}
}
代码二:
class Solution {
public static void main(String[] args) {
Node node= new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
node.next = node2;
node2.next = node3;
node3.next = node4;
Node temp = node;
//不同之处
while (temp != null) {
temp = temp.next;
}
temp = new Node(10);
System.out.println(node.next.next.next.next.val);
}
}
class Node<T> {
Node next;
T t;
Node(T t) {
this.t = t;
}
}
3、单链表的实现代码
/**
* 〈一句话功能简述〉<br>
* 〈功能详细描述〉
*
* @author wangzha
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@SuppressWarnings("ALL")
public class LinkedList<T> {
//头结点,头结点是为了方便在下面的方法中遍历链表用的
public Node head = new Node(null);
public int size;
public LinkedList() {
}
/**
* 获取元素值
*
* @param i
* @return
*/
public T get(int i) {
if (i < 0 || i > size - 1) {
throw new ArrayIndexOutOfBoundsException("获取的位置不合法");
} else {
//把第一个节点给临时节点temp,让temp遍历
Node temp = head;
//counter用来计数,找到i在链表里的节点位置,头结点不算链表的真实节点,所以从-1开始计数
int counter = -1;
while (temp != null) {
if (counter == i) {
return (T) temp.t;
}
temp = temp.next;
counter++;
}
}
return null;
}
/**
* 添加元素
*
* @param t
*/
public void add(T t) {
Node temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = new Node(t);
size++;
}
/**
* 指定位置添加元素
*
* @param i
* @param t
*/
public void add(int i, T t) {
if (i < 0 || i > size) {
throw new ArrayIndexOutOfBoundsException("插入的位置不合法");
} else {
Node temp = head;
int counter = -1;
while (temp != null) {
if ((i - 1) == counter) {
//将i前面的节点指向node,node的指向i节点
Node node = new Node(t);
Node back = temp.next;
temp.next = node;
node.next = back;
size++;
}
temp = temp.next;
counter++;
}
}
}
/**
* @param i
*/
public void delete(int i) {
if (i < 0 || i > size) {
throw new ArrayIndexOutOfBoundsException("删除的位置不合法");
} else {
Node temp = head;
int counter = -1;
while (temp != null) {
//将i前面的节点指向i后面的节点
if ((i - 1) == counter) {
temp.next = temp.next.next;
size--;
}
counter++;
temp = temp.next;
}
}
}
private class Node<T> {
Node next;
T t;
Node(T t) {
this.t = t;
}
}
}