数据结构之栈和队列

目录

一、栈

1.1栈的概念

 1.2栈的使用

1.3栈的模拟实现

1.栈的创建

2.入栈

3.出栈

4.查看栈顶元素

5.获取栈的大小

测试

1.4共享栈

1.共享栈的实现

1.1共享栈的创建

1.2入栈

1.3出栈

1.4共享栈基本操作完整代码

二、队列

2.1概念

2.2队列的使用

2.3队列的模拟实现

1.假溢出

2.循环队列

2.1实现思路

2.2循环队列实现

1.队列的创建

2.判断是否为空&&判断是否为满

3.入队操作

4.出队操作

5.查看队头元素&&查看队尾元素

6.循环队列完整代码

2.4双端队列(Deque)


一、栈

1.1栈的概念

栈:是一种特殊的线性表,只允许在固定的一端进行插入和删除元素。

栈顶:进行数据的插入和删除的一端,另一端则为栈底。

压栈:栈的插入操作叫做进栈/入栈/压栈。压栈操作在栈顶进行。

出栈:栈的删除操作叫做出栈。出栈操作也是在栈顶进行。

 1.2栈的使用

 public static void main(String[] args) {
        Stack<Integer> stack=new Stack<>();
        stack.push(1);//入栈
        stack.push(2);
        stack.push(3);
        System.out.println(stack.peek());//喵一眼栈顶
        System.out.println(stack.empty());//判断栈是否为空
        System.out.println(stack.size());//栈大小
        while(!stack.empty()){
            System.out.println(stack.pop());//出栈
        }
      
    }

1.3栈的模拟实现

从图中我们可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。

1.栈的创建

这里我们用动态顺序表实现。


class MyStack{
    private int[] elem;//存储元素的数组
    private int capacity;//容量
    private int size;//使用的元素个数
    public MyStack(){
        this.elem=new int[10];
        this.capacity=10;
        this.size=0;
    }

    public void push(int val){

    }

}

2.入栈

使用顺序表时,要先判断栈内满了没,满了要进行扩容,再进行入栈

 public void push(int val){
        if(isFull()){//满了,进行扩容
            elem= Arrays.copyOf(elem,capacity*2);
              this.capacity*=2;
        }
        //进行入栈操作
        this.elem[this.size++]=val;
    }
    public boolean isFull(){
        return this.size==this.capacity;
    }

3.出栈

在进行出栈时,我们要判断栈是否为空,为空返回-1,否则进行出栈操作

 public int pop(){
        if(isEmpty()){
            return -1;
        }
        //进行出栈操作,弹出栈顶元素
        return this.elem[--this.size];
    }

    public boolean isEmpty(){
        return this.size==0;
    }

4.查看栈顶元素

 public int peek(){
        if(isEmpty()){
            return -1;
        }
        return this.elem[this.size-1];
    }

5.获取栈的大小

public int size(){
        return this.size;
    }

测试

public class StackAL {
    public static void main(String[] args) {
        MyStack myStack=new MyStack();
        myStack.push(1);
        myStack.push(2);
        myStack.push(3);
        System.out.println(myStack.size());
        System.out.println(myStack.peek());
        while(!myStack.isEmpty()){
            System.out.print(myStack.pop()+" ");
        }

    }


1.4共享栈

共享栈主要是利用栈底位置不变,可以利用一个一维数组来存放两个顺序栈 

 这两个栈的栈顶向着一维数组内部,当top0=-1时0号栈为空,当too1=MaxSize时1号栈为空。当top0+1=top1时,判断栈为满。

1.共享栈的实现

1.1共享栈的创建
class ShareStacks{
   private int[] elem;
   private int top0;
   private int top1;
    public ShareStacks(int size){
        elem=new int[size];
        this.top0=-1;
        this.top1=size;
    }
}
1.2入栈

我们借助index判断是要进哪一个栈,在入栈时还需要判断top0+1==top1,如果相等,则栈满。

 public void push(int val,int index){
        if(top0+1==top1){
            System.out.println("栈已满");
            return;
        }
        if(index==0){//在栈0中压入元素
            elem[++top0]=val;
        }
        if(index==1){//在栈1中压入元素
            elem[--top1]=val;
        }
        if(index<0||index>1){//栈索引输入错误
            System.out.println("index输入错误");
        }
    }
1.3出栈

我们在出栈的过程中,我们需要判断栈内是否为空,不为空才能进行出栈操作。

这里我们把判断栈内是否为空封装成一个方法isempty()。

 public boolean isempty(int index){
        if(index==0){
            if(top0==-1){
                return true;
            }else{
                return false;
            }
        }
        if(index==1){
            if(top1==elem.length){
                return true;
            }else{
                return false;
            }
        }
        return false;
    }

/**
     * 从指定栈中弹出元素。
     * @param index 栈的索引,0表示栈0,1表示栈1。
     * @return 弹出的元素值。如果栈为空,则不返回任何值。
     */
    public int pop(int index){
        int val=0;
        // 尝试从栈0中弹出元素
        if(index==0){
            // 如果栈0为空,则打印信息并返回
            if(isempty(index)){
                System.out.println("栈0为空");
                return 0;
            }else{
                // 从栈0弹出元素并返回
               val=elem[top0--];
            }
        }
        // 尝试从栈1中弹出元素
        if(index==1){
            // 如果栈1为空,则打印信息并返回
            if(isempty(index)){
                System.out.println("栈1为空");
                return 0;
            }else{
                // 从栈1弹出元素并返回
                val=elem[top1++];
            }
        }
        // 如果索引既不是0也不是1,返回0作为错误指示
        return val;
    }

其余操作与栈基本相同。 

1.4共享栈基本操作完整代码

class ShareStacks{
   private int[] elem;
   private int top0;
   private int top1;
    public ShareStacks(int size){
        elem=new int[size];
        this.top0=-1;
        this.top1=size;
    }
    public void push(int val,int index){
        if(top0+1==top1){
            System.out.println("栈已满");
            return;
        }
        if(index==0){//在栈0中压入元素
            elem[++top0]=val;
        }
        if(index==1){//在栈1中压入元素
            elem[--top1]=val;
        }
        if(index<0||index>1){//栈索引输入错误
            System.out.println("index输入错误");
        }
    }
    /**
     * 从指定栈中弹出元素。
     * @param index 栈的索引,0表示栈0,1表示栈1。
     * @return 弹出的元素值。如果栈为空,则不返回任何值。
     */
    public int pop(int index){
        int val=0;
        // 尝试从栈0中弹出元素
        if(index==0){
            // 如果栈0为空,则打印信息并返回
            if(isempty(index)){
                System.out.println("栈0为空");
                return 0;
            }else{
                // 从栈0弹出元素并返回
               val=elem[top0--];
            }
        }
        // 尝试从栈1中弹出元素
        if(index==1){
            // 如果栈1为空,则打印信息并返回
            if(isempty(index)){
                System.out.println("栈1为空");
                return 0;
            }else{
                // 从栈1弹出元素并返回
                val=elem[top1++];
            }
        }
        // 如果索引既不是0也不是1,返回0作为错误指示
        return val;
    }

    public int peek(int index){
        if(index<0||index>1){
            throw new IllegalArgumentException("index输入错误");
        }
        if(index==0){
            if(isempty(index)){
                System.out.println("栈0为空");
                return 0;
            }else{
                return elem[top0];
            }
        }
        if(index==1){
            if(isempty(index)){
                System.out.println("栈1为空");
                return 0;
            }else{
                return elem[top1];
            }
        }
        return 0;
    }
    public boolean isempty(int index){
        if(index==0){
            if(top0==-1){
                return true;
            }else{
                return false;
            }
        }
        if(index==1){
            if(top1==elem.length){
                return true;
            }else{
                return false;
            }
        }
        return false;
    }
}

二、队列

2.1概念

队列:只允许在一端进行插入,在另一端进行删除数据操作的特殊线性表,队列是先进先出,进行插入操作的一端叫做队尾,进行删除操作的一端叫做队头。

2.2队列的使用

在java中,Queue是个接口,底层是通过链表来实现的。

队列中常用的方法

 使用:由于Queue底层是双向链表LinkList,LinkList实现了Queue接口,所以在实例化时必须实例化LinkList的对象。下面是对java中Queue的使用

 public static void main(String[] args) {
        Queue<Integer> queue=new LinkedList<>();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.size());

        while(!queue.isEmpty()){
            System.out.print(queue.poll()+" ");
        }
    }

2.3队列的模拟实现

队列的实现可以用顺序结构,也可以使用链式结构 

       队列的顺序存储是指分配一块空间给队列存储元素,并设置两个成员变量,一个front指向队头,一个rear指向队尾。

1.假溢出

对于顺序队列,我们在使用的时候,可能会遇到假溢出的情况

举个例子:我们一个长度为5的顺序队列,在这个队列中已经存储了3个元素(4,6,8)。

我们现在进行出队,出队两个元素,那么front就要往前走两步。

 我们再进行入队操作,这次我们入队3个元素。

 我们可以看到此时rear已经走到了数组外了,我们已经不能再进行入队操作了,但队列中的空间还没满,如果再进行入队操作就会溢出,这就叫做“假溢出”。

为了避免这种情况,我们可以使用循环队列。

2.循环队列

头尾相接的队列,能够避免假溢出的问题

2.1实现思路

对于循环队列,我们依旧采用的是一维数组来进行存储队列的元素。

1.使用两个指针:

           头指针(front):作为队头,指向队列的第一个元素。

           尾指针(rear):作为队尾,指针队列的最后一个元素之后的位置。

2.判断为空:即front==rear时,队列为空。

3.判断是否为满:这里我们不能再用简单的判断两个指针是否相等,头尾指针都有可能回到数组的初始位置。我们这里牺牲一个元素空间用来判断队列是否为满,即(rear+1)%size==front,其中size为数组的长度

2.2循环队列实现
1.队列的创建

这里我们使用是顺序存储。

class MyQueue{
    private int[] elem;
    private int front;//队头
    private int rear;//队尾

    public MyQueue(int size){
        elem=new int[size];
    }
}
2.判断是否为空&&判断是否为满
 public boolean isEmpty(){
        return rear==front;
    }
    public boolean isFull(){
        return (rear+1)%elem.length==front;
    }
3.入队操作

在入队的时候,我们需要判断一下队列是否已经满了(这里不进行扩容操作)。

 public boolean offer(int val){
        if(isFull()){
            return false;
        }
        //进行入队操作
        elem[rear]=val;
        rear=(rear+1)%elem.length;
        return true;
    }
4.出队操作

对于front,不可以直接进行+1操作,当front在数组的最后一个位置时,我们想让front回到数组初始位置,我们需要让头指针(front+1)%elem.length

 public int poll(){
        if(isEmpty()){
            return -1;
        }
        //进行出队操作
        int val=elem[front];
        front=(front+1)%elem.length;
        return val;
    }
5.查看队头元素&&查看队尾元素

查看队头元素,我们只需要返回front所在位置的元素即可。

对于查看队尾元素,我们可能会返回elem[rear-1]位置的元素,但这样有缺陷,若rear所处位置为0,那么减1就有问题了。可以定义一个变量,用来判断rear所处位置。如果rear=0,那么就返回elem.length-1位置处的元素,否则直接返回rear-1处的元素。

 public int peek(){//查看队头元素
        if(isEmpty()){
            return -1;
        }
        return elem[front];
    }
    
    public int peekRear(){//查看队尾元素
        if(isEmpty()){
            return -1;
        }
        int index=(rear==0)? elem.length-1:rear-1;
        return elem[index];
    }

实现了这些功能,我们可以测试一下

public static void main(String[] args) {
        MyQueue myQueue=new MyQueue(3);
        myQueue.offer(1);//入队
        myQueue.offer(2);
        System.out.println(myQueue.peek());//查看队头元素
        System.out.println(myQueue.peekRear());//查看队尾元素
        System.out.println(myQueue.poll());//出队
        System.out.println(myQueue.poll());
    }

6.循环队列完整代码
class MyQueue{
    private int[] elem;
    private int front;//队头
    private int rear;//队尾

    public MyQueue(int size){
        elem=new int[size];
    }
    public boolean offer(int val){
        if(isFull()){
            return false;
        }
        //进行入队操作
        elem[rear]=val;
        rear=(rear+1)%elem.length;
        return true;
    }

    public int poll(){
        if(isEmpty()){
            return -1;
        }
        //进行出队操作
        int val=elem[front];
        front=(front+1)%elem.length;
        return val;
    }

    public int peek(){//查看队头元素
        if(isEmpty()){
            return -1;
        }
        return elem[front];
    }

    public int peekRear(){//查看队尾元素
        if(isEmpty()){
            return -1;
        }
        int index=(rear==0)? elem.length-1:rear-1;
        return elem[index];
    }


    public boolean isEmpty(){
        return rear==front;
    }
    public boolean isFull(){
        return (rear+1)%elem.length==front;
    }
}

2.4双端队列(Deque)

双端队列是允许在两端进行入队和出队操作。

Deque也是一个接口,在使用的时候需要创建LinkList的对象。

 我们可以在Deque (Java SE 11 & JDK 11 ) (runoob.com)中查看Deque的常见操作。

 栈和队列均可以使用该接口。

  public static void main(String[] args) {
        Deque<Integer> deque = new LinkedList<>();
        deque.offer(1);
        deque.offer(2);
        deque.offer(3);// 1 2 3
        System.out.println(deque.peekLast());//查看队尾元素
        System.out.println(deque.peekFirst());//查看队首元素
        System.out.println(deque.pollLast());//弹出队尾元素
        System.out.println(deque.pollFirst());//弹出队首元素
    }

相关习题

1. 用队列实现栈225. 用队列实现栈 - 力扣(LeetCode)

class MyQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;
    public MyQueue() {
        stack1=new Stack<>();
        stack2=new Stack<>();
    }

    public void push(int x) {
        stack1.push(x);
    }

    public int pop() {
        if(empty()){
            return -1;
        }
        if(stack2.empty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }

    public int peek() {
        if(empty()){
            return -1;
        }
     if(stack2.empty()){
         while(!stack1.empty()){
             stack2.push(stack1.pop());
         }
     }
     return stack2.peek();
    }

    public boolean empty() {
        return stack1.empty()&&stack2.empty();
    }
}

2. 用栈实现队列232. 用栈实现队列 - 力扣(LeetCode)

/**
 * 用队列实现栈
 */
class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;
    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    public void push(int x) {
        //判断栈内为不为空
        if(empty()){
            //入队到第一个
            queue1.offer(x);
        }
        //不为空,判断哪个队列不为空进哪个
        if(!queue1.isEmpty()){
            queue1.offer(x);
        }else{
            queue2.offer(x);
        }

    }

    public int pop() {
        //判断哪个队列不为空
        if(!queue1.isEmpty()){
            int size=queue1.size();
            //让size-1个元素放到第二个队列中
            for(int i=0;i<size-1;i++){
                queue2.offer(queue1.poll());
            }
            return queue1.poll();
        }else{
            int size=queue2.size();
            for(int i=0;i<size-1;i++){
                queue1.offer(queue2.poll());
            }
            return queue2.poll();
        }
    }

    public int top() {
        if(!queue1.isEmpty()){
            int size=queue1.size();
            int res=-1;
            for(int i=0;i<size;i++){
                res =queue1.poll();
                queue2.offer(res);
            }
            return res;
        }else{
            int size=queue2.size();
            int res=-1;
            for(int i=0;i<size;i++){
                res=queue2.poll();
                queue1.offer(res);
            }
            return res;
        }
    }

    public boolean empty() {
        return queue1.isEmpty()&&queue2.isEmpty();
    }
}

若有不足,欢迎指正~

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhyhgx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值