打造属于自己的链表
前面数组篇中的动态数组实现,以及基于数组实现的栈和队列,它们的底层依旧是依托于静态数组,靠的是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
可以看出来,还是链表队列省时间,因为没有涉及到查找的操作。但是当达到百万或者千万级别可能就会有出入了。