- 前言
之前整理了栈和队列两个简单的线性数据结构,并用Java代码实现,今天这篇博客整理下最简单的动态数据结构,链表。然后用Java实现单向链表。
- 链表实现
通过创建一个内部类Node来模拟存储链表的数据,Node类中有两个属性,包括一个指向下一个节点的next和一个存储数据的data。
private class Node {
private Node next; //指向下一个节点
private T data; //存储的数据
//提供三个构造方法
public Node(Node next, T data) {
this.next = next;
this.data = data;
}
public Node(T data) {
this(null, data);
}
public Node() {
this(null, null);
}
}
链表中维护两个属性,一个是链表的头结点的引用head,一个是链表中数据的大小size。首先是两个最简单的方法,获取链表的大小以及判断链表是否为空。
/**
* @author Y
* @date 2020/3/23
*/
public class LinkedList<T> {
private Node head; //链表头结点
private int size; // 链表大小
// 添加默认构造函数
public LinkedList() {
this.head = null;
this.size = 0;
}
// 判断是否为空
public boolean isEmpty() {
return this.size == 0;
}
// 获取当前链表容量
public int getSize() {
return this.size;
}
}
之后是往链表中添加元素的方法,添加元素的时候,首先需要找到需要添加元素的位置的前一个节点 prev,分为三个步骤,首先将新加节点的next指向prev的next,然后将prev的next指向新加的节点,之后再维护一下size变量,这边要注意下往链表头添加元素的情况,链表头没有前一个元素,这时候直接让新加元素的next指向头结点,之后把头结点head赋值成新加的元素。(解释很苍白~~~ 有时间补个图)
/**
* 往链表指定位置添加元素
* @param index
* @param data
*/
public void add(int index, T data) {
if (index < 0 || index > size)
throw new IllegalArgumentException("illegal param index "+index);
// 新增节点
Node node = new Node(data);
if (index == 0) {
node.next = head;
head = node;
} else {
Node prev = head;
// 找出前一个节点
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
node.next = prev.next;
prev.next = node;
}
//维护size变量
size ++;
}
/**
* 添加链表头部元素
* @param data
*/
public void addFirst(T data) {
add(0,data);
}
/**
* 添加链表尾部元素
* @param data
*/
public void addLast(T data) {
add(size, data);
}
删除链表元素,删除链表中的元素也是要找到需要删除元素的前一个节点prev,然后直接将前一个节点的next指向需要删除节点的next的就可以了,之后再维护一下size遍历,同样,删除链表头的时候没有前一个元素,这个时候就直接把head指向head的next就可以了。
/**
* 删除指定下标元素
* @param index
* @return
*/
public T delete(int index) {
//这边删除要注意一下边界值
if (index < 0 || index >= size)
throw new IllegalArgumentException("illegal param index is "+index);
T res;
if (index == 0) {
res = head.data;
head = head.next;
} else {
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//当前节点
Node cur = prev.next;
res = cur.data;
prev.next = cur.next;
}
//维护size变量
size --;
return res;
}
/**
* 删除前一个元素
* @return
*/
public T deleteFirst() {
return delete(0);
}
/**
* 删除后一个元素
* @return
*/
public T deleteLast() {
return delete(size - 1);
}
还有一些比如判断指定元素是否存在,获取指定下标的元素都类似,就不贴了,下面就加一个打印链表的方法。
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedList ");
Node cur = head;
while (cur != null) {
sb.append(cur.data +"-> ");
cur = cur.next;
}
sb.append(" NULL");
return sb.toString();
}
测试用例以及测试结果。
@Test
public void test03() {
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 15; i++) {
if (i == 5 || i == 10) {
linkedList.add(i,i);
} else {
linkedList.addFirst(i);
}
}
System.out.println(linkedList);
linkedList.delete(2);
System.out.println(linkedList);
linkedList.deleteFirst();
System.out.println(linkedList);
linkedList.deleteLast();
System.out.println(linkedList);
}
其实我们还可以通过添加一个虚拟头节点的方式来解决添加和删除元素的时候,头元素没有前一个元素的情况。就简单贴一下代码。
/**
* 虚拟头结点
* @author Y
* @date 2020/3/20
*/
public class DummyLinkedList<T> {
private Node dummyNode; // 使用虚拟的头结点
private int size;
public DummyLinkedList() {
this.dummyNode = new Node(); //调用构造函数的时候 赋予默认值
this.size = 0;
}
private class Node {
private Node next;
private T t;
public Node(Node next, T t) {
this.next = next;
this.t = t;
}
public Node(T t) {
this(null,t);
}
public Node() {
this(null,null);
}
}
public void add(int index, T t) {
if (index < 0 || index > size)
throw new IllegalArgumentException("illegal params index "+index);
Node prev = dummyNode;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node node = new Node(t);
node.next = prev.next;
prev.next = node;
size ++;
}
public void addFirst(T t) {
add(0,t);
}
public void addLast(T t) {
add(size,t);
}
public T delete(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("illegal params index "+index);
Node prev = dummyNode;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node cur = prev.next;
T res = (T) cur.t;
prev.next = prev.next.next;
size--;
return res;
}
public T deleteLast() {
return delete(size - 1);
}
public T deleteFirst() {
return delete(0);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedList");
Node cur = dummyNode;
for (int i = 0; i < size; i++) {
cur = cur.next;
sb.append(cur.t + "->");
}
sb.append("NULL");
return sb.toString();
}
}
测试用例和测试结果。可以看出来测试结果是一样的。
@Test
public void test04() {
DummyLinkedList<Integer> linkedList = new DummyLinkedList<>();
for (int i = 0; i < 15; i++) {
if (i == 5 || i == 10) {
linkedList.add(i,i);
} else {
linkedList.addFirst(i);
}
}
System.out.println(linkedList);
linkedList.delete(2);
System.out.println(linkedList);
linkedList.deleteFirst();
System.out.println(linkedList);
linkedList.deleteLast();
System.out.println(linkedList);
}