概念
特点
链表的数据存储在节点(node)中.
- 优点:真正的动态,不需要处理固定容量问题
- 缺点:丧失了随机访问的能力
数组和链表对比
数组
数组最好用于索引有语意的情况
最大的优点: 支持快速查询链表
链表不适合用于索引有语意的情况
最大的优点: 动态链表的实现
```java package cn.wllsrx.zoe.java.linkedlist;
/**
- 增删改查时间复杂度都为O(n) *
- 增删查只对链表头进行操作时间复杂度为O(1)
@author zoe **/ public class LinkedList
{ private class Node {
public E e;public Node next;public Node(E e, Node next) {this.e = e;this.next = next;}public Node(E e) {this(e, null);}public Node() {this(null, null);}@Overridepublic String toString() {return e.toString();}
}
/**
虚拟头节点 */ private Node dummyHead; private int size;
public LinkedList() { dummyHead = new Node(null, null); size = 0; }
/**
- 获取链表中的元素个数 *
@return 个数 */ public int getSize() { return size; }
/**
- 判断链表是否为空 *
@return 为空true 反之false */ public boolean isEmpty() { return size == 0; }
/**
- 在链表index位置添加元素 时间复杂度O(n) *
- @param index 索引0开始
@param e 元素 */ public void add(int index, E e) { if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. illegal index");
} //dummyHead是索引0前面的一个元素 Node prev = dummyHead; //遍历index前一个位置的元素 for (int i = 0; i < index; i++) {
prev = prev.next;
} prev.next = new Node(e, prev.next); size++;
}
/**
- 在链表的头添加新的元素e 时间复杂度O(n) *
@param e 元素 */ public void addFirst(E e) { add(0, e); }
/**
- 在链表末尾添加元素 时间复杂度O(1) *
@param e 元素 */ public void addLast(E e) { add(size, e); }
/**
- 获取索引位置的元素 时间复杂度O(n) *
- @param index 索引
@return 元素 */ public E get(int index) { //检查索引下标 this.checkIndex(index); //获取索引所在位置的元素,dummyHead.next是链表的第一个元素 Node cur = dummyHead.next; for (int i = 0; i < index; i++) {
cur = cur.next;
} return cur.e; }
/**
- 获取链表第一个元素 *
@return E */ public E getFirst() { return get(0); }
/**
- 获取链表最后一个元素 *
@return E */ public E getLast() { return get(size - 1); }
/**
- 修改索引index位置的元素为e 时间复杂度O(n) *
- @param index 索引
@param e 元素 */ public void set(int index, E e) { this.checkIndex(index); Node cur = dummyHead.next; for (int i = 0; i < index; i++) {
cur = cur.next;
} cur.e = e; }
/**
- 删除索引位置的元素 时间复杂度O(n) *
- @param index 索引
@return 被删除元素 */ public E remove(int index) { this.checkIndex(index); //索引位置的前一个元素 Node pre = dummyHead; for (int i = 0; i < index; i++) {
pre = pre.next;
} //被删除索引位置的元素 Node del = pre.next; pre.next = del.next; del.next = null; size—; return del.e; }
/**
- 删除链表第一个元素 时间复杂度O(1) *
@return 被删除元素 */ public E removeFirst() { return remove(0); }
/**
- 删除链表最后一个元素 时间复杂度O(n) *
@return 被删除元素 */ public E removeLast() { return remove(size - 1); }
/**
- 检查索引是否合法 *
@param index 索引 */ private void checkIndex(int index) { if (index < 0 || index > size) {
throw new IllegalArgumentException("set failed. illegal index");
} }
/**
- 判断元素是否存在 时间复杂度O(n) *
- @param e 元素
- @return 是否存在
*/
public boolean contains(E e) {
Node cur = dummyHead.next;
while (cur != null) {
} return false; }if (cur.e.equals(e)) {return true;}cur = cur.next;
@Overridepublic String toString() {StringBuilder builder = new StringBuilder();for (Node cur = dummyHead.next; cur != null; cur = cur.next) {builder.append(cur).append("->");}builder.append("null");return builder.toString();}public static void main(String[] args) {LinkedList<Integer> linkedList = new LinkedList<>();for (int i = 0; i < 6; i++) {linkedList.addFirst(i);System.out.println(linkedList);}linkedList.add(2, 888);System.out.println(linkedList);linkedList.remove(2);System.out.println(linkedList);linkedList.removeFirst();System.out.println(linkedList);linkedList.removeLast();System.out.println(linkedList);}
}
<a name="3b94686a189018195ae9b7398eff1a73"></a>## 使用链表实现栈<a name="cf8e8ae558b2d5517081b4777d961a68"></a>### 栈接口```javapublic interface Stack<E> {/*** 获取栈中元素个数** 时间复杂度 O(1)* @return int*/int getSize();/*** 判断栈是否为空** 时间复杂度 O(1)* @return boolean*/boolean isEmpty();/*** 在栈中添加元素e(入栈)** 时间复杂度 O(1)均摊* @param e 元素*/void push(E e);/*** 从栈中取出元素(出栈)** 时间复杂度 O(1)均摊* @return E*/E pop();/*** 查看栈顶元素** 时间复杂度 O(1)* @return E*/E peek();}
链表实现栈
/*** 使用链表实现栈** @author zoe**/public class LinkedListStack<E> implements Stack<E> {private final LinkedList<E> list;public LinkedListStack() {list = new LinkedList<>();}@Overridepublic int getSize() {return list.getSize();}@Overridepublic boolean isEmpty() {return list.isEmpty();}@Overridepublic void push(E e) {list.addFirst(e);}@Overridepublic E pop() {return list.removeFirst();}@Overridepublic E peek() {return list.getFirst();}@Overridepublic String toString() {return "Stack: top " +list;}public static void main(String[] args) {LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();for (int i = 0; i < 7; i++) {linkedListStack.push(i);System.out.println(linkedListStack);}linkedListStack.pop();System.out.println(linkedListStack);}}
链表实现栈和数组实现栈的耗时比较
数组实现栈
public class ArrayStack<E> implements Stack<E> {Array<E> array;public ArrayStack(int capacity) {array = new Array<>(capacity);}public ArrayStack() {array = new Array<>();}@Overridepublic int getSize() {return array.getSize();}@Overridepublic boolean isEmpty() {return array.isEmpty();}public int getCapacity() {return array.getCapacity();}@Overridepublic void push(E e) {//在数组末尾添加元素array.addLast(e);}@Overridepublic E pop() {return array.removeLast();}@Overridepublic E peek() {return array.getLast();}@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("Stack: ");builder.append("[");for (int i = 0; i < array.getSize(); i++) {builder.append(array.get(i));if (i != array.getSize() - 1) {builder.append(",");}}builder.append("] top");return builder.toString();}public static void main(String[] args) {ArrayStack<Integer> stack = new ArrayStack<>();for (int i = 0; i < 5; i++) {//入栈stack.push(i);System.out.println(stack);}//出栈stack.pop();System.out.println(stack);}}
耗时对比
public class TestStack {public static void main(String[] args) {int opCount = 100000;ArrayStack<Integer> arrayStack = new ArrayStack<>();System.out.println("ArrayStack, time: " + testStack(arrayStack, opCount) + "s");LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();System.out.println("LinkedListStack, time: " + testStack(linkedListStack, opCount) + "s");}public static double testStack(Stack<Integer> stack, int opCount) {long startTime = System.nanoTime();SecureRandom random = new SecureRandom();for (int i = 0; i < opCount; i++) {stack.push(random.nextInt(Integer.MAX_VALUE));}for (int i = 0; i < opCount; i++) {stack.pop();}long endTime = System.nanoTime();return (endTime - startTime) / 1000000000.0;}}
使用链表实现队列
链表接口
public interface Queue<E> {/*** 入队操作** 时间复杂度 O(1) 均摊 ArrayQueue 和 LoopQueue同* @param e 元素*/void enqueue(E e);/*** 出队操作** 时间复杂度 O(n) ArrayQueue复杂度为O(n) LoopQueue复杂度O(1) 均摊* 出队拿出数组中第一个元素后,剩下的元素都要前移* @return 元素*/E dequeue();/*** 查看队列队首的元素** 时间复杂度 O(1) ArrayQueue 和 LoopQueue同* @return 元素*/E getFront();/*** 获取队列的数量** 时间复杂度 O(1) ArrayQueue 和 LoopQueue同* @return size*/int getSize();/*** 判断队列是否为空** 时间复杂度 O(1) ArrayQueue 和 LoopQueue同* @return true为空, false反之*/boolean isEmpty();}
链表实现队列
public class LinkedListQueue<E> implements Queue<E> {private class Node {public E e;public Node next;public Node(E e, Node next) {this.e = e;this.next = next;}public Node(E e) {this(e, null);}public Node() {this(null, null);}@Overridepublic String toString() {return e.toString();}}private Node head, tail;private int size;public LinkedListQueue() {head = null;tail = null;size = 0;}@Overridepublic void enqueue(E e) {//如果队尾为空,说明队首也为空if (tail == null) {//直接在队尾添加节点tail = new Node(e);head = tail;} else {tail.next = new Node(e);tail = tail.next;}size++;}@Overridepublic E dequeue() {this.checkSize();//出队是删除队首,也就是headNode ret = head;//把head指向之前head的下一个节点head = ret.next;//之前的head的下一个节点改为空ret.next = null;//修改head指向后队列可能为空if (head == null) {tail = null;}size--;return ret.e;}private void checkSize() {if (isEmpty()) {throw new IllegalArgumentException("cannot dequeue from an empty queue");}}@Overridepublic E getFront() {this.checkSize();return head.e;}@Overridepublic int getSize() {return size;}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("Queue: front ");Node cur = head;while (cur!=null){builder.append(cur).append("->");cur = cur.next;}builder.append("Null tail");return builder.toString();}public static void main(String[] args) {LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();for (int i = 0; i < 10; i++) {//向队列中添加元素linkedListQueue.enqueue(i);System.out.println(linkedListQueue);if (i % 3 == 2) {linkedListQueue.dequeue();System.out.println(linkedListQueue);}}}}
数组队列,循环队列和链表队列耗时比较
数组队列
public class ArrayQueue<E> implements Queue<E> {Array<E> array;public ArrayQueue(int capacity) {array = new Array<>(capacity);}public ArrayQueue() {array = new Array<>();}@Overridepublic void enqueue(E e) {//向数组的末尾添加元素array.addLast(e);}@Overridepublic E dequeue() {//取出第一个元素return array.removeFirst();}@Overridepublic E getFront() {return array.getFirst();}@Overridepublic int getSize() {return array.getSize();}@Overridepublic boolean isEmpty() {return array.isEmpty();}public int getCapacity() {return array.getCapacity();}@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("Queue: ");builder.append("front [");for (int i = 0; i < array.getSize(); i++) {builder.append(array.get(i));if (i != array.getSize() - 1) {builder.append(",");}}builder.append("] tail");return builder.toString();}public static void main(String[] args) {ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();for (int i = 0; i < 10; i++) {//向队列中添加元素arrayQueue.enqueue(i);System.out.println(arrayQueue);if (i % 3 == 2) {arrayQueue.dequeue();System.out.println(arrayQueue);}}}}
循环队列
public class LoopQueue<E> implements Queue<E> {private E[] data;private int front, tail;private int size;@SuppressWarnings("unchecked")public LoopQueue(int capacity) {data = (E[]) new Object[capacity + 1];front = 0;tail = 0;size = 0;}public int getCapacity() {return data.length - 1;}public LoopQueue() {this(10);}@Overridepublic void enqueue(E e) {if ((tail + 1) % data.length == front) {resize(getCapacity() * 2);}data[tail] = e;tail = (tail + 1) % data.length;size++;}@SuppressWarnings("unchecked")private void resize(int newCapacity) {E[] newData = (E[]) new Object[newCapacity + 1];for (int i = 0; i < size; i++) {//新数组第一个元素对应的data是front,第二个元素是front+1,第三个是front+2,依次类推//循环队列可能会产生数组越界newData[i] = data[(i + front) % data.length];}data = newData;front = 0;tail = size;}@Overridepublic E dequeue() {if (isEmpty()) {throw new IllegalArgumentException("Cannot dequeue from an empty queue");}E ret = data[front];data[front] = null;front = (front + 1) % data.length;size--;//缩容if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {resize(getCapacity() / 2);}return ret;}@Overridepublic E getFront() {if (isEmpty()) {throw new IllegalArgumentException("Cannot dequeue from an empty queue");}return data[front];}@Overridepublic int getSize() {return size;}@Overridepublic boolean isEmpty() {return front == tail;}@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append(String.format("Queue: size = %d,capacity = %d\n", size, getCapacity()));builder.append("fount [");for (int i = front; i != tail; i = (i + 1) % data.length) {builder.append(data[i]);if (i != size - 1) {builder.append(",");}}builder.append("] tail");return builder.toString();}public static void main(String[] args) {LoopQueue<Integer> loopQueue = new LoopQueue<>();for (int i = 0; i < 10; i++) {//向队列中添加元素loopQueue.enqueue(i);System.out.println(loopQueue);if (i % 3 == 2) {loopQueue.dequeue();System.out.println(loopQueue);}}}}
耗时对比
public class TestQueue {public static void main(String[] args) {int opCount = 100000;System.out.println("数组队列ArrayQueue耗时: " + testQueue(new ArrayQueue<>(), opCount) + "s");System.out.println("循环队列LoopQueue耗时: " + testQueue(new LoopQueue<>(), opCount) + "s");System.out.println("链表队列LinkedListQueue耗时: " + testQueue(new LinkedListQueue<>(), opCount) + "s");}private static double testQueue(Queue<Integer> q, int opCount) {long startTime = System.nanoTime();SecureRandom random = new SecureRandom();for (int i = 0; i < opCount; i++) {//入队操作q.enqueue(random.nextInt(Integer.MAX_VALUE));}for (int i = 0; i < opCount; i++) {//出队操作q.dequeue();}long endTime = System.nanoTime();return (endTime - startTime) / 1000000000.0;}}
链表应用
移除链表元素(leetcode 203)
1. 要求
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
列表中的节点在范围 [0, 104] 内
1 <= Node.val <= 50
0 <= k <= 50
2. Java不使用虚拟头节点实现
public class LinkSolution {private static class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) {this.val = val;}ListNode(int val, ListNode next) {this.val = val;this.next = next;}//使用arr作为参数,创建链表,当前得listNode为链表头节点public ListNode(int[] arr) {if (arr == null || arr.length == 0) {throw new IllegalArgumentException("arr can not be empty");}//将数组第一个元素作为头节点this.val = arr[0];//当前节点ListNode cur = this;//循环遍历从第二个元素开始放到节点的下一节点for (int i = 1; i < arr.length; i++) {cur.next = new ListNode(arr[i]);//将加入的节点赋值给当前节点,遍历添加这一节点的下一节点cur = cur.next;}}@Overridepublic String toString() {StringBuilder stringBuilder = new StringBuilder();ListNode cur = this;while (cur != null){stringBuilder.append(cur.val).append("->");cur = cur.next;}stringBuilder.append("null");return stringBuilder.toString();}}public static ListNode removeElements(ListNode head, int val) {//处理头节点,所有头节点的下一节点也与val相等时循环处理while (head != null && head.val == val) {//若头节点与val相等,删除头节点ListNode delNode = head;//头节点改为将要删除的头节点的下一节点head = head.next;//删除节点的下一节点设为nulldelNode.next = null;//上面过程可直接写为head = head.next;//head = head.next;}if (head == null) {return null;}//待删除的前一个节点.prev是头节点,一定不是待删除的节点,//前面已经对头节点做过处理,走到这一步说明头节点的val一定与val不相等ListNode prev = head;//循环头节点的下一个节点之后所有节点,判断是否删除,逻辑与头节点的删除逻辑相同while (prev.next != null) {if (prev.next.val == val) {ListNode delNode = prev.next;prev.next = delNode.next;delNode.next = null;//上面三行可直接写为//prev.next = prev.next.next;} else {//不需要删除直接将prev直接后挪一个节点prev = prev.next;}}return head;}}
3. go实现
type ListNode struct {Val intNext *ListNode}func removeElements(head *ListNode, val int) *ListNode {for head != nil && head.Val == val {head = head.Next}if head == nil {return head}pre := headfor pre.Next != nil {if pre.Next.Val == val {pre.Next = pre.Next.Next} else {pre = pre.Next}}return head}
4. Java使用虚拟头节点实现
public static ListNode removeElements1(ListNode head, int val) {//创建虚拟头节点ListNode dummyHead = new ListNode(-1);//虚拟头节点得下一节点指向真实头节点dummyHead.next = head;//从第一个元素之前得节点开始,不需要处理头节点ListNode prev = dummyHead;while (prev.next != null) {if (prev.next.val == val) {prev.next = prev.next.next;} else {prev = prev.next;}}return dummyHead.next;}
5.所写方法测试
public static void main(String[] args) {int[] nums = {1,2,6,3,4,5,6};ListNode head = new ListNode(nums);System.out.println(head);System.out.println("不使用虚拟头节点: " + LinkSolution.removeElements(head,6));System.out.println("使用虚拟头节点: " + LinkSolution.removeElements1(head,6));}
