每天都在学习框架,抽时间来巩固一下基础,记录下链表的实现过程。
1. 单向链表

单向链表的接口比较简单,包含两个元素,element数据本体,next指针。
一个单向链表中的头部和尾部是两个特殊的部分。有了头结点就能遍历获取到整个链表的内容,当遍历到指针为Null的节点时,说明到达了尾部,整个链表遍历结束。
@Data
public class Node<T> {
private T element;
private Node next;
public boolean hasNext() {
return next != null;
}
public Node() {
}
public Node(T element) {
this.element = element;
}
public Node(T element, Node next) {
this.element = element;
this.next = next;
}
}1.1. 增加节点
测试用例:
@Test
public void addTest() {
SinglyLinkedList<String> linked = new SinglyLinkedList<>();
linked.add("a");
linked.add("b");
linked.add("c");
assertThat(linked.getLength(), is(3));
SinglyLinkedList<Integer> intLinked = new SinglyLinkedList<>(4);
intLinked.add(3);
intLinked.add(2);
intLinked.add(1);
assertNotNull(intLinked.toString());
assertThat(intLinked.getLength(), is(4));
}实现代码:
public class SinglyLinkedList<T> {
@Getter
private Node head;
/**
* 链表长度
*/
private Integer length;
public SinglyLinkedList() {
length = 0;
}
public SinglyLinkedList(T data) {
this.head = new Node<>(data);
length = 1;
}
public void add(T data) {
Node<T> newHead = new Node<>(data);
if (head == null) {
head = newHead;
} else {
newHead.setNext(head);
head = newHead;
}
length++;
}
public int getLength() {
return this.length;
}
@Override
public String toString() {
if (head == null) {
return "";
}
Node temp = head;
StringBuilder stringBuilder = new StringBuilder();
while (temp.hasNext()) {
stringBuilder.append(temp.getElement()).append(",");
temp = temp.getNext();
}
return stringBuilder.append(temp.getElement()).toString();
}
}1.2. 获取索引数据
测试用例:
@Test(expected = IllegalArgumentException.class)
public void getValueTest() {
SinglyLinkedList<String> stringLinked = new SinglyLinkedList<>();
stringLinked.add("a");
stringLinked.add("b");
stringLinked.add("c");
log.info(stringLinked.toString());
assertThat(stringLinked.getValue(0), is("c"));
assertThat(stringLinked.getValue(2), is("a"));
stringLinked.getValue(3);
}实现代码:
public T getValue(Integer index) {
if (index > length - 1 || index < 0) {
throw new IllegalArgumentException("错误的索引值");
}
Node<T> result = head;
for (int i = 0; i < index; i++) {
result = result.getNext();
}
return result.getElement();
}1.3. 链表翻转
链表翻转在实现的过程中总感觉特别的绕,出现了好几次递归的情况,所以画图梳理了一下逻辑。
通过操作翻转前链表数据,每次取出current和next节点,创建新的节点设置翻转关系后,重复逻辑进行下一个节点的设置。

测试用例:
@Test
public void reverseTest() {
assertReverse(1);
assertReverse(0);
assertReverse(2);
assertReverse(10);
}
private void assertReverse(int num) {
SinglyLinkedList<Integer> linked = new SinglyLinkedList<>();
for (int i = 0; i < num; i++) {
linked.add(i);
}
log.info("翻转前:{}", linked);
linked.reverse();
log.info("翻转后:{}", linked);
Node head = linked.getHead();
for (int i = 0; i < num; i++) {
Integer element = (Integer) head.getElement();
assertThat(element, is(i));
if (head.hasNext()) {
head = head.getNext();
}
}
}实现代码:
/**
* 链表翻转
*/
public void reverse() {
if (head == null || !head.hasNext()) {
return;
}
Node newNode = null;
while (head.hasNext()) {
Node current = head;
Node next = current.getNext();
head = head.getNext();
if (newNode == null) {
newNode = new Node();
newNode.setElement(next.getElement());
newNode.setNext(new Node(current.getElement()));
} else {
newNode = new Node(next.getElement(), newNode);
}
}
this.head = newNode;
}