一,链表的概念及结构
- 概念:
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
-
结构:
实际中链表的结构非常多样,以下情况组合起来就有八种链表结构;- 单向 , 双向
- 带头 , 不带头
- 循环 , 非循环
二,代码实现
下面只介绍无头单向非循环链表的实现
- 代码实现:
// 一个节点
class Node {
public int data; // 数据
public Node next = null; // 下一个节点的位置
public Node(int data) {
this.data = data;
}
}
public class LinkedList {
// 管理所有的链表节点. 只需要记录头结点的位置即可
// 初始情况下 head 为 null, 此时表示空链表(不带傀儡节点)
private Node head = null;
// private Node tail = null; // 优化尾插
// private int size = 0; // 优化获取长度
public void addFirst(int data) {
// 1. 根据 data 值构建一个链表节点(Node对象)
Node node = new Node(data);
// 2. 如果链表为空链表
if (head == null) {
head = node;
return;
}
// 3. 如果链表不是空链表
node.next = head;
head = node;
}
public void addLast(int data) {
// 1. 根据 data 构造一个 Node 对象
Node node = new Node(data);
// 2. 如果链表为空链表
if (head == null) {
head = node;
return;
}
// 3. 如果链表非空, 需要先找到这个链表末尾的最后一个节点
Node tail = head;
while (tail.next != null) {
tail = tail.next;
}
// 循环结束之后, tail 就对应到最后一个节点了
tail.next = node;
}
public void display() {
// 把链表中的每个元素都打印出来
for (Node cur = head; cur != null; cur = cur.next) {
System.out.print(cur.data + " ");
}
System.out.println();
}
public int getSize() {
int size = 0;
for (Node cur = head; cur != null; cur = cur.next) {
size++;
}
return size;
}
// 插入成功, 返回 true, 否则 false
public boolean addIndex(int index, int data) {
int size = getSize();
// 1. 判定 index 是否有效
if (index < 0 || index > size) {
// index 无效, 插入失败
return false;
}
// 2. 如果 index 为 0, 相当于头插
if (index == 0) {
addFirst(data);
return true;
}
// 3. 如果 index 为 size, 相当于尾插
if (index == size) {
addLast(data);
return true;
}
Node node = new Node(data);
// 4. 如果 index 是一个中间的位置
// a) 先找到 index 的前一个节点 index - 1
Node prev = getPos(index - 1);
// b) 接下来就把新节点插入到 prev 之后
// 注释是头插的代码
// node.next = head;
// head = node;
node.next = prev.next;
prev.next = node;
return true;
}
- 代码测试
// 给定 index 下标, 找到对应的节点
private Node getPos(int index) {
Node cur = head;
for (int i = 0; i < index; i++) {
// cur.next 操作之前必须要保证
// cur 是非 null 的
cur = cur.next;
}
return cur;
}
public boolean contains(int toFind) {
for (Node cur = head; cur != null; cur = cur.next) {
if (cur.data == toFind) {
return true;
}
}
return false;
}
public void remove(int toRemove) {
// 1. 如果要删除元素是头结点, 特殊处理一下
if (head.data == toRemove) {
// 头结点要被删掉
head = head.next;
return;
}
// 2. 如果要删除元素不是头结点, 找到要删除节点的前一个位置
Node prev = searchPrev(toRemove);
// 3. 修改引用的指向, 完成删除
// prev.next = prev.next.next;
Node toDelete = prev.next;
prev.next = toDelete.next;
}
private Node searchPrev(int toRemove) {
// 找到 toRemove 的前一个节点
for (Node cur = head; cur != null
&& cur.next != null; cur = cur.next) {
if (cur.next.data == toRemove) {
return cur;
}
}
return null;
}
public void removeAll(int toRemove) {
// 1. 先删除非头结点, 需要找到待删除节点的前一个位置
// prev 始终指向 cur 的前一个位置
Node prev = head;
Node cur = head.next;
while (cur != null) {
if (cur.data == toRemove) {
// cur 节点需要被删除掉
prev.next = cur.next;
cur = prev.next;
} else {
// prev 和 cur 同步往后移动
prev = cur;
cur = cur.next;
}
}
// 2. 处理头结点为要删除节点的情况
if (head.data == toRemove) {
head = head.next;
}
}
public void clear() {
head = null;
}
}
- 测试代码
public class Test{
private static void testAddFirst(){ //测试头插
LinkedList6 List = new LinkedList6();
List.addFirst(1);
List.addFirst(1);
List.addFirst(1);
List.addFirst(1);
List.display(); //测试链表打印
int size = List.getSize(); //测试获取链表长度
System.out.println(size);
}
private static void testAddLast(){ //测试尾插
LinkedList6 List1 = new LinkedList6();
List1.addLast(1);
List1.addLast(2);
List1.addLast(3);
List1.addLast(4);
List1.addLast(5);
List1.display();
List1.addIndex(2,8); //测试指定位置插入
List1.display();
boolean result = List1.contains(10); //测试链表是否包含某个元素
System.out.println(result);
List1.remove(8); //测试删除链表中指定节点(从左到右第一个出现的)
List1.display();
List1.addLast(1);
List1.addLast(1);
List1.addLast(1);
List1.display();
List1.removeAll(1); //测试删除链表中指定元素的所有节点
List1.display();
List1.clear(); //测试清空链表
List1.display();
}
public static void main(String[] args){
//testAddFirst();
testAddLast();
}
}