1、链表使用的解决方案
【链表的概述】
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
【链表的分类】
链表有很多种不同的类型:单向链表,双向链表以及循环链表。
由于N 个结点依次相链构成的链表的每个结点中只包含一个指针域,故又称单链表或线性链表。
在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。
循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。
2、带头节点单向链表
定义头节点
private Node head = null; // 头节点
节点类
private static class Node {
int value; // 节点的值
Node next; // 下一个节点
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
为什么节点类用 private 修饰?
链表和节点之间存在整体和部分的关系,对外隐藏节点的属性和实现细节。链表类封装了节点类的属性。
为什么节点类用 static 关键字修饰?
static 可以在内部类变量和外部的成员变量没有关系时添加。
【采用头插法创建链表】
public void addFirst(int value) {
// 链表无节点
//head = new Node(value, null);
// 链表有节点
head = new Node(value, head);
}
【遍历链表】
public void loop1 (Consumer<Integer> consumer) {
Node p = head;
while (p != null) { //p当前节点为空时结束循环
consumer.accept(p.value);
p = p.next; //节点位置后移
}
}
list.loop1(value -> System.out.println(value));//输出: 4 3 2 1
public void loop2(Consumer<Integer> consumer) {
for (Node p = head; p != null; p = p.next) {
consumer.accept(p.value);
}
}
list.loop2(value -> System.out.println(value));//输出: 4 3 2 1
@Override
public Iterator<Integer> iterator() {
// 匿名内部类 ----> 带名字内部类
return new NodeIterator();
}
private class NodeIterator implements Iterator<Integer> {
Node p = head; // 新节点指向头节点
@Override
public boolean hasNext() { //判断有无下一个节点
return p != null;
}
@Override
public Integer next() { // 当前节点的值和指向下一个节点的地址
int value = p.value;
p = p.next;
return value;
}
}
【采用尾插法创建链表】
// 查找最后一个节点
private Node findLast() {
//判断是否为空链表
if (head == null){
return null;
}
Node p;
for (p = head; p.next != null; p = p.next) {
}
return p;
}
// 尾插法
public void addLast(int value) {
// 找到最后一个节点
Node last = findLast();
//空链表
if (last == null){
addFirst(value);
return;
}
// 新节点
last.next = new Node(value, null);
}
【获取节点的索引】
public void getIndex () {
int i = 0;
for (Node p = head; p != null; p = p.next, i++) {
System.out.println(p.value + "索引是:" + i);
}
}
【根据指定的索引获取对应节点】
public int get (int index) {
Node node = findNode(index);
if (node == null) {
// 抛出异常
throw IllegalIndex(index);
}
return node.value;
}
// 参数不合法异常
private IllegalArgumentException IllegalIndex(int index) {
return new IllegalArgumentException(
String.format("index[%d] " + index + "不合法 %n"));
}
【向指定索引位置插入节点】
public void insert(int index, int value) {
//索引为0
if (index == 0) {
addFirst(value);
return;
}
// 找到上一个节点
Node prev = findNode(index - 1);
//找不到节点
if (prev == null) {
throw IllegalIndex(index);
}
// 新节点在上一个节点之后
prev.next = new Node(value, prev.next);
}
【删除节点】
// 删除第一节点
public void removeFirst() {
if (head == null) {
throw IllegalIndex(0);
}
head = head.next;
}
// 删除指定索引的节点
public void remove(int index) {
// 索引为 0
if (index == 0) {
removeFirst();
return;
}
Node prev = findNode(index - 1);
if (prev == null) {
throw IllegalIndex(index);
}
// 待删除的节点
Node removed = prev.next;
// 删除节点 是null
if (removed == null) {
throw IllegalIndex(index);
}
prev.next = removed.next;
}
3、哨兵的单向链表
当链表为空时,执行addLast(int value)、insert(int index, int value)、remove(int index)和链表非空时,执行addLast(int value)、insert(int index, int value)、remove(int index)的逻辑不同。采用哨兵节点为了简化addLast(int value)、insert(int index, int value)、remove(int index)操作链表。
【哨兵节点】
private Node head = new Node(100, null);
【初始化节点对象】
private static class Node {
int value; // 节点的值
Node next; // 下一个节点
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
【链表的遍历】
// 遍历链表1: while consumer 要执行的操作
public void loop1(Consumer<Integer> consumer) {
Node p = head.next;
while (p != null) { //p当前节点为空时结束循环
consumer.accept(p.value);
p = p.next; //节点位置后移
}
}
//遍历链表2: fori形式 consumer 要执行的操作
public void loop2(Consumer<Integer> consumer) {
for (Node p = head.next; p != null; p = p.next) {
consumer.accept(p.value);
}
}
//遍历3:迭代器
@Override
public Iterator<Integer> iterator() {
// 匿名内部类 ----> 带名字内部类
return new NodeIterator();
}
private class NodeIterator implements Iterator<Integer> {
Node p = head.next; // 新节点指向头节点
@Override
public boolean hasNext() { //判断有无下一个节点
return p != null;
}
@Override
public Integer next() { // 当前节点的值和指向下一个节点的地址
int value = p.value;
p = p.next;
return value;
}
}
【查找最后一个节点】
private Node findLast() {
Node p;
for (p = head; p.next != null; p = p.next) {
}
return p;
}
【根据索引查找指定的节点】
private Node findNode(int index) {
int i = -1;
for (Node p = head; p != null; p = p.next, i++) {
if (i == index) {
return p;
}
}
return null;
}
【从尾部插入节点】
public void addLast(int value) {
// 找到最后一个节点
Node last = findLast();
// 新节点
last.next = new Node(value, null);
}
【获取索引指定的节点】
public int get(int index) {
Node node = findNode(index);
if (node == null) {
// 抛出异常
throw IllegalIndex(index);
}
return node.value;
}
【获取链表的节点索引】
public void getIndex() {
int i = 0;
for (Node p = head.next; p != null; p = p.next, i++) {
System.out.println(p.value + "索引是:" + i);
}
}
【向指定位置插入节点】
public void insert(int index, int value) {
// 找到上一个节点
Node prev = findNode(index - 1);
//找不到节点
if (prev == null) {
throw IllegalIndex(index);
}
// 新节点在上一个节点之后
prev.next = new Node(value, prev.next);
}
【参数不合法异常】
private IllegalArgumentException IllegalIndex(int index) {
return new IllegalArgumentException(
String.format("index[%d] 不合法 %n" ));
}
【删除节点】
public void remove(int index) {
Node prev = findNode(index - 1);
if (prev == null) {
throw IllegalIndex(index);
}
// 待删除的节点
Node removed = prev.next;
// 删除节点 是null
if (removed == null) {
throw IllegalIndex(index);
}
prev.next = removed.next;
}
// 删除第一节点
public void removeFirst() {
remove(0);
}