概述
在计算机科学中,链表是数据元素的线性集合,其每个元素都指向下一个元素,元素存储上并不连续。
性能
随机访问性能
根据 index 查找,时间复杂度 O ( n ) O(n) O(n)
插入或删除性能
- 起始位置: O ( 1 ) O(1) O(1)
- 结束位置:如果已知 tail 尾节点是 O ( 1 ) O(1) O(1),不知道 tail 尾节点是 O ( n ) O(n) O(n)
- 中间位置:根据 index 查找时间 + O ( 1 ) O(1) O(1)
图示
头插(中间插入与尾插相差不大,大家可尝试自己画图理解)
- 新建value为1的节点;
- 将head指向新节点
遍历
- 新建了一个指针p指向了head指向的节点不为空则向后移动(相当p = p.next):
- 直到移动到指向为null之后结束(p = p.next =null)
删除(头部、尾部、中间删除差距不大自己可以尝试画图理解) - 只用将head指向head.next即可
- Java中引用不到的节点会通过gc回收机制自动回收,不会占用内存(例如1这个节点)
案例
根据单向链表的定义,首先定义一个存储 value 和 next 指针的类 Node,和一个描述头部节点的引用
public class SinglyLinkedList {
private Node head = null; //头指针
/*
* 节点类
* */
private static class Node{ //内部类(不用对外暴露内部结构)
int value; //值
Node next; //下一个节点指针
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
}
- Node定义为内部类,是为了对外隐藏实现细节,没必要让类的使用者关心 Node 结构
- 定义为 static 内部类,是因为 Node 不需要与 SinglyLinkedList 实例相关,多个 SinglyLinkedList实例能共用 Node 类定义。
头部添加
public void addFirst(int value) {
// 1. 链表为空
// Node node = new Node(value, null); //创建一个新节点
// head = node; //将head 的next指针指向这个创建的节点
// head = new Node(value, null); //这个时浓缩版本
// 2. 链表非空 (既能处理非空也能处理为空)
head = new Node(value, head);
}
- new一个节点然后将head指针指向这个新结点,如果是首次添加则新节点的next指针因该为null(第一次的话haed也为null所以可以这样写代表两个情况),反之因该为head指向的地址(画图体会)
- 这里head中存的是地址
while 遍历
//遍历
public void loop1(Consumer<Integer> consumer) {
Node p = head; //初始应该指向头指针指向的节点
while (p != null) {
consumer.accept(p.value);
p = p.next; //处理完当前节点之后在在指向下一个节点
}
}
while 遍历
public void loop2(Consumer<Integer> consumer) {
for (Node p = head; p != null; p = p.next ) {
consumer.accept(p.value);
}
}
- 以上两种遍历都可以把要做的事以 Consumer 函数的方式传递进来
- Consumer 的规则是一个参数,无返回值,因此像 System.out::println 方法等都是 Consumer
- 调用 Consumer 时,将当前节点 p.value 作为参数传递给它
迭代器遍历
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node p = head;
@Override
public boolean hasNext() { //是否有下一个元素
return p != null;
}
@Override
public Integer next() { //返回当前元素并指向,下一个元素
int v = p.value;
p = p.next;
return v;
}
};
}
-
hasNext 用来判断是否还有必要调用 next
-
next 做两件事
- 返回当前节点的 value
- 指向下一个节点
递归遍历
public class SinglyLinkedList implements Iterable<Integer> {
// ...
public void loop() {
recursion(this.head);
}
private void recursion(Node curr) {
if (curr == null) {
return;
}
// 前面做些事
recursion(curr.next);
// 后面做些事
}
}
尾部添加
先查找出最后的节点
//查找最后一个节点
private Node findLast() {
Node p;
if (head == null){ //空链表
return null;
}
for (p = head; p.next != null; p = p.next) { //问题,如果head为null ,则p.next 报空指针了
}
return p;
}
//尾插
public void addLast(int value) {
Node last = findLast();
if(last == null) {
addFirst(value);
return ;
}
last.next = new Node(value, null);
}
查找结点(utils)
// 查找Node 节点
private Node findeNode(int index) {
int i = 0;
for(Node p = head; p != null; p = p.next, i++) {
if(i == index) {
return p;
}
}
return null; //没有找到
}
对外开放的查找所在索引值的value
//对外返回索引节点的值
public int get(int index) {
Node node = findeNode(index);
if(node == null) {
IllegalIndex(index);
}
return node.value;
}
向索引位置加入新节点
// 向索引位置加入新节点
public void insert(int index, int value) {
if (index == 0) {
addFirst(value);
return;
}
Node prev = findeNode(index - 1);//找到上一个节点
if(prev == null) IllegalIndex(index);
//创建一个新节点,让perv指向下一个节点的next指针给到新节点,然后自己又指向这个创建的节点
prev.next = new Node(value,prev.next);
}
一个异常封装
private static IllegalArgumentException IllegalIndex(int index) {
throw new IllegalArgumentException(String.format("index [%d] 不合法%n", index)); //不合法参数
}
全部代码:
package com.itheima.datastructure;
//单项链表
import java.util.Iterator;
import java.util.function.Consumer;
public class SinglyLinkedList implements Iterable<Integer> { //整体
private Node head = null; //头指针
/*
* 节点类
* */
private static class Node{ //内部类(不用对外暴露内部结构)
int value; //值
Node next; //下一个节点指针
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
//头茬
public void addFirst(int value) {
// 1. 链表为空
// Node node = new Node(value, null); //创建一个新节点
// head = node; //将head 的next指针指向这个创建的节点
// head = new Node(value, null); //这个时浓缩版本
// 2. 链表非空 (既能处理非空也能处理为空)
head = new Node(value, head);
}
//遍历
public void loop1(Consumer<Integer> consumer) {
Node p = head; //初始应该指向头指针指向的节点
while (p != null) {
consumer.accept(p.value);
p = p.next; //处理完当前节点之后在在指向下一个节点
}
}
public void loop2(Consumer<Integer> consumer) {
for (Node p = head; p != null; p = p.next ) {
consumer.accept(p.value);
}
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node p = head;
@Override
public boolean hasNext() { //是否有下一个元素
return p != null;
}
@Override
public Integer next() { //返回当前元素并指向,下一个元素
int v = p.value;
p = p.next;
return v;
}
};
}
//查找最后一个节点
private Node findLast() {
Node p;
if (head == null){ //空链表
return null;
}
for (p = head; p.next != null; p = p.next) { //问题,如果head为null ,则p.next 报空指针了
}
return p;
}
//尾插
public void addLast(int value) {
Node last = findLast();
if(last == null) {
addFirst(value);
return ;
}
last.next = new Node(value, null);
}
// 查找Node 节点
private Node findeNode(int index) {
int i = 0;
for(Node p = head; p != null; p = p.next, i++) {
if(i == index) {
return p;
}
}
return null; //没有找到
}
//对外返回索引节点的值
public int get(int index) {
Node node = findeNode(index);
if(node == null) {
IllegalIndex(index);
}
return node.value;
}
// 向索引位置加入新节点
public void insert(int index, int value) {
if (index == 0) {
addFirst(value);
return;
}
Node prev = findeNode(index - 1);//找到上一个节点
if(prev == null) IllegalIndex(index);
//创建一个新节点,让perv指向下一个节点的next指针给到新节点,然后自己又指向这个创建的节点
prev.next = new Node(value,prev.next);
}
private static IllegalArgumentException IllegalIndex(int index) {
throw new IllegalArgumentException(String.format("index [%d] 不合法%n", index)); //不合法参数
}
public int size() {
int i = 0;
for (Node p = head; p != null; p = p.next, i++) {
}
return i;
}
// 删除第一个节点
public void removeFirst() {
if(head == null){
throw IllegalIndex(0);
}
head = head.next; //head头指针指向第二个节点 java中会自动清理内存
}
//根据index删除
public void remove(int index) {
if (index == 0) {
removeFirst();
return;
}
Node prev = findeNode(index - 1); //找到上一个节点
if (prev == null) throw IllegalIndex(index); //没有查到节点(前一个结点差不到,那么后面也不会存在)
Node removed = prev.next; //将要被删除的节点
//这个问题出现在删除的节点的上一个节点是最后一个节点的情况(前一个能找到,但是待删除节点不存在)
if (removed == null) throw IllegalIndex(index);
prev.next = removed.next;
}
}