打造屬於自己的鏈表
前面數組篇中的動態數組實現,以及基於數組實現的棧和隊列,它們的底層依舊是依託於靜態數組,靠的是resize來解決固定容量的問題。但是鏈表就不同了,他實現了真正的動態數據結構,通過指針的指向來實現元素之間的關係依賴。
數組和鏈表的對比
- 數組支持快速查詢,是因爲在創建數組的時候,在堆中創建了連續的空間。正因此,數組在插入和刪除元素的時候可能需要開闢額外的空間,所以慢了。
- 而鏈表真正的優點是實現了動態數據結構,你不要考慮容量的問題,有新元素插入只需要改變一下指針的指向就可以。但也因此喪失了快速查找的功能,因爲在堆中元素與元素之間不是連續的空間,它們可以分佈各地,唯一的聯繫那就是指針了。
鏈表
經過上面的一通操作(哈塞給)之後,相信對數組和鏈表有了更深刻的認識,下面我們來實現屬於自己的鏈表。
package design.linkedlist;
/**
* @program: design
* @description: 鏈表實現類, 增刪改查時間複雜度:O(n)
* @author: cyj
* @create: 2019-03-21 11:32
**/
public class LinkedList<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);
}
@Override
public String toString() {
return e.toString();
}
}
//虛擬頭節點
private Node dummyHead;
int size;
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
/**
* 獲取鏈表中的元素個數
*
* @return
*/
public int getSize() {
return size;
}
/**
* 判斷鏈表是否爲空
*
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在鏈表的index添加元素e
* 在鏈表中不是一個常用的操作,主要理解
* 時間複雜度:O(n)
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. Illegal index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
// Node node = new Node(e);
// node.next = prev.next;
// prev.next = node;
prev.next = new Node(e, prev.next);
size++;
}
/**
* 在鏈表末尾添加新得元素e
* 時間複雜度:O(n)
*
* @param e
*/
public void addLast(E e) {
add(size, e);
}
/**
* 在鏈表頭添加元素
* 時間複雜度:O(1)
*/
public void addFirst(E e) {
// Node node = new Node(e);
// node.next = head;
// head = node;
// head = new Node(e, head);
// size++;
add(0, e);
}
/**
* 獲得鏈表的index處的元素e
* 在鏈表中不是一個常用的操作,主要聯繫理解
* 時間複雜度:O(n)
*/
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Get failed. Illegal index.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
/**
* 獲得鏈表的第一個元素
*
* @return
*/
public E getFirst() {
return get(0);
}
/**
* 獲得鏈表的最後一個元素
*
* @return
*/
public E getLast() {
return get(size - 1);
}
/**
* 修改鏈表的第index個位置的元素爲e
* 時間複雜度:O(n)
*
* @param index
* @param e
*/
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Update failed. Illegal index.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
/**
* 刪除鏈表的第index個位置的元素爲e
* 時間複雜度:O(n)
*
* @param index
*/
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Update failed. Illegal index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
retNode.next = null;
size--;
return retNode.e;
}
/**
* 刪除鏈表第一個元素
* 時間複雜度:O(1)
*
* @return
*/
public E removeFirst() {
return remove(0);
}
/**
* 刪除鏈表最後一個元素
* 時間複雜度:O(n)
*
* @return
*/
public E removeLast() {
return remove(size - 1);
}
/**
* 查找鏈表中是否有元素e
* 時間複雜度:O(n)
*
* @param e
* @return
*/
public boolean contains(E e) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
res.append(cur + "->");
cur = cur.next;
}
// for (Node cur=dummyHead.next;cur!=null;cur=cur.next) {
// res.append(cur + "->");
// }
res.append("NULL");
return res.toString();
}
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 5; i++) {
linkedList.addFirst(i);
System.out.println(linkedList);
}
//在索引爲2的地方添加元素666
linkedList.add(2, 666);
System.out.println(linkedList);
//刪除索引爲2的元素
linkedList.remove(2);
System.out.println(linkedList);
//刪除鏈表第一個元素
linkedList.removeFirst();
System.out.println(linkedList);
//刪除鏈表最後一個元素
linkedList.removeLast();
System.out.println(linkedList);
}
}
運行結果:
0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
4->3->666->2->1->0->NULL
4->3->2->1->0->NULL
3->2->1->0->NULL
3->2->1->NULL
鏈表的添加操作
添加操作時,操作順序很重要。找到要添加節點的前一個節點,然後將添加元素的指針指向節點的下一個節點,最後在把前一個節點的指針指向要添加的元素。
可是頭元素的沒有前一個節點,因爲需要設立一個虛擬頭節點,即上面代碼中的dummyHead。
鏈表的刪除操作
基於鏈表實現棧和隊列
鏈表棧
package design.linkedlist;
import design.stack.Stack;
/**
* @program: design
* @description: 鏈表棧的實現
* @author: cyj
* @create: 2019-03-21 15:47
**/
public class LinkedListStack<E> implements Stack<E> {
private LinkedList<E> list;
public LinkedListStack() {
list = new LinkedList<>();
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void push(E e) {
list.addFirst(e);
}
@Override
public E pop() {
return list.removeFirst();
}
@Override
public E peek() {
return list.getFirst();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Stack : top ");
res.append(list);
return res.toString();
}
public static void main(String[] args) {
LinkedListStack<Integer> stack = new LinkedListStack<>();
for (int i = 0; i < 5; i++) {
stack.push(i);
System.out.println(stack);
}
stack.pop();
System.out.println(stack);
}
}
運行結果:
Stack : top 0->NULL
Stack : top 1->0->NULL
Stack : top 2->1->0->NULL
Stack : top 3->2->1->0->NULL
Stack : top 4->3->2->1->0->NULL
Stack : top 3->2->1->0->NULL
鏈表棧和數組棧的效率對比
package design.linkedlist;
import design.stack.ArrayStack;
import design.stack.Stack;
import java.util.Random;
/**
* @program: design
* @description: 數組棧與鏈表棧的效率對比
* @author: cyj
* @create: 2019-03-21 15:52
**/
public class StackTest {
/**
* 測試使用stack運行opCount個push和pop操作所需要的時間,單位:秒
*
* @param stack
* @param opCount
* @return
*/
private static double testStack(Stack<Integer> stack, int opCount) {
long startTime = System.nanoTime();
Random random = new Random();
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 static void main(String[] args) {
int opCount = 100000;
ArrayStack<Integer> arrayStack = new ArrayStack<>();
double time1 = testStack(arrayStack, opCount);
System.out.println("ArrayStack , time: " + time1 + " s");
LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();
double time2 = testStack(linkedListStack, opCount);
System.out.println("LinkedListStack , time: " + time2 + " s");
}
}
(分別進行入隊操作後再出隊)
運行時間:
ArrayStack , time: 0.013294353 s
LinkedListStack , time: 0.010873164 s
可以看到 執行效率是差不多的。
鏈表隊列
package design.linkedlist;
import design.queue.Queue;
/**
* @program: design
* @description: 實現鏈表隊列
* @author: cyj
* @create: 2019-03-21 17:26
**/
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);
}
@Override
public String toString() {
return e.toString();
}
}
private Node head, tail;
private int size;
public LinkedListQueue() {
head = null;
tail = null;
size = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void enqueue(E e) {
//tail爲空,則head爲空,鏈表爲空
if (tail == null) {
tail = new Node(e);
head = tail;
} else {
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
Node retNode = head;
head = head.next;
retNode.next = null;
if (head == null) {
tail = null;
}
size--;
return retNode.e;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
return head.e;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("LinkedListQueue : front ");
Node cur = head;
while (cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL tail");
return res.toString();
}
public static void main(String[] args) {
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
System.out.println(queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}
運行時間:(front爲隊首,tail爲隊尾,每次增長3個元素,就去除隊首元素)
LinkedListQueue : front 0->NULL tail
LinkedListQueue : front 0->1->NULL tail
LinkedListQueue : front 0->1->2->NULL tail
LinkedListQueue : front 1->2->NULL tail
LinkedListQueue : front 1->2->3->NULL tail
LinkedListQueue : front 1->2->3->4->NULL tail
LinkedListQueue : front 1->2->3->4->5->NULL tail
LinkedListQueue : front 2->3->4->5->NULL tail
LinkedListQueue : front 2->3->4->5->6->NULL tail
LinkedListQueue : front 2->3->4->5->6->7->NULL tail
LinkedListQueue : front 2->3->4->5->6->7->8->NULL tail
LinkedListQueue : front 3->4->5->6->7->8->NULL tail
LinkedListQueue : front 3->4->5->6->7->8->9->NULL tail
數組隊列,循環隊列,鏈表隊列的比較
package design.queue;
import design.linkedlist.LinkedListQueue;
import java.util.Random;
/**
* @program: design
* @description: 數組隊列和循環隊列的比較
* @author: cyj
* @create: 2019-03-20 16:20
**/
public class QueueTest {
/**
* 測試使用q運行opCount個enqueue和dequeue操作所需要的時間,單位:秒
*
* @param q
* @param opCount
* @return
*/
private static double testQueue(Queue<Integer> q, int opCount) {
long startTime = System.nanoTime();
Random random = new Random();
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;
}
public static void main(String[] args) {
int opCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time1 = testQueue(arrayQueue, opCount);
System.out.println("ArrayQueue , time: " + time1 + " s");
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2 = testQueue(loopQueue, opCount);
System.out.println("LoopQueue , time: " + time2 + " s");
LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();
double time3 = testQueue(loopQueue, opCount);
System.out.println("LinkedListQueue , time: " + time3 + " s");
}
}
運行結果:
ArrayQueue , time: 3.300562516 s
LoopQueue , time: 0.012768131 s
LinkedListQueue , time: 0.006027373 s
可以看出來,還是鏈表隊列省時間,因爲沒有涉及到查找的操作。但是當達到百萬或者千萬級別可能就會有出入了。