数据结构(Java)--栈和队列

目录

一、栈(Stack)

1.栈的常用方法

2.栈的使用

3.栈的模拟实现

4.栈的应用场景

二、队列(Queue)

1.队列的常用方法

2.队列的使用

3.队列的模拟实现

4.循环队列

 5.双端队列

三、练习题

一、栈(Stack)

栈(Stack)是一种后进先出(Last In First Out, LIFO)的数据结构。在栈中,最后添加的元素最先被访问和移除。栈的操作包括压入(Push)元素,将元素添加到栈的顶部,和弹出(Pop)元素,从栈的顶部移除元素。栈的示例包括浏览器的后退按钮、函数调用的执行和撤销操作。

1.栈的常用方法
方法
功能
Stack()
构造一个空的栈
E push(E e)
e 入栈,并返回 e
E pop()
将栈顶元素出栈并返回
E peek()
获取栈顶元素
int size()
获取栈中有效元素个数
boolean empty()
检测栈是否为空
2.栈的使用
import java.util.*;

public class StackExample {
    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());

        // 出栈操作
        int item = stack.pop();
        System.out.println("出栈元素: " + item);

        // 判断栈是否为空
        System.out.println("栈是否为空: " + stack.isEmpty());
    }
}
 

运行结果:

栈顶元素: 3
出栈元素: 3
栈是否为空: false
 

3.栈的模拟实现

用数组实现(入栈、出栈时间复杂度为O(1)):

MyStack:

import java.util.Arrays;

public class MyStack {
    public int[] elem;
    public int usedSize;

    public MyStack() {
        this.elem = new int[10];
    }

    public void push(int val) {
        if(isFull()) {
            this.elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
    }

    public boolean isFull() {
        return usedSize == elem.length;
    }

    public int pop() {
        if(isEmpty()){
            throw new EmptyStackException();
        }
        int val = elem[usedSize-1];
        usedSize--;
        return val;
    }
    //获取栈顶元素 但是不删除
    public int peek() {
        if(isEmpty()){
            throw new EmptyStackException();
        }
        return elem[usedSize - 1];
    }

    public boolean isEmpty() {
        return usedSize == 0;
    }
}

EmptyStackException:

public class EmptyStackException extends RuntimeException{
    public EmptyStackException(String message) {
        super(message);
    }

    public EmptyStackException() {
    }
}

Test:

public class Test {
    public static void main(String[] args) {
        MyStack stack = new MyStack();
        stack.push(12);
        stack.push(23);
        stack.push(34);
        stack.push(45);
        stack.push(56);

        System.out.println(stack.pop());
        System.out.println(stack.peek());
        System.out.println(stack.isEmpty());
    }
}

运行结果:

56
45
false 

若使用单链表实现:

采用尾插法入栈,入栈时间复杂度为O(n),如果有last,那么是O(1),但是出栈操作一定是O(n)。采用头插法入栈,入栈时间复杂度为O(1),同时出栈复杂度也是O(1)。

若使用双向链表实现:

不管是头插法还是尾插法,时间复杂度都是O(1)。 

4.栈的应用场景

a.将递归转化为循环(以逆序打印链表为例)

   递归方法:

void printList(Node head){
    if(null != head){
        printList(head.next);
        System.out.print(head.val + " ");
    }
}

   循环方法: 

void printList(Node head){
    if(null == head){
        return;
    }
    Stack<Node> s = new Stack<>();
    // 将链表中的结点保存在栈中
    Node cur = head;
    while(null != cur){
        s.push(cur);
        cur = cur.next;
    }
    // 将栈中的元素出栈
    while(!s.empty()){
    System.out.print(s.pop().val + " ");
    }
}

b.括号匹配  答题

思路:

  • 先把左括号入栈(push),若栈为空,直接返回false;
  • 再判断是否匹配(peek & pop);
  • 若栈不为空,返回false。
class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(int i = 0;i < s.length();i++) {
            char ch1 = s.charAt(i);
            if(ch1 == '(' || ch1 == '[' || ch1 == '{'){
                stack.push(ch1);
            }else{
                if(stack.isEmpty()){
                    return false;
                }
                char ch2 = stack.peek();
                if((ch2=='('&&ch1==')')||(ch2=='['&&ch1==']')||(ch2=='{'&&ch1=='}')){
                    stack.pop();
                }else{
                    return false;
                }
            }
        }
        if(!stack.isEmpty()){
            return false;
        }
        return true;
    }
}

c.逆波兰表达式求值  答题 

思路:创建一个栈,依次把数字放进去,遇到运算符时取出,先取出的作右运算数,将所得结果再放回栈中,直到栈为空为止。

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for(String str : tokens){
            if(!isOperations(str)){
                int num = Integer.parseInt(str);
                stack.push(num);
            }else{
                int val2 = stack.pop();
                int val1 = stack.pop();
                switch(str){
                    case "+":
                        stack.push(val1+val2);
                        break;
                    case "-":
                        stack.push(val1-val2);
                        break;
                    case "*":
                        stack.push(val1*val2);
                        break;
                    case "/":
                        stack.push(val1/val2);
                        break;
                }
            }
        }
        return stack.pop();
    }

    private boolean isOperations(String ch){
        if(ch.equals("+")||ch.equals("-")||ch.equals("*")||ch.equals("/")){
            return true;
        }
        return false;
    }
}

d.出栈入栈次序匹配  答题 

思路:遍历pushV数组,每次入栈一个元素之后,用栈顶元素和popV的下标比较,若一样则出栈,若不一样,继续遍历,直到遍历完整个pushV数组。

public boolean IsPopOrder (int[] pushV, int[] popV) {
    Stack<Integer> stack = new Stack<>();
    int j = 0;
    for(int i = 0;i<pushV.length;i++){
        stack.push(pushV[i]);
        while(!stack.isEmpty() && j<popV.length && stack.peek()==popV[j]){
            stack.pop();
            j++;
        }
    }
    return stack.isEmpty();
}

e.最小栈  答题 

class MinStack {

    public Stack<Integer> stack;
    public Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }

    public void push(int val) {
        stack.push(val);
        if(minStack.isEmpty()){
            minStack.push(val);
        }else{
            if(val <= minStack.peek()){
                minStack.push(val);
            }
        }
    }

    public void pop() {
        if(stack.empty()) {
            return;
        }
        int popVal = stack.pop();
        if(popVal == minStack.peek()) {
            minStack.pop();
        }
    }

    public int top() {
        if(stack.isEmpty()){
            return -1;
        }
        return stack.peek();
    }

    public int getMin() {
        if(stack.isEmpty()){
            return -1;
        }
        return minStack.peek();
    }
}

二、队列(Queue)

队列是一种线性数据结构,它遵循先进先出(FIFO)的原则。在队列中,新元素被插入到队尾,而只有队首元素可以被删除。队列通常用于需要按顺序处理元素的场景,比如任务调度、请求处理等。队列的操作包括入队(将元素插入队尾)、出队(将队首元素删除并返回)、获取队首元素(返回队首元素但不删除)等。

1.队列的常用方法
方法
功能
boolean offer(E e)
入队列
E poll()
出队列
peek()
获取队头元素
int size()
获取队列中有效元素个数
boolean isEmpty()
检测队列是否为空
2.队列的使用

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。 

import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();

        // 入队
        queue.add("Alice");
        queue.add("Bob");
        queue.add("Charlie");

        // 出队
        String firstPerson = queue.remove();
        System.out.println("出队的人:" + firstPerson);

        // 获取队首元素
        String peekPerson = queue.peek();
        System.out.println("队首的人:" + peekPerson);

        // 遍历队列
        System.out.println("队列中的人:");
        for (String person : queue) {
            System.out.println(person);
        }
    }
}

 运行结果:

出队的人:Alice
队首的人:Bob
队列中的人:
Bob
Charlie

3.队列的模拟实现

使用双向链表实现(普通数组无法实现):

MyQueue:

package demo;

public class MyQueue {

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

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

    public ListNode head;
    public ListNode last;
    public int size = 0;

    public void offer(int e){
        ListNode node = new ListNode(e);
        if (head == null) {
            head = last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = node;
        }
        size++;
    }

    public int poll() {
        if (head == null) {
            return -1;
        }
        ListNode po = head;
        head = head.next;
        if (head != null) {
            head.prev = null;
        }
        size--;
        return po.val;
    }

    public int peek() {
        if (head == null) {
            return -1;
        }
        return head.val;
    }

    public int size() {
        return size;
    }

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

Test:

package demo;

public class Test {
    public static void main(String[] args) {
        MyQueue queue = new MyQueue();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        queue.offer(4);

        System.out.println(queue.poll());
        System.out.println(queue.peek());
        System.out.println(queue.size());
        System.out.println(queue.isEmpty());
    }
}

运行结果:

1
2
3
false

4.循环队列

循环队列是一种特殊的队列数据结构,它通过使用固定大小的数组来实现。在循环队列中,当队列的尾部达到数组的末尾时,如果队列的头部还有空闲位置,就可以将尾部元素循环移动到数组的起始位置,从而实现循环的效果。

循环队列有一些特点:

  1. 队列的容量是固定的,一旦定义后就无法改变。
  2. 队列有一个头指针和一个尾指针,分别指向队列的头部和尾部。
  3. 当队列为空时,头指针和尾指针都指向同一个位置。
  4. 当队列满时,头指针和尾指针指向的位置相邻。

循环队列的主要操作包括:

  1. 入队(enqueue):将元素添加到队列的尾部。
  2. 出队(dequeue):从队列的头部移除并返回元素。
  3. 判空(isEmpty):判断队列是否为空。
  4. 判满(isFull):判断队列是否已满。
  5. 获取队头元素(getFront):返回队列的头部元素。

设计循环队列:

class MyCircularQueue {

    public int front;
    public int rear;
    public int[] arr;

    public MyCircularQueue(int k) {
        arr = new int[k + 1];
    }
    
    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        arr[rear] = value;
        rear = (rear+1)%arr.length;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        front = (front+1)%arr.length;
        return true;
    }

    
    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return arr[front];
    }
    
    public int Rear() {
        if(isEmpty()){
            return -1;
        }
        int index = (rear == 0)?arr.length-1:rear-1;
        return arr[index];
    }
    
    public boolean isEmpty() {
        return front == rear;
    }
    
    public boolean isFull() {
        return (rear+1)%arr.length == front;
    }
}
 5.双端队列

双端队列(Deque)是一种特殊的队列数据结构,它允许从队列两端进行插入和删除操作。双端队列可以在头部和尾部同时进行入队和出队操作,因此它可以被看作是一种兼具栈和队列性质的数据结构。

双端队列的实现可以使用数组或链表。在使用数组实现时,需要考虑数组的动态扩容和缩容问题;在使用链表实现时,可以直接在头部和尾部进行插入和删除操作。双端队列的选择主要取决于具体的应用场景和需求。

三、练习题

题目:用队列实现栈。  答题

思路:模拟入栈:将数据放入不为空的队列当中;

           模拟出栈:把不为空的队列中的size-1个元素放到另一个队列当中,剩下的这一个就                                 是要出栈的元素。

class MyStack {
    
    public Queue<Integer> qu1;
    public Queue<Integer> qu2;

    public MyStack() {
        qu1 = new LinkedList<>();
        qu2 = new LinkedList<>();
    }

    public void push(int x) {
        if (!qu1.isEmpty()) {
            qu1.offer(x);
        } else if (!qu2.isEmpty()) {
            qu2.offer(x);
        } else {
            qu1.offer(x);
        }
    }

    public int pop() {
        if (empty()) {
            return -1;
        }
        if (!qu1.isEmpty()) {
            int size = qu1.size();
            for (int i = 0; i < size - 1; i++) {
                qu2.offer(qu1.poll());
            }
            return qu1.poll();
        } else {
            int size = qu2.size();
            for (int i = 0; i < size - 1; i++) {
                qu1.offer(qu2.poll());
            }
            return qu2.poll();
        }
    }

    public int top() {
        if (empty()) {
            return -1;
        }
        if (!qu1.isEmpty()) {
            int size = qu1.size();
            int val = 0;
            for (int i = 0; i < size; i++) {
                val = qu1.poll();
                qu2.offer(val);
            }
            return val;
        } else {
            int size = qu2.size();
            int val = 0;
            for (int i = 0; i < size; i++) {
                val = qu2.poll();
                qu1.offer(val);
            }
            return val;
        }
    }

    public boolean empty() {
        return qu1.isEmpty() && qu2.isEmpty();
    }
}

题目:用栈实现队列。  答题 

思路:模拟入队:都放到第一个栈;

           模拟出队:判断第二个栈是不是空的,若是,把第一个栈中所有元素都放到第二个栈                               里,取出第二个栈的栈顶元素,否则,直接取出第二个栈的栈顶元素。

class MyQueue {

    public ArrayDeque<Integer> stack1;
    public ArrayDeque<Integer> stack2;

    public MyQueue() {
        stack1 = new  ArrayDeque<>();
        stack2 = new  ArrayDeque<>();
    }
    
    public void push(int x) {
        stack1.push(x);
    }
    
    public int pop() {
        if(empty()) {
            return -1;
        }
        if(stack2.isEmpty()) {
           //第一个栈里面所有的元素 放到第二个栈当中 
           while(!stack1.isEmpty()) {
               stack2.push(stack1.pop());
           }
        }
        return stack2.pop();
    }
    
    public int peek() {
        if(empty()) {
            return -1;
        }
        if(stack2.isEmpty()) {
           //第一个栈里面所有的元素 放到第二个栈当中 
           while(!stack1.isEmpty()) {
               stack2.push(stack1.pop());
           }
        }
        return stack2.peek();
    }
    
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

 链栈与顺序栈相比,比较明显的优点是( )

A.插入操作更加方便

B.删除操作更加方便

C.入栈时不需要扩容

A错误,如果是链栈,一般需要进行头插或者头删操作,而顺序栈一般进行尾插和尾删操作,链表的操作比顺序表复杂,因此使用顺序结构实现栈更简单

B错误,原因参考A

C正确,链式结构实现栈时,每次入栈相当于链表中头插一个节点,没有扩容一说

下列关于栈的叙述中,正确的是()

A.栈底元素一定是最后入栈的元素

B.栈顶元素一定是最先入栈的元素

C.栈操作遵循先进后出的原则

D.以上说法均错误

栈是先进后出,队列先进先出,因此:

A错误:栈底元素是最先入栈的

B错误:栈顶元素一定是最后入栈的

C正确,栈的特性后进先出或者先进后出

D错误:C说法明显是正确的

故选择C。

用无头单链表存储队列,front引用队头,back引用队尾,则在进行出队列操作时( )

A.仅修改front

B.front 和 back 都要修改

C.front 和 back 可能都要修改

D.仅修改back

出队列时:

  1. 如果队列中有多个节点时,只需要修改front
  2. 如果队列中只有一个节点时,front和back都需要修改

故应该选择C。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哞哞不熬夜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值