第二十五篇 玩转数据结构——链表(LinkedList)

 
 
 
1.. 链表的重要性
  • 我们之前实现的动态数组、栈、队列,底层都是依托静态数组,靠resize来解决固定容量的问题,而"链表"则是一种真正的动态数据结构,不需要处理固定容量的问题;
  • 链表是最简单的动态数据结构;
  • 学习链表有助于更深入的理解"引用"(或指针);
  • 学习链表有助于更深入的理解"递归";
  • 链表可以用来辅助组成其他数据结构;
2.. 链表基础
  • 数据存储在"节点"(Node)中;
  • 链表的形象化解释如下图:
  • 链表的优点在于,它是一种真正的动态数据结构,不需要处理固定容量问题;
  • 链表的缺点在于,相较于数组,失去了随机访问的能力;
  • 数组和链表的对比如下图所示:
3.. 链表的实现
  • 实现链表的业务逻辑如下:
  • 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;
            }
    
            //只传了参数e的构造函数
            public Node(E e) {
                this(e, null);
            }
    
            //不传递参数的构造函数
            public Node() {
                this(null, null);
            }
    
            //方便打印测试
            @Override
            public String toString() {
                return e.toString();
            }
        }
    
        private Node dummyHead;
        private int size;
    
        //构造函数
        public LinkedList() {
            dummyHead = new Node(null, null);  //虚拟头节点
            size = 0;
        }
    
        //获取链表中的元素个数
        public int getSize() {
            return size;
        }
    
        //判断链表是否为空
        public boolean isEmpty() {
            return size == 0;
        }
    
        //在链表的index(0-based)位置添加新的元素e
        //在链表中不是一个常用操作
        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;
            }
    
            prev.next = new Node(e, prev.next);
            size++;
        }
    
        //在链表头添加新元素e
        public void addFirst(E e) {
            add(0, e);
        }
    
        //在链表末尾添加新的元素e
        public void addLast(E e) {
            add(size, e);
        }
    
        //获取链表的index(0-based)位置的元素
        //在链表中也不是一个常用操作
        public E get(int index) {
            if (index < 0 || index > size - 1) {
                throw new IllegalArgumentException("Get failed. Illegal index.");
            }
    
            Node cur = dummyHead.next;
            for (int i = 0; i < index; i++) {
                cur = cur.next;
            }
            return cur.e;
        }
    
        //获得链表的第一个元素
        public E getFirst() {
            return get(0);
        }
    
        //获得链表的最后一个元素
        public E getLast() {
            return get(size - 1);
        }
    
        //修改链表的index(0-based)位置的元素为e
        //在链表中也不是一个常用操作
        public void set(int index, E e) {
            if (index < 0 || index > size - 1) {
                throw new IllegalArgumentException("Set failed. Illegal index.");
            }
    
            Node cur = dummyHead.next;
            for (int i = 0; i < index; i++) {
                cur = cur.next;
            }
            cur.e = e;
        }
    
        //查找链表中是否存在元素e
        public boolean contains(E e) {
            Node cur = dummyHead.next;
            while (cur != null) {
                if (cur.e.equals(e)) {
                    return true;
                }
                cur = cur.next;
            }
            return false;
        }
    
        //删除链表的index(0-based)位置的元素,并返回该元素
        //在链表中也不是一个常用操作
        public E remove(int index) {
            if (index < 0 || index > size - 1) {
                throw new IllegalArgumentException("Remove 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;
        }
    
        //删除链表中的第一个元素,并返回该元素
        public E removeFirst() {
            return remove(0);
        }
    
        //删除链表中的最后一个元素,并返回该元素
        public E removeLast() {
            return remove(size - 1);
        }
    
        //方便打印测试
        @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 class Main {
    
        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);
            }
    
            linkedList.add(2, 666);
            System.out.println(linkedList);
    
            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
4.. 链表的时间复杂度分析:
  • 添加操作 O(n)
  • addLast(e)     O(n)
    addFirst(e)    O(1)
    add(index, e)  O(n/2)=O(n)
  • 删除操作 O(n)
  • removeLase(e)     O(n)
    removeFirst(e)    O(1)
    remove(index, e)  O(n/2)=O(n)
  • 修改操作 O(n)
  • set(index, e)  O(n)
  • 查找操作 O(n)
  • get(index)   O(n)
    contains(e)  O(n)
  • 总体来说,链表的增、删、改、查的时间复杂度都是O(n)
  • 如果我们只对链表的头进行添加和删除操作,那么时间复杂度是O(1),如果我们只查链表头的元素,那么时间复杂度也是O(1),满足这些条件的数据结构,我们很容易就会想到"栈",对于"栈"而言,遵循后进先出的原则,只对栈的一端,也就是"栈顶"进行操作,无论是添加、删除还是查看元素,都在栈顶进行。所以,我们就可以把链表头当作栈顶,用链表来作为栈的底层实现,来实现出一个栈。
5.. 使用链表来实现一个"栈"
  • 链表栈的实现及测试的业务逻辑如下:
  • public class LinkedListStack<E> implements Stack<E> {
    
        private LinkedList<E> list;
    
        //构造函数
        public LinkedListStack() {
            list = new LinkedList<>();
        }
    
        //实现getSize方法
        @Override
        public int getSize() {
            return list.getSize();
        }
    
        //实现isEmpty方法
        @Override
        public boolean isEmpty() {
            return list.isEmpty();
        }
    
        //实现push方法
        @Override
        public void push(E e) {
            list.addFirst(e);
        }
    
        //实现pop方法
        @Override
        public E pop() {
            return list.removeFirst();
        }
    
        //实现peek方法
        @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<>();
            //测试入栈push
            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
6.. 数组栈与链表栈的性能比较
  • 测试的业务逻辑如下:
  • import java.util.Random;
    
    public class Main {
    
        //测试使用stack运行opCount个push和pop操作所需的时间,单位:秒
        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 = 10000;
    
            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会在扩容和缩容操作上面耗费时间,LinkedListStack则会在创建新的Node上面耗费时间
        }
    }
  • 这两种栈的时间复杂度基本处于相同的水平
7.. 使用链表实现一个"队列"
  • 链表队列的实现及测试的业务逻辑如下
  • public class LinkListQueue<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 LinkListQueue() {
            head = null;
            tail = null;
            size = 0;
        }
    
        //实现getSize
        @Override
        public int getSize() {
            return size;
        }
    
        //实现isEmpty
        @Override
        public boolean isEmpty() {
            return size == 0;
        }
    
        //实现enqueue
        @Override
        public void enqueue(E e) {
            if (tail == null) {
                tail = new Node(e);
                head = tail;
            } else {
                tail.next = new Node(e);
                tail = tail.next;
            }
            size++;
        }
    
        //实现dequeue
        @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;
        }
    
        //实现getFront
        public E getFront() {
            if (isEmpty()) {
                throw new IllegalArgumentException("Queue is empty.");
            }
            return head.e;
        }
    
        //方便打印测试
        public String toString() {
            StringBuilder res = new StringBuilder();
            res.append("Queue: front ");
    
            Node cur = head;
            while (cur != null) {
                res.append(cur + "->");
                cur = cur.next;
            }
            res.append("NULL");
            return res.toString();
        }
    
        //测试
        public static void main(String[] args) {
            LinkListQueue<Integer> queue = new LinkListQueue<>();
            for (int i = 0; i < 6; i++) {
                queue.enqueue(i);
                System.out.println(queue);
    
                if (i % 3 == 2) {
                    queue.dequeue();
                    System.out.println(queue);
                }
            }
        }
    }
  • 输出结果:
  • Queue: front 0->NULL
    Queue: front 0->1->NULL
    Queue: front 0->1->2->NULL
    Queue: front 1->2->NULL
    Queue: front 1->2->3->NULL
    Queue: front 1->2->3->4->NULL
    Queue: front 1->2->3->4->5->NULL
    Queue: front 2->3->4->5->NULL
8.. 数组队列、循环队列和链表队列的性能比较
  • 测试的业务逻辑如下:
  • import java.util.Random;
    
    public class Main {
    
        // 测试使用q运行opCount个enqueue和dequeue操作所需要的时间,单位:秒
        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");
    
            LinkListQueue<Integer> linkListQueue = new LinkListQueue<>();
            double time3 = testQueue(linkListQueue, opCount);
            System.out.println("LinkListQueue, time: " + time3 + " s");
    
        }
    }
  • 输出结果:
  • ArrayQueue, time: 3.069366801 s
    LoopQueue, time: 0.010702659 s
    LinkListQueue, time: 0.007079073 s

猜你喜欢

转载自www.cnblogs.com/xuezou/p/9280470.html