栈Stack 和 队列Queue 的实现 ( 用顺序表链表实现 )

栈:后进先出
队列:先进先出

更复杂的队列:
1.优先队列
2.消息队列(队列中的元素带有类型,出队列的时候可以按照类型来取元素)
3.阻塞队列。如果队列满,此时继续插入元素,就会阻塞 如果队列空,此时继续取元素,也会阻塞
4.无锁队列,更高效的线程安全队列, 保证线程安全的常见手段,加锁,但是加锁效率比较低(CAS),加锁速度慢但是节省 CPU资源,CAS速度快,但是更消耗 CPU。

1 栈的实现:

核心操作是 入栈、出栈、取栈顶元素

1.1 顺序表实现栈:

  使用尾插操作表示 “入栈”,使用尾删操作表示 “出栈”,使用根据下标获取元素的操作表示 “取栈顶元素”(头插头删,需要搬运之后的元素,时间复杂度会变高,变复杂);

public class MyStack_ArrayList {
    private int[] data = new int[100];
    private int size = 0;

    // 1、入栈
    public void push (int val) {
        if (size > data.length) {
            return;
        }
        data[size] = val;
        size ++;
    }

    // 2、出栈
    public Integer pop () {
        if (size == 0) {
            return null;
        }
        int ret = data[size - 1];
        size --;
        return ret;
    }

    // 3、取栈顶元素
    public Integer peek() {
        if (size == 0) {
            return null;
        }
        return data[size - 1];
    }

}

1.2 链表实现栈:

  使用头插操作表示 “入栈”,使用头删操作表示 “出栈”,直接取头节点,就是 “取栈顶元素”(尾插尾删可以做到高效的实现,但是得记录下最后一个甚至倒数第二个元素的信息,代码更复杂);

class ListNode {
    int val;
    ListNode next;

    public ListNode(int val) {
        this.val = val;
    }
}
public class MyStack2_LinkedList {
    private ListNode head = null;
    // 1、入栈
    public void push(int val) {
        ListNode newNode = new ListNode(val);
        // 不带傀儡节点,就需要判断链表是否空
        if (head == null) {
            head = newNode;
            return ;
        }
        newNode.next = head;
        head = newNode;
    }

    // 2、出栈
    public Integer pop () {
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            int ret = head.val;
            head = null;
            return ret;
        }
        int ret = head.val;
        head = head.next;
        return ret;
    }

    // 3、取栈顶元素
    public Integer peek(){
        if (head == null) {
            return null;
        }
        return head.val;
    }

}

2 队列的实现:

核心操作 出队列、入队列、取队首元素

2.1 顺序表实现队列:

  Java标准库中的 Queue 对应的实现只有 LinkedList 一种选择,没有环形队列。
  可以使用特殊的方式实现,用两个引用分别指着队的首部和尾部,有效数据的区间为 [ head , tail ),“入队列” 就把新的元素放到 tail 对应的下标上,同时 tail ++,“出队列” 就是把原来 head 指向的元素排除到有效区间之外,把 head ++(时间复杂度都为 O(1) )。
环形队列:
  相比较于链表版本的队列,这个队列更快,但是空间是固定大小的,扩容成本很高。
在这里插入图片描述
(1) 队列为空,head 和 tail 重合;
ZW5naGVpdGk,shadow_10,text_aHR0cHM6L
(2)元素入队列,注意 tail 指向最后一个元素的后一个位置;
在这里插入图片描述
(3)接着元素入队列;
在这里插入图片描述
(4)队列满了,head 和 tail 重合;
在这里插入图片描述
  这时会发现,队列满和队列空都是 head 和 tail 重合,我们就需要区分,什么时候是满了,什么时候是空。

方法一:
  不把环形队列压榨的太干净,故意浪费一个空间,用 head 和 tail 重合表示空队列,用 tail == head - 1;表示满队列。
在这里插入图片描述
方法二:
  通过多使用一个变量 size 来记录队列的元素个数,“size 等于 0” 就是空,“size == 数组长度” 就是满。

// 这里我们使用第二个方法来实现代码
// 用 size 维护代码为空还是为满
public class MyQueue2_ArrayList {
    private int[] data = new int[100];
    private int head = 0;
    private int tail = 0;
    int size = 0;

    // 1、入队列
    public boolean offer(int val) {
        if (size == data.length) {
            return false;
        }
        data[tail] = val;
        tail ++;
        if (tail == data.length) {
            tail = 0;
        }
        size ++;
        return true;
    }

    // 2、出队列
    public Integer poll() {
        if (size == 0) {
            return null;
        }
        int ret = data[head];
        head ++;
        if (head == data.length) {
            head = 0;
        }
        size --;
        return ret;
    }

    // 3、取队首元素
    public Integer peek() {
        if (size == 0) {
            return null;
        }
        return data[head];
    }
}

2.2 链表实现队列:

  使用尾插表示 “入队列”,使用头删表示 “出队列”,直接取到头结点表示 “取队首元素”;

package MyStackAndQueue;

public class MyQueue_LinkedList {
    static class ListNode {
        int val;
        ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }

    private ListNode head = null;
    private ListNode tail = null;

    // 1、入队列
    public boolean offer(int val) {
        ListNode newNode = new ListNode(val);
        if (head == null) {
            head = newNode;
            tail = newNode;
            return true;
        }
        tail.next = newNode;
        tail = tail.next;
        return true;
    }

    // 2、出队列
    public Integer poll() {
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            int ret = head.val;
            head = null;
            return ret;
        }
        int ret = head.val;
        head = head.next;
        return ret;
    }

    // 3、取队首元素
    public Integer peek() {
        if (head == null) {
            return null;
        }
        return head.val;
    }

}

3 标准库中的 Stack / Queue

  在标准库中,Stack 是一个类,继承自 Vector,Vector 和 顺序表、链表是并列关系,它们都实现了 List 接口;Queue 是一个接口,不能直接实例化,而需要创建对应的子类,实际使用的是 LinkedList 。
  其中 Vector 现在使用的并不多,Vector 也是顺序表,和 ArrayList 很相似,它们的区别是:
(1) Vector 是线程安全的,ArrayList 是线程不安全的;
(2) Vector 扩容时,在典型的实现中,新的 capacity 是原来的 2 倍,而 ArrayList 是原来的 1.5 倍;
(3)Vector 出现时间较为久远,而 ArrayList 是之后才出现的。
  还要注意,Stack 只有一套方法 push、pop 、peek 来完成核心的三个操作,而 Queue 有两套方法,一套方法返特殊值:offer、poll、peek;一套方法抛出异常:add、remove、element。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值