数据结构【六】- 链表实现栈和队列

一. 使用链表实现栈

1. 原理

    需求:由于栈是一个后入先出的结构,它只对栈的一段操作。无论是增加元素,删除元素,查看元素,都在栈顶进行。所以对于链表来说,我们的在添加和删除链表头的时间复杂度是O(1)。所以我们这里将链表头作为栈顶。

    解决:

       (1)利用我们在数据结构【二】-栈】中的栈的接口,来使用链表实现栈

public interface Stack<E> {
   int getSize();
   boolean isEmpty();
   void push(E e);
   E pop();
   E peek();
}

              

 

      (2)创立一个新的类LinkedListStack,实现基本的方法

2. 代码

这里的代码LinkedList,请参考上一节的博客的链表代码。

public class LinkedListStack<E> implements Stack<E> {
    private LinkedList<E> list;
    public LinkedListStack(){
        list = new LinkedList<E>();
    }
    @Override
    public int getSize() {
        return list.getSize();
    }
    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }
    @Override
    public void push(E e) {
        list.addFist(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<Integer>();
        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

3. 链表实现栈和动态数组实现栈的时间差异

就一个操作分析:比如push()操作

1. 对于链表实现栈:在所操作的时候,我们每次都要new一个Node,这个过程也会耗时。因为它会不停的在内存中寻找新可以开辟空间的地方开辟空间。通常push()操作越多,new操作的耗时就越明显。

2. 对于动态数组实现栈:时不时就要一个新的数组,将原来数组中的元素拷贝到新的数组中(扩容)。这个过程相对耗时。

3. 其实ArrayStack和LinkedListStack,都是同一复杂度的。所以不同情况下,二者的耗时由差异是正常情况。不存在复杂度的巨大差异。我这边测试当push()操作执行200000次的时候,LinkedListStack所需的时间短。当push()操作执行2000000次的时候,ArrayStack所需的时间短。

二.带有尾指针的链表:使用链表实现队列

1. 原理

    需求:用链表的结构来实现队列【队列:先进先出的结构】

    解决:

       (1)由于队列是先进先出的结构,需要在队列头删除元素,在对列尾加入元素。但是对于链表来说,在链表头添加和删除元素都是O(1)的复杂度,但是在链表尾添加和删除元素都是O(n)的复杂度,所以为了使得用链表实现的队列的复杂度降低,我们在链表尾部在设立一个Node节点,命名为tail。对于tail来说,在tail之后添加元素很容易,但是在删除tail位置的元素的复杂度是O(n)。所以我们应该在链表头来删除元素,在链表尾部来添加元素。所以我们将链表头作为队首,将链表尾部作为队尾。

       (2)在这里不使用虚拟头节点了,是因为这里不牵扯到对链表中的操作。所以没必要统一对链表中间元素进行的操作和链表两边元素的操作他们之间带来的逻辑不统一的情况。

       (3)当链表为空的时候,head和tail也是null。由于没有dummyHead,要注意链表为空的情况。

2. 代码

public interface Queue<E> {
    void enQueue(E e);
    E deQueue();
    E getFront();
    int getSize();
    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);
        }
        @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) {
        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 empty queue.");
        }
        Node delNode = head;
        head = head.next;//head指向了原来头节点的下一个节点
        delNode.next = null;//将delNode从链表中断开
        if(head == null){
            tail = null;
        }
        size--;
        return delNode.e;
    }
    @Override
    public E getFront() {
        if(isEmpty()){
            throw new IllegalArgumentException("Queue is empty.");
        }
        return head.e;
    }
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: front ");
        Node curr = head;
        while(curr != null){
            res.append(curr+"->");
            curr = curr.next;
        }
        res.append("NULL tail");
        return res.toString();
    }
}

3. 链表实现栈和动态数组实现栈的时间差异

就一个操作分析:比如push()操作

1. 对于用数组实现队列:由于它的时间复杂度是O(n^2)的,所以它的速度是最慢的。

2. 对于用循环数组来实现队列:它的时间复杂度是O(n)的

3. 对于用链表实现来实现队列:它的时间复杂度是O(n)的。所以和用循环数组实现队列所用的时间差不多。

 

三.使用链表实现队列【在有dummyHead的情况下】

1. 需求

    用链表的结构来实现队列【队列:先进先出的结构】。上一节用的是没有DummyHead的情况下的代码,这里实现的是有dummyHead节点的代码。

参考:数据结构【五】- 链表【链表的介绍和实现:增删改查】

2.代码

package Queue;

public interface Queue<E> {
    void enQueue(E e);
    E deQueue();
    E getFront();
    int getSize();
    boolean isEmpty();
}
package LinkedList;

import Queue.Queue;

public class LinkedListQueueDummy<E> implements Queue<E> {
//1. 使用内部类来表示节点
    public class Node{
        E val;
        Node next;

        public Node(E val, Node next){
            this.val = val;
            this.next = next;
        }
        public Node(E val){this(val,null);}
        public Node() {
            this(null, null);
        }
        @Override
        public String toString(){
            return val.toString();
        }
    }
//2.构建节点
    //设置虚拟头节点
    private Node dummyHead;
    //设置尾节点
    private Node tail;
    //链表的大小
    private int size;
//3. 初始化链表
    public LinkedListQueueDummy(){
        dummyHead = new Node(null,null);//初始化dummyHead
        tail = null;
        size =0;
    }
//4. getSize()方法
    @Override
    public int getSize() {
        return size;
    }
//5. isEmpty()方法
    @Override
    public boolean isEmpty() {
        return size==0;
    }
//6.添加数据
    @Override
    public void enQueue(E e) {
        if(isEmpty()){
            tail =  new Node(e);
            dummyHead.next = tail;
        }else{
            tail.next = new Node(e);
            tail = tail.next;
        }
        size++;
    }
//7.删除节点
    @Override
    public E deQueue() {
        if(isEmpty()){
            throw new IllegalArgumentException("Cannot dequeue from empty queue.");
        }
        Node prev = dummyHead;
        Node delNode = prev.next;
        prev.next = delNode.next;
        delNode.next = null;
        if( prev.next == null){
            tail = null;
        }
        size--;
        return delNode.val;
    }

    @Override
    public E getFront() {
        if(isEmpty()){
            throw new IllegalArgumentException("Queue is empty.");
        }
        return dummyHead.next.val;
    }
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: front ");
        LinkedListQueueDummy.Node curr = dummyHead.next;
        while(curr != null){
            res.append(curr+"->");
            curr = curr.next;
        }
        res.append("NULL tail");
        return res.toString();
    }
}

3.测试

    public static void main(String[] args){
        LinkedListQueueDummy<Integer> linkedListQueue = new LinkedListQueueDummy<Integer>();
        linkedListQueue.enQueue(8);
        linkedListQueue.enQueue(9);
        System.err.println(linkedListQueue);
        System.err.println(linkedListQueue.deQueue());
        System.err.println(linkedListQueue);
        linkedListQueue.enQueue(10);
        System.err.println(linkedListQueue);
    }

Queue: front 8->9->NULL tail
8
Queue: front 9->NULL tail
Queue: front 9->10->NULL tail

                                    以上所有内容都是通过"慕课网"听"liuyubobobo"的《玩转数据结构》课程后总结


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值