前言
链式存储结构的特点
- 通过指针域表示数据元素间的逻辑关系
- 不需要连续的存储空间
- 存储空间是动态的随时申请随时用
- 和数组比较插入和删除不要移动数据元素
单链表简介
单链表是链表的其中一种也是最基础的数据结构,链表中的数据是用结点来表示的。
每个结点的构成:数据域+指针域(如图)
一个完整的链表:
头结点说明:
本文在链表的第一个结点前额外增加了一虚拟头结点,只是为了方便后面的操作,不是必须的。虚拟头数据域一般不放数据
代码实现
类结构
public class SinglyLinkedList<T> {
private static class Node<T> {
T data;
Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public Node(T data) {
this(data, null);
}
}
private Node<T> header;//链表头
private int size = 0;//链表大小
//构造虚拟头
public SinglyLinkedList() {
this.header = new Node<>(null, null);
}
}
基本操作
插入指定位置
/**
* 找到index的前一个结点
* @param index
* @return
*/
private Node<T> getNodeByIndex(int index) {
if (index < 0 || index > this.size) {
throw new IllegalArgumentException("index is error");
}
Node<T> node = this.header;
for (int i = 0; node != null && i <= index - 1; i++) {
node = node.next;
}
return node;
}
/**
* 添加到链表的指定位置
* @param data
* @param index
*/
public void insert(T data, int index) {
//待插入结点的前驱结点
Node<T> preNode = getNodeByIndex(index);
Node<T> node = new Node<>(data);
//当前结点
Node<T> currNode = preNode.next;
//新结点的后继结点指向当前结点
node.next = currNode;
//待插入结点的后继结点指向新结点
preNode.next = node;
size++;
}
删除指定位置的结点
/**
* 删除指定位置的节点
* @param index
* @return
*/
public boolean remove(int index) {
//待删除结点的前驱结点
Node<T> preNode = getNodeByIndex(index);
if (preNode.next == null) {
return true;
}
//当前结点
Node<T> currNode = preNode.next;
//当前结点后继结点
preNode.next = currNode.next;
size--;
return true;
}
获取指定位置的数据
/**
* 获取指定位置的数据
* @param index
* @return
*/
public T get(int index) {
//结点的前驱结点
Node<T> preNode = getNodeByIndex(index);
if (preNode.next == null) {
return null;
}
return preNode.next.data;
}
链表反转
单链表反转是面试时常考的题目,在这里重点讲解下
原链表:
反转后链表:
/**
* 链表反转
*/
public void reverse() {
Node<T> p = this.header.next;
this.header.next = null;//虚拟表头指针域置为空
Node<T> temp;//临时结点,保存
while (p != null) {
temp = p;
p = p.next;
temp.next = this.header.next;
this.header.next = temp;
}
}
清空链表
/**
* 清空链表
*/
public void clear() {
Node temp = this.header;
while (temp != null) {
Node next = temp.next;
temp.next = null;
temp.data = null;
temp = next;
}
this.size = 0;
}
完整代码
public class SinglyLinkedList<T> {
private static class Node<T> {
T data;
Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public Node(T data) {
this(data, null);
}
}
private Node<T> header;//链表头
private int size = 0;//链表大小
//构造虚拟头
public SinglyLinkedList() {
this.header = new Node<>(null, null);
}
/**
* 追加到链表尾部
*
* @param data
*/
public void append(T data) {
//待插入结点的前驱结点
Node<T> preNode = getNodeByIndex(this.size);
preNode.next = new Node(data);
this.size++;
}
/**
* 找到index的前一个结点
* @param index
* @return
*/
private Node<T> getNodeByIndex(int index) {
if (index < 0 || index > this.size) {
throw new IllegalArgumentException("index is error");
}
Node<T> node = this.header;
for (int i = 0; node != null && i <= index - 1; i++) {
node = node.next;
}
return node;
}
/**
* 添加到链表的指定位置
* @param data
* @param index
*/
public void insert(T data, int index) {
//待插入结点的前驱结点
Node<T> preNode = getNodeByIndex(index);
Node<T> node = new Node<>(data);
//当前结点
Node<T> currNode = preNode.next;
//新结点的后继结点指向当前结点
node.next = currNode;
//待插入结点的后继结点指向新结点
preNode.next = node;
size++;
}
/**
* 删除指定位置的结点
* @param index
* @return
*/
public boolean remove(int index) {
//待删除结点的前驱结点
Node<T> preNode = getNodeByIndex(index);
if (preNode.next == null) {
return true;
}
//当前结点
Node<T> currNode = preNode.next;
//当前结点后继结点
preNode.next = currNode.next;
size--;
return true;
}
/**
* 获取指定位置的数据
* @param index
* @return
*/
public T get(int index) {
//结点的前驱结点
Node<T> preNode = getNodeByIndex(index);
if (preNode.next == null) {
return null;
}
return preNode.next.data;
}
/**
* 链表翻转
*/
public void reverse() {
Node<T> p = this.header.next;
this.header.next = null;//虚拟表头置为空
Node<T> temp;
while (p != null) {
temp = p;
p = p.next;
temp.next = this.header.next;
this.header.next = temp;
}
}
public void show() {
Node<T> temp = this.header.next;
if (temp == null) {
System.out.println("空链表");
return;
}
while (temp != null) {
System.out.print(temp.data);
temp = temp.next;
}
System.out.println("");
}
/**
* 清空链表
*/
public void clear() {
Node temp = this.header;
while (temp != null) {
Node next = temp.next;
temp.next = null;
temp.data = null;
temp = next;
}
this.size = 0;
}
public boolean isEmpty() {
return this.size == 0;
}
/**
* 链表大小
*
* @return
*/
public int size() {
return this.size;
}
}
测试结果
public static void main(String[] args) {
SinglyLinkedList<String> sLinkedList = new SinglyLinkedList<>();
sLinkedList.append("a");
sLinkedList.append("b");
sLinkedList.append("c");
sLinkedList.append("e");
System.out.println("打印链表:");
sLinkedList.show();
System.out.println("插入到指定位置:");
//插入到指定位置
sLinkedList.insert("d",3);
sLinkedList.show();
System.out.println("删除c:");
sLinkedList.remove(2);
sLinkedList.show();
System.out.println("链表翻转:");
sLinkedList.reverse();
sLinkedList.show();
System.out.println("清空链表:");
sLinkedList.clear();
sLinkedList.show();
}