1.单链表特征:
1.1.有一个头结点, 遍历必须从头结点开始
1.2.单向导通, 也就是只能从前向后遍历, 不能反过来
1.3.最后一个结点指向null
1.4.由第一点可以知道, 单链表的查询效率是不够快的, 因为每次查询都需要从头结点开始
1.5.删除和插入元素的效率高
以下就是一个单链表
2.单链表的代码构成
2.1.Node.java(结点)
/*
使用了泛型, 这样可以让链表储存的数据更加多元化
*/
public class Node<T> {
public T data;
public Node<T> next;
public Node(T data) {
this.data = data;
}
}
2.2.Linked.java(用于构成链表)
public class Linked<T> {
private Node<T> head = null;
}
3.增删改查实现
3.1.增
3.1.1.头插入法
一开始head是null, 也就是第一次插入node, 这时候直接让head = node即可
public void addHead(T data) {
Node<T> node = new Node<>(data);
if (head == null) {
head = node;
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/ed840113e32071a228f21e79d959e0bb.png)
![已经将node插入链表](https://i-blog.csdnimg.cn/blog_migrate/d6c5935911b61005bd8f5a52f1434565.png)
如果不是第一次插入结点, 则要将node.next = head, head = node, 这样就可以将结点插入到第一个位置
public void addHead(T data) {
Node<T> node = new Node<>(data);
if (head == null) {
head = node;
} else {
node.next = head;
head = node;
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/df08557725d99c6a4127c4223efa4eea.png)
![](https://i-blog.csdnimg.cn/blog_migrate/134b03eebb895d7a3d2c53342961e19e.png)
3.1.2.尾插入法
尾插入法第一个结点插入时和头插入法是一样的, 这里就不再讲了, 这里只讲插入第n个结点的方法
因为要把node插入到最后一个, 所以要找到链表的尾部进行插入, 寻找链表尾部的办法就是从头结点一个一个判断下去, 看看next是否位null
以该链表为例:
首先我们需要一个临时变量用于寻找最后一个结点, 这里命名为temp, 并且指向和head相同的对象, temp = head
再对temp向下迭代, 让temp = temp.next, 也就是自己等于自己的下一个, 达到向下迭代的目的, 而迭代停止的条件是, 当已经没有下一个时, 即temp.next = null时
这时temp.next == null, 这时候可以结束迭代, 这时就可以插入新的node了, 将temp.next = node即可
具体代码:
public void addTail(T data) {
Node<T> node = new Node<>(data);
// 头为空, 代表链表为空, 直接将头改为新结点
if (head == null) {
head = node;
} else {
// 当头结点不为空, 寻找最后的结点, 将新结点插入到最后结点的下一个
Node<T> temp = head;
// 寻找最后一个结点
while (temp.next != null) {
temp = temp.next;
}
temp.next = node;
}
}
3.2.删
删除分为三种情况:
3.2.1.删除的是第一个结点
直接将head = head.next, 相当于直接跳过第一个结点, 当一个结点不被引用时它会被系统回收
3.2.2.删除的是其他结点
需要使用临时变量temp进行查找, 一个一个向后寻找, 知道找到或找到null则结束
注意: 这里要判断temp.next.data中的数据是否相等, 因为要通过上一个结点才能删除该结点
例如此时要删除的结点是node2, 这时temp.next.data == node2.data
只需要将temp.next = temp.next.next, 这时node2就被跳过了
具体代码:
public void delete(T target) {
// 头结点下一个为空, 且目标就是头结点, 直接清空链表
if (head.next == null && target.equals(head.data)) {
head = null;
return;
}
// 若不止一个结点, 且头结点就是目标结点
if (target.equals(head.data)) {
// 直接跳过头结点, 即删掉头结点
head = head.next;
} else {
// 若头结点不是目标
Node<T> temp = head;
while (true) {
// 找到目标
if (target.equals(temp.next.data)) {
// 进行删除(跳过目标结点)
temp.next = temp.next.next;
break;
}
// 若没找到, 继续向下寻找
temp = temp.next;
}
}
}
3.3.改
寻找符合要求的结点, 将其值修改为新的值, 假设node1就是要修改的结点, temp.data = newData
具体代码:
public void update(T target, T newData) {
// 使用临时结点进行寻找目标结点
Node<T> temp = head;
while (temp != null) {
// 当找到目标结点时
if (temp.data.equals(target)) {
// 更换结点值即可
temp.data = newData;
break;
}
// 若未找到, 继续向下寻找
temp = temp.next;
}
}
3.4.查
3.4.1.遍历链表
和查找目标结点做法类似, 用temp临时结点一个一个向下遍历输出即可, 直到遇到null停止遍历
具体代码:
public void show() {
Node<T> temp = head;
while (temp != null) {
System.out.print(temp.data + " ");
temp = temp.next;
}
}
3.4.2.通过值获得该结点
用temp结点向下查找符合条件的结点, 进行返回, 若循环中没有返回, 则返回null
public Node<T> getNode(T target) {
Node<T> temp = head;
while (temp != null) {
// 找到目标, 返回当前结点
if (temp.data.equals(target)) return temp;
// 没找到继续向下找
temp = temp.next;
}
// 否则返回null
return null;
}
4.测试结果:
public class Main {
public static void main(String[] args) {
Linked<Integer> l = new Linked<>();
l.addTail(1);
l.addTail(2);
l.addTail(3);
l.addHead(4);
l.addHead(5);
l.addHead(6);
l.delete(3);
l.delete(4);
l.show();
}
}
运行结果:
6 5 1 2
可以看到运行结果是没问题的
5.全部代码
为了方便理解, 没有对成员变量进行封装, 直接采用public
1.Node.java
public class Node<T> {
public T data;
public Node<T> next;
public Node(T date) {
this.data = date;
}
}
2.Linked.java
public class Linked<T> {
private Node<T> head = null;
/**
* 通过目标查询相应的结点, 并返回
* 未找到返回null
*
* @param target 目标
* @return 查询到的结点
*/
public Node<T> getNode(T target) {
Node<T> temp = head;
while (temp != null) {
if (temp.data.equals(target)) {
return temp;
}
temp = temp.next;
}
return null;
}
/**
* 头插入法
* 将结点插入第一个位置, 效率较高
*
* @param date 数据
*/
public void addHead(T date) {
Node<T> node = new Node<>(date);
if (head == null) {
head = node;
} else {
node.next = head;
head = node;
}
}
/**
* 尾插入法
* 将结点插入到最后一个位置, 效率较低
*
* @param date 数据
*/
public void addTail(T date) {
Node<T> node = new Node<>(date);
// 头为空, 代表链表为空, 直接将头改为新结点
if (head == null) {
head = node;
} else {
// 当头结点不为空, 寻找最后的结点, 将新结点插入到最后结点的下一个
Node<T> temp = head;
// 寻找最后一个结点
while (temp.next != null) {
temp = temp.next;
}
temp.next = node;
}
}
/**
* 更新数据
*
* @param target 目标结点
* @param newData 新数据
*/
public void update(T target, T newData) {
Node<T> temp = head;
while (temp != null) {
if (temp.data.equals(target)) {
temp.data = newData;
break;
}
temp = temp.next;
}
}
/**
* 删除结点
*
* @param target 目标结点
*/
public void delete(T target) {
// 头结点下一个为空, 代表只有头结点一个, 直接清空链表
if (head.next == null) {
head = null;
return;
}
// 若不止一个结点, 且头结点就是目标结点
if (target.equals(head.data)) {
// 直接跳过头结点, 即删掉头结点
head = head.next;
} else {
// 若头结点不是目标
Node<T> temp = head;
while (true) {
// 寻找目标
if (target.equals(temp.next.data)) {
temp.next = temp.next.next;
break;
}
temp = temp.next;
}
}
}
public void show() {
Node<T> temp = head;
while (temp != null) {
System.out.print(temp.data + " ");
temp = temp.next;
}
}
}
3.Main.java
public class Main {
public static void main(String[] args) {
Linked<Integer> l = new Linked<>();
l.addTail(1);
l.addTail(2);
l.addTail(3);
l.addHead(4);
l.addHead(5);
l.addHead(6);
l.delete(3);
l.delete(4);
l.show();
}
}