1.链表概念
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
链表有一个个节点(Node)组成,每个节点内都会有存放数值和指针的位置.。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向、双向
- 带头、不带头
- 循环、非循环
2.无头单向非循环链表实现
定义 LinkedNode类的成员的属性和方法
class LinkedNode {
public int data = 0;
public LinkedNode next = null;
public LinkedNode(int data) {
this.data = data;
}
}
链表的头结点(第一个节点). 有了这个头结点之后,就可以根据 next 把所有的剩下的元素都获取到
private LinkedNode head = null;
1.头插法
public void addFirst(int elem) {
LinkedNode node = new LinkedNode(elem);
if (this.head == null) {
// 如果是空链表
this.head = node;
return;
}
// 非空的情况
node.next = head.next;
head = node;
return;
}
2.尾插法
public void addLast(int elem) {
LinkedNode node = new LinkedNode(elem);
if (this.head == null) {
this.head = node;
return;
}
LinkedNode cur = this.head;
while (cur.next != null) {
cur = cur.next;
}
// 循环结束之后, cur 就已经指向最后一个节点了
// 新节点插入到 cur 之后即可
cur.next = node;
}
3.任意位置插入,第一个数据结点为0号下标
public void addIndex(int index, int elem) {
LinkedNode node = new LinkedNode(elem);
int len = size();
if(index < 0 || index > len){
return;
}
if(index == 0){
addFirst(elem);
return;
}
if(index == len){
addLast(elem);
return;
}
LinkedNode prev = getIndexPos(index-1);
node.next = prev.next;
prev.next = node;
}
private LinkedNode getIndexPos(int index) {
// 是否要判定一下 index 是在有效范围中呢?
LinkedNode cur = this.head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur;
}
4.查找是否包含关键字key是否在单链表中
public boolean contains(int toFind) {
// 直接遍历链表, 依次比较每个元素就行了
for (LinkedNode cur = this.head;
cur != null; cur = cur.next) {
if (cur.data == toFind) {
return true;
}
}
return false;
}
5.删除第一次出现关键字为key的节点
public void remove(int toRemove) {
// 1. 先单独处理下空链表的情况
if (head == null) {
return;
}
// 2. 先考虑是否要删除的是头结点
if (head.data == toRemove) {
this.head = this.head.next;
return;
}
// 3. 删除中间的节点, 找到要删除元素的前一个元素
LinkedNode prev = searchPrev(toRemove);
if (prev == null) {
return;
}
// prev.next = prev.next.next; // 这种写法虽然没错, 但是比较抽象
LinkedNode nodeToRemove = prev.next;
prev.next = nodeToRemove.next;
}
private LinkedNode searchPrev(int toRemove) {
// 找到要删除元素的前一个位置
if (this.head == null) {
return null;
}
LinkedNode prev = this.head;
while (prev.next != null) {
if (prev.next.data == toRemove) {
return prev;
}
prev = prev.next;
}
// 返回 null 表示没找到
return null;
}
6.删除所有值为key的节点
public void removeAllKey(int toRemove) {
if (head == null) {
return;
}
// 先处理后续的节点, 最后处理头结点的情况
LinkedNode prev = head;
LinkedNode cur = head.next;
while (cur != null) {
if (cur.data == toRemove) {
// cur 对应的节点就应该删掉
prev.next = cur.next; // 删除 cur 指向的节点
cur = prev.next; // 让 cur 继续指向 prev 的下一个节点
} else {
// cur 对应的节点不用删除
prev = cur;
cur = cur.next;
}
}
// 头结点的情况
if (this.head.data == toRemove) {
this.head = this.head.next;
}
return;
}
7.得到链表的长度,打印和清除
public int size() {
int size = 0;
for (LinkedNode cur = this.head;
cur != null; cur = cur.next) {
size++;
}
return size;
}
public void display() {
// 打印链表中的所有元素
System.out.print("[");
for (LinkedNode node = this.head;
node != null; node = node.next) {
System.out.print(node.data);
if (node.next != null) {
// 如果不是最后一个元素就加上 , 否则不加
System.out.print(", ");
}
}
System.out.println("]");
}
public void clear() {
// Java 垃圾回收, 后续的所有节点, 只要这个节点
// 没有引用指向它, 就会被 JVM 判定成垃圾.
// 就会被自动回收掉
this.head = null;
}
3.无头双向链表实现
定义 DLinkedNode类的成员的属性和方法
class DLinkedNode {
public int val = 0;
public DLinkedNode prev = null;
public DLinkedNode next = null;
public DLinkedNode(int val) {
this.val = val;
}
}
链表的头结点和构造方法
private DLinkedNode head = null;
public DLinkedList() {
// 创建傀儡节点
head = new DLinkedNode(-1);
// 带环
head.next = head;
head.prev = head;
}
1.头插法
public void addFirst(int data) {
// 1. 创建一个新的节点
DLinkedNode newNode = new DLinkedNode(data);
DLinkedNode next = head.next;
newNode.next = next;
next.prev = newNode;
head.next = newNode;
newNode.prev = head;
}
2.尾插法
public void addLast(int data) {
DLinkedNode newNode = new DLinkedNode(data);
// 这个节点就是新的节点的前一个节点
// head 就是新的节点的后一个节点
DLinkedNode prev = head.prev;
newNode.next = head;
head.prev = newNode;
prev.next = newNode;
newNode.prev = prev;
}
3.任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
int size = size();
if (index < 0 || index > size) {
return;
}
if (index == 0) {
// 头插
addFirst(data);
return;
}
if (index == size) {
// 尾插
addLast(data);
return;
}
DLinkedNode next = getPos(index);
DLinkedNode prev = next.prev;
DLinkedNode newNode = new DLinkedNode(data);
prev.next = newNode;
newNode.prev = prev;
next.prev = newNode;
newNode.next = next;
}
public DLinkedNode getPos(int index) {
// 找到下标为 index 对应的节点
DLinkedNode cur = head.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur;
}
4.查找是否包含关键字key是否在单链表中
public boolean contains(int toFind) {
for (DLinkedNode cur = head.next;
cur != head; cur = cur.next) {
if (cur.val == toFind) {
return true;
}
}
return false;
}
5.删除第一次出现关键字为key的结点
public void remove(int key) {
// 1. 先根据 key 找到要删除的元素的位置
DLinkedNode toRemove = find(key);
if (toRemove == null) {
// 没有找到要删除的节点
return;
}
// 2. 具体进行删除
DLinkedNode prev = toRemove.prev;
DLinkedNode next = toRemove.next;
prev.next = next;
next.prev = prev;
}
6…删除所有值为key的结点
public void removeAll(int key) {
while (true) {
// 1. 先根据 key 找到要删除的元素的位置
DLinkedNode toRemove = find(key);
if (toRemove == null) {
// 没有找到要删除的节点
return;
}
// 2. 具体进行删除
DLinkedNode prev = toRemove.prev;
DLinkedNode next = toRemove.next;
prev.next = next;
next.prev = prev;
}
}
public DLinkedNode find(int key) {
for (DLinkedNode cur = head.next;
cur != head; cur = cur.next) {
if (cur.val == key) {
return cur;
}
}
return null;
}
7.得到链表的长度,打印和清除
public int size() {
int size = 0;
for (DLinkedNode cur = head.next;
cur != head; cur = cur.next) {
size++;
}
return size;
}
public void display() {
System.out.print("正向: [");
for (DLinkedNode cur = head.next;
cur != head; cur = cur.next) {
System.out.print(cur.val);
if (cur.next != head) {
System.out.print(",");
}
}
System.out.println("]");
System.out.print("反向: [");
for (DLinkedNode cur = head.prev;
cur != head; cur = cur.prev) {
System.out.print(cur.val);
if (cur.prev != head) {
System.out.print(",");
}
}
System.out.println("]");
}
public void clear() {
// clear 预期得到的是一个初始状态下的空链表
// 对于我们当前这样的链表来说, 初始状态也
// 带有一个傀儡节点, 傀儡节点 prev 和 next 指向自身
head.next = head;
head.prev = head;
}