数据结构————栈和队列

4.1栈

4.1.1栈的定义

  • 后进先出的线性表是限定仅在表尾进行插入和删除操作的线性表
  • 我们把允许插入和删除的一端称为 栈顶,另一端称为 栈底,不含任何数据元素的栈称为 空栈
  • 插入元素到栈顶的操作,称为入栈;从栈顶删除最后一个元素的操作,称为出栈

在这里插入图片描述

4.1.1.1栈的顺序存储结构

🦊初始化操作

  • 由于是顺序存储结构,那么底层存储数据的是数组
    private Object[] arr;//存储数据的数组
    private int top;//指向栈顶元素的指针
    private int length;//存储数据长度

    public ArrayStack(int capacity) {
        this.arr =  new Object[capacity];
        this.top = -1;
        this.length = 0;
    }

🦉入栈操作

  • 首先判断栈是否已满
  • 如果未满,将top指针加1,并将元素添加到数组中
    //入栈
    public void push(T element){
        if (top == arr.length - 1) {
            throw new StackOverflowError();
        }
        top++;
        arr[top]=element;
        length++;
    }

🦈出栈操作

  • 首先需要判断当前栈是否还含有数据
  • 当栈中存在数据,取出下标为top的元素并返回,随后将top栈顶指针减一,长度减一
    //出栈
    public T pop(){
        if(length==0){
            throw new EmptyStackException();
        }
        T a= (T) arr[top];
        top--;
        length--;
        return a;
    }

🦌获取所有数据

  • 首先将top指针赋值给index,随后通过while循环遍历栈内数据,直到index=-1截止循环
    //获取所有数据
    public void getAllData(){
        StringBuilder sb=new StringBuilder();
        int index=top;
        while(index!=-1){
            sb.append(arr[index]).append(",");
            index--;
        }
        String str=sb.toString();
        if (str.endsWith(",")){
            str=str.substring(0,str.length()-1);
        }
        System.out.println(str);
    }

完整代码

package Stack;

import java.util.EmptyStackException;

public class ArrayStack<T> {
    private Object[] arr;//存储数据的数组
    private int top;//指向栈顶元素的指针
    private int length;//存储数据长度

    public ArrayStack() {
    }

    public int getLength() {
        return length;
    }

    public ArrayStack(int capacity) {
        this.arr =  new Object[capacity];
        this.top = -1;
        this.length = 0;
    }

    //入栈
    public void push(T element){
        if (top == arr.length - 1) {
            throw new StackOverflowError();
        }
        top++;
        arr[top]=element;
        length++;
    }

    //出栈
    public T pop(){
        if(length==0){
            throw new EmptyStackException();
        }
        T a= (T) arr[top];
        top--;
        length--;
        return a;
    }

    //获取栈顶元素
    public T peek(){
        if (length==0){
            throw new EmptyStackException();
        }
        return (T) arr[top];
    }

    //获取所有数据
    public void getAllData(){
        StringBuilder sb=new StringBuilder();
        int index=top;
        while(index!=-1){
            sb.append(arr[index]).append(",");
            index--;
        }
        String str=sb.toString();
        if (str.endsWith(",")){
            str=str.substring(0,str.length()-1);
        }
        System.out.println(str);
    }
}

测试

package Stack;

public class test {
    public static void main(String[] args) {
        ArrayStack<String> stack=new ArrayStack<>(20);
        stack.push("1");
        stack.push("2");
        stack.push("3");
        stack.push("4");

        //获取所有数据
        stack.getAllData();//4,3,2,1

        //获取栈顶元素
        System.out.println(stack.peek());//4

        //出栈
        System.out.println(stack.pop());//4
        stack.getAllData();//321

        //获取栈的数据长度
        System.out.println(stack.getLength());//3
    }
}

4.1.1.2两栈共享空间

  • 两栈共享空间是一种数据结构,它允许两个栈共享一个数组的空间。这意味着这个数组被分成两个部分,每个部分都可以被一个栈使用。在这里插入图片描述

  • 在两栈共享空间中,两个栈可以在数组的两端开始,向中间移动

  • 对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数组空间,这就可以最大化的利用数组的空间

代码实现

🌾实现思路

  1. 定义一个整型数组作为两个栈的存储空间,同时定义两个整型变量分别表示两个栈的栈顶位置;
  2. 对于栈1的入栈操作,检查数组是否已满,如果未满则将元素加入栈1中,同时将栈1的栈顶位置加1;
  3. 对于栈2的入栈操作,同样检查数组是否已满,如果未满则将元素加入栈2中,同时将栈2的栈顶位置减1;
  4. 对于栈1的出栈操作,检查栈是否为空,如果非空则将栈1的栈顶元素弹出并返回,同时将栈1的栈顶位置减1;
  5. 对于栈2的出栈操作,同样检查栈是否为空,如果非空则将栈2的栈顶元素弹出并返回,同时将栈2的栈顶位置加1;
  6. 在进行栈操作时,需要注意栈的溢出和下溢问题,以保证栈的正确性。

🌿完整代码

package Stack;

public class ShareStack {
    private int[] arr;
    private int top1;
    private int top2;

    public ShareStack() {
        this.arr = new int[10];
        this.top1 = -1;
        this.top2 = arr.length;
    }
    public void push1(int value) {
        if (top1 < top2 - 1) {
            arr[++top1] = value;
        } else {
            System.out.println("Stack Overflow");
        }
    }

    public void push2(int value) {
        if (top1 < top2 - 1) {
            arr[--top2] = value;
        } else {
            System.out.println("Stack Overflow");
        }
    }

    public int pop1() {
        if (top1 >= 0) {
            return arr[top1--];
        } else {
            System.out.println("Stack Underflow");
            return -1;
        }
    }

    public int pop2() {
        if (top2 < arr.length) {
            return arr[top2++];
        } else {
            System.out.println("Stack Underflow");
            return -1;
        }
    }
}


4.1.2栈的链式存储结构

        对于链式存储结构的栈,栈顶是指向最新入栈的元素的指针。由于链表是从头结点开始,一直延伸到尾结点的,因此在链式存储结构中,将栈顶指针指向链表的头结点,可以使得入栈和出栈操作都在链表的头部进行,这样可以方便的进行插入和删除操作。

代码实现

🍉完整代码

package Stack;

public class LinkedStack<T> {
    private Node<T> top;

    private static class Node<T> {
        private T data;
        private Node<T> next;

        public Node(T data) {
            this.data = data;
        }
    }

    public boolean isEmpty() {
        return top == null;
    }

    public void push(T data) {
        Node<T> newNode = new Node<>(data);
        newNode.next = top;
        top = newNode;
    }

    public T pop() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty");
        }
        T data = top.data;
        top = top.next;
        return data;
    }
}

🍊测试结果

package Stack;

public class test2 {
    public static void main(String[] args) {
        LinkedStack<String> stack = new LinkedStack<>();
        stack.push("Hello");
        stack.push("World");
        System.out.println(stack.pop()); // 输出 "World"
        System.out.println(stack.pop()); // 输出 "Hello"
    }
}

4.1.3栈的应用——四则运算表达式求值

4.1.3.1后缀表达式的定义

        后缀表达式,也叫做逆波兰表达式,是一种不需要括号的表达式表示方法。在后缀表达式中,运算符位于操作数之后。例如,中缀表达式3+4的后缀表达式表示为3 4 +
        后缀表达式的优点是计算过程中不需要考虑运算符的优先级,直接按顺序进行操作即可

4.1.3.2中缀表达式转后缀表达式

🙈转换规则

1. 初始化一个空栈和一个空字符串来存储后缀表达式。

2. 从左到右扫描中缀表达式的每一个元素。

3. 如果当前元素是操作数(数字或变量),则将其添加到后缀表达式字符串的末尾。

3. 如果当前元素是左括号“(”,则将其压入栈中。

4. 如果当前元素是右括号“)”,则将栈中的元素弹出并添加到后缀表达式直到遇到左括号。左右括号不需要添加到后缀表达式中。

5. 如果当前元素是操作符(加、减、乘、除等),则将其与栈顶操作符进行比较。

6. 如果栈顶操作符的优先级大于或等于当前操作符,则弹出栈顶操作符并添加到后缀表达式字符串中,然后继续比较当前操作符与新的栈顶操作符。

7. 如果当前操作符的优先级大于栈顶操作符,则将当前操作符压入栈中。

8. 当中缀表达式扫描结束后,将栈中剩余的操作符弹出并添加到后缀表达式中,直到栈为空。

9. 后缀表达式即为转换后的结果。

🙉例子
假设有一个中缀表达式:

2 + 3 * (4 - 1)
现在我们按照中缀表达式转换为后缀表达式的规则来进行转换:
  1. 初始化一个空栈和一个空字符串:stack = [], postfix = “”
  2. 从左到右扫描中缀表达式的每一个元素。
    • 是操作数,将其添加到后缀表达式字符串的末尾:postfix = “2”
    • 是操作符,将其压入栈中:stack = [“+”]
    • 是操作数,将其添加到后缀表达式字符串的末尾:postfix = “2 3”
    • 是操作符,将其压入栈中:stack = [“+”, “*”]
    • 是左括号,将其压入栈中:stack = [“+”, “*”, “(”]
    • 是操作数,将其添加到后缀表达式字符串的末尾:postfix = “2 3 4”
    • 是操作符,将其压入栈中:stack = [“+”, “*”, “(”, “-”]
    • 是操作数,将其添加到后缀表达式字符串的末尾:postfix = “2 3 4 1”
    • 是右括号,将栈中的元素弹出并添加到后缀表达式直到遇到左括号:postfix = “2 3 4 1 -”, stack = [“+”, “*”]
  3. 当中缀表达式扫描结束后,将栈中剩余的操作符弹出并添加到后缀表达式中,直到栈为空:postfix = “2 3 4 1 - * +”
  4. 后缀表达式即为转换后的结果:2 3 4 1 - * +

4.1.3.3 后缀表达式计算结果

🙈计算规则

1. 初始化一个空栈。

2. 从左到右扫描后缀表达式的每一个元素。

3. 如果当前元素是操作数(数字或变量),则将其压入栈中。

4. 如果当前元素是操作符(加、减、乘、除等),则从栈中弹出两个操作数进行运算,并将结果压入栈中。

5. 当扫描结束后,栈中只剩下一个元素,即为后缀表达式的计算结果。

🙉例子
假设有一个后缀表达式:

2 3 4 1 - * +
现在我们按照后缀表达式计算结果的规则来进行计算:
  1. 初始化一个空栈:stack = []

  2. 从左到右扫描后缀表达式的每一个元素。

    • 2 是操作数,将其压入栈中:stack = [2]
    • 3 是操作数,将其压入栈中:stack = [2, 3]
    • 4 是操作数,将其压入栈中:stack = [2, 3, 4]
    • 1 是操作数,将其压入栈中:stack = [2, 3, 4, 1]
    • 是操作符,从栈中弹出两个操作数进行运算,并将结果压入栈中:stack = [2, 3, 3]
    • 是操作符,从栈中弹出两个操作数进行运算,并将结果压入栈中:stack = [2, 9]
    • 是操作符,从栈中弹出两个操作数进行运算,并将结果压入栈中:stack = [11]
  3. 当扫描结束后,栈中只剩下一个元素,即为后缀表达式的计算结果。

  4. 因此,后缀表达式 2 3 4 1 - * + 的计算结果为 11。

4.2队列

4.2.1队列的定义

  • 队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表
  • 队列是一种先进先出的线性表,允许插入的一端称为队尾,允许删除的一端称为队头

在这里插入图片描述

4.2.2循环队列

4.2.2.1队列顺序存储的不足

  1. 存储空间的浪费:在队列顺序存储中,需要预分配一定的存储空间,但实际使用时可能会出现存储空间的浪费。例如,当队列中的元素个数小于数组长度时,数组中的一部分空间就会被浪费。在这里插入图片描述

  2. 队列长度固定:由于队列顺序存储需要预先分配一定的存储空间,因此队列的长度是固定的,无法动态扩展。如果队列中的元素个数超过了数组长度,就会出现队列溢出的问题在这里插入图片描述

  3. 插入和删除操作低效:在队列顺序存储中,插入和删除操作需要移动元素,效率较低。例如,当队列中的元素出队时,需要将队列中剩余的元素向前移动一个位置,以保证队列的连续性

  4. 队头出队困难:在队列顺序存储中,出队操作通常都是从队头开始的。但是如果队列中有大量元素时,出队操作的效率会较低,因为需要将队列中剩余的元素向前移动一个位置。

4.2.2.2循环队列

我们把队列的这种首尾相接的顺序存储结构称为循环队列

🎅队列满条件

针对循环队列,引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置在这里插入图片描述
当持续向循环队列中插入元素,出现以下情况
在这里插入图片描述
这个时候front和rear指针指向同一个位置,这跟队列为空时的情况是相同的,那怎么来判断队列是否为空,又或是队满呢?在这里插入图片描述
可采取两种方法进行区分:
方法一
       设置一个计数器来记录队列中的元素个数,当队列中的元素个数等于队列的容量时,队列就被认为是已满的。
方法二
       使用front指针和rear指针的相对位置。在循环队列中,当rear指针指向的位置的下一个位置等于front指针的位置时,说明队列已满。

在这里插入图片描述

       我们假设循环队列的容量为n,则队列已满的条件:

(rear+1)%n==front

       需要注意的是,在循环队列的实现中,为了避免队列满时rear指针和front指针指向同一个位置,我们通常会浪费一个数组元素的空间,及当数组中有n个元素时,实际只存储了n-1个元素,用来区分队列是满还是空

🎄通用的计算循环队列长度
       假设循环队列的容量为n,则计算循环队列的长度:

(rear - front + n) % n

4.2.2.3循环队列的代码实现


经过上面的学习,定义一个循环队列是非常简单的啦,快来尝试一番吧


🍉完整代码

package Queue;

public class CircularQueue {
    private int[] arr;
    private int front;
    private int rear;
    private int capacity;

    public CircularQueue(int capacity) {
        arr = new int[capacity];
        front = 0;
        rear = 0;
        this.capacity = capacity;
    }

    public CircularQueue() {
    }

    public boolean isEmpty(){
        return front==rear;//判断队列是否为空
    }

    public boolean isFull(){
        return (rear + 1) % capacity == front; // 判断队列是否已满
    }

    public int size() {
        return (rear - front + capacity) % capacity; // 计算队列长度
    }

    public void enqueue(int data) {
        if (isFull()) {
            throw new RuntimeException("Queue is full.");
        }
        arr[rear] = data; // 插入元素到队尾
        rear = (rear + 1) % capacity; // 移动队尾指针
    }

    public int dequeue() {
        if (isEmpty()) {
            throw new RuntimeException("Queue is empty.");
        }
        int data = arr[front]; // 取出队头元素
        front = (front + 1) % capacity; // 移动队头指针
        return data;
    }
}

🍊测试

package Queue;

public class test {
    public static void main(String[] args) {
        CircularQueue queue = new CircularQueue(5);

        System.out.println("Is queue empty? " + queue.isEmpty()); // true
        System.out.println("Is queue full? " + queue.isFull()); // false
        System.out.println("Queue size: " + queue.size()); // 0

        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);
        System.out.println("Is queue empty? " + queue.isEmpty()); // false
        System.out.println("Is queue full? " + queue.isFull()); // false
        System.out.println("Queue size: " + queue.size()); // 3

        System.out.println("Dequeue: " + queue.dequeue()); // 1
        System.out.println("Queue size: " + queue.size()); // 2

        queue.enqueue(4);
        queue.enqueue(5);
        System.out.println("Is queue full? " + queue.isFull()); // true

        try {
            queue.enqueue(6); // throws exception
        } catch (Exception e) {
            System.out.println("Enqueue exception: " + e.getMessage()); // Queue is full.
        }

        while (!queue.isEmpty()) {
            System.out.println("Dequeue: " + queue.dequeue());
        }
        System.out.println("Queue size: " + queue.size()); // 0
    }
}

🍇结果

Is queue empty? true
Is queue full? false
Queue size: 0
Is queue empty? false
Is queue full? false
Queue size: 3
Dequeue: 1
Queue size: 2
Is queue full? true
Enqueue exception: Queue is full.
Dequeue: 2
Dequeue: 3
Dequeue: 4
Dequeue: 5
Queue size: 0

4.2.3队列的链式存储结构及实现

       队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已。
       为了操作上的方便,我们将队头指针指向队列的头结点,而队尾指针指向终端结点,空队列时,front和rear都指向头结点。
🍉完整代码

package LinkedQue;

import java.util.NoSuchElementException;

public class LinkedQueue<T> {
    private Node<T> front; // 队头指针
    private Node<T> rear; // 队尾指针

    // 节点类
    private static class Node<T> {
        T data; // 数据域
        Node<T> next; // 指针域

        Node(T data) {
            this.data = data;
            this.next = null;
        }
    }

    public LinkedQueue() {
        this.front = null;
        this.rear = null;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return front == null;
    }

    // 元素入队
    public void enqueue(T data) {
        Node<T> newNode = new Node<>(data);
        if (isEmpty()) {
            front = newNode;
            rear = newNode;
        } else {
            rear.next = newNode;
            rear = newNode;
        }
    }

    // 元素出队
    public T dequeue() {
        if (isEmpty()) {
            throw new NoSuchElementException("Queue is empty");
        } else {
            T data = front.data;
            front = front.next;
            if (front == null) {
                rear = null;
            }
            return data;
        }
    }

    // 获取队头元素
    public T peek() {
        if (isEmpty()) {
            throw new NoSuchElementException("Queue is empty");
        } else {
            return front.data;
        }
    }

    // 获取队列长度
    public int size() {
        int count = 0;
        Node<T> current = front;
        while (current != null) {
            count++;
            current = current.next;
        }
        return count;
    }
}

🍊测试

package LinkedQue;

import Stack.LinkedStack;

public class test {
    public static void main(String[] args) {
        LinkedQueue<Integer> queue = new LinkedQueue<>();
        System.out.println("Queue is empty: " + queue.isEmpty()); // true

        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);
        System.out.println("Queue size: " + queue.size()); // 3

        int front = queue.peek();
        System.out.println("Front element: " + front); // 1

        int element = queue.dequeue();
        System.out.println("Dequeue element: " + element); // 1

        System.out.println("Queue size: " + queue.size()); // 2

        element = queue.dequeue();
        System.out.println("Dequeue element: " + element); // 2

        System.out.println("Queue size: " + queue.size()); // 1

        element = queue.dequeue();
        System.out.println("Dequeue element: " + element); // 3

        System.out.println("Queue is empty: " + queue.isEmpty()); // true

        // queue.dequeue(); // throws NoSuchElementException
        // queue.peek(); // throws NoSuchElementException
    }
}

🍇结果

Queue is empty: true
Queue size: 3
Front element: 1
Dequeue element: 1
Queue size: 2
Dequeue element: 2
Queue size: 1
Dequeue element: 3
Queue is empty: true
Exception in thread "main" java.util.NoSuchElementException: Queue is empty
    at Queue.peek(Queue.java:37)
    at QueueTest.main(QueueTest.java:30)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值