当前位置: 首页 > article >正文

数据结构线性表-栈和队列的实现

1. 栈(Stack)

1.1 概念

:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈 顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈。出数据在栈顶。

1.2 栈的使用

从上图中可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的Vector是线程安全;Vector类,是线程安全的动态数组,但是性能较差 , 现在已经不是很常用了 , 可以说已经过时了。

常用方法

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

public static void main(String[] args) {
Stack<Integer> s = new Stack();
s.push(1);
s.push(2);
s.push(3);
s.push(4);
System.out.println(s.size()); // 获取栈中有效元素个数---> 4
System.out.println(s.peek()); // 获取栈顶元素---> 4
s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
if(s.empty()){
System.out.println("栈空");
}else{
System.out.println(s.size());
 }
}

1.3 栈的模拟实现

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

代码实现

1. 构造方法

class MyStack{

    private int[] arr;

    // size 记录栈中元素个数
    private int size;

    public MyStack(){
        // 调用无参构造方法 默认最大容量12
        this(12);
    }

    public MyStack(int MaxSize){
        this.arr = new int[MaxSize];
    }
}

2. 入栈(push)

// 入栈
    public int push(int value){
        if(this.size == arr.length){
            // 栈满 ,需要扩容

            int[] copyArr;
            // 复制arr 数组并扩容一倍
            copyArr = Arrays.copyOf(arr,2 * arr.length);
            arr = copyArr;

        }
        //将元素添加到size位置
        this.arr[size] = value;
        // 元素个数加一
        this.size++;
        // 返回添加元素
        return value;
    }

3. 出栈(pop)

// 出栈
    public int pop(){
        if(this.size == 0){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        // 获得栈顶元素
        int value = this.arr[size - 1];
        // size - 1 之后, 下一次插入时会覆盖原数据,利用覆盖替代删除
        this.size--;
        return value;
    }

4.获取栈顶元素(peek)

// 获取栈顶元素
    public int peek(){
        if(this.size == 0){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        return this.arr[this.size - 1];
    }

5.获取元素个数(getSize)

//获取元素个数
    public int getSize(){
        return this.size;
    }

6.判断栈是否为空(isEmpty)

//判断元素是否为空
    public boolean isEmpty(){
        return this.size == 0;
    }

完整代码

import java.util.Arrays;

public class MyStack{

    private int[] arr;

    // size 记录栈中元素个数
    private int size;

    public MyStack(){
        // 调用无参构造方法 默认最大容量12
        this(12);
    }

    public MyStack(int MaxSize){
        this.arr = new int[MaxSize];
    }

    // 入栈
    public int push(int value){
        if(this.size == arr.length){
            // 栈满 ,需要扩容

            int[] copyArr;
            // 复制arr 数组并扩容一倍
            copyArr = Arrays.copyOf(arr,2 * arr.length);
            arr = copyArr;

        }
        //将元素添加到size位置
        this.arr[size] = value;
        // 元素个数加一
        this.size++;
        // 返回添加元素
        return value;
    }

    // 出栈
    public int pop(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        // 获得栈顶元素
        int value = this.arr[size - 1];
        // size - 1 之后, 下一次插入时会覆盖原数据,利用覆盖替代删除
        this.size--;
        return value;
    }

    // 获取栈顶元素
    public int peek(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        return this.arr[this.size - 1];
    }

    //获取元素个数
    public int getSize(){
        return this.size;
    }

    //判断元素是否为空
    public boolean isEmpty(){
        return this.size == 0;
    }
}

1.4 栈的应用场景

1. 改变元素的序列

1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是( )

     A: 1,4,3,2                      B: 2,3,4,1                 C: 3,1,4,2                            D: 3,4,2,1

根据栈先进后出的性质,结合题目中进栈的过程中也可以出栈,如A选项:1进1出,2进3进4进,4出3出2出即符合题意,同理C选项,1进2进3进3出之后不可能直接出1,故C选项不可能实现。

2.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺 序是( )。

A: 12345ABCDE          B: EDCBA54321         C: ABCDE12345           D: 54321EDCBA

先进后出,依次入栈,依次出栈,故B选项合理

2. 将递归转化为循环

递归实现逆序打印

 public void display(ListNode head){
        if(head == null){
            return;
        }
     //直到链表末尾,再归回去
        if(head.next == null){
            System.out.println(head.val+" ");
            return;
        }
        display(head.next);
        System.out.println(head.val+" ");
}

使用栈实现逆序打印

public void display(ListNode head){
        if(head == null){
            return;
         }
        Stack<ListNode> stack  = new Stack<>();
        ListNode cur = head;
         while(cur!= null){
              stack.push(cur);
              cur = cur.next;
             }
        while(!stack.empty()){
            ListNode ret =   stack.pop();
            System.out.println(ret.val+" ");
        }
    }

2. 队列(Queue)

2.1 概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

入队列:进行插入操作的一端称为队尾(Tail/Rear)

出队列:进行删除操作的一端称为队头 (Head/Front)

2.2 队列的使用

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


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

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

public static void main(String[] args) {
Queue<Integer> q = new LinkedList<>();
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5); // 从队尾入队列
System.out.println(q.size());
System.out.println(q.peek()); // 获取队头元素
q.poll();
System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
if(q.isEmpty()){
System.out.println("队列空");
}else{
System.out.println(q.size());
}
}

2.3 队列模拟实现

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构和链式结构 。

 
public class Queue {
    // 双向链表节点
    public static class ListNode{
        ListNode next;
        ListNode prev;
        int value;
        ListNode(int value){
            this.value = value;
        }
    }
    ListNode first; // 队头
    ListNode last; // 队尾
    int size = 0;
    // 入队列---向双向链表位置插入新节点
    public void offer(int e){
        ListNode newNode = new ListNode(e);
        if(first == null){
            first = newNode;
// last = newNode;
        }else{
            last.next = newNode;
            newNode.prev = last;
// last = newNode;
        }
        last = newNode;
        size++;
    }
    // 出队列---将双向链表第一个节点删除掉
    public int poll(){
// 1. 队列为空
// 2. 队列中只有一个元素----链表中只有一个节点---直接删除
// 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
        int value = 0;
        if(first == null){
            return null;
        }else if(first == last){
            last = null;
            first = null;
        }else{
            value = first.value;
            first = first.next;
            first.prev.next = null;
            first.prev = null;
        }
        --size;
        return value;
    }
    // 获取队头元素---获取链
    public int peek(){
        if(first == null){
            return null;
        }
        return first.value;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty(){
        return first == null;
    }
}

2.4 循环队列

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。

数组下标循环的小技巧 

1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length

2. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset)%array.length

如何区分空与满

1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记

public class CircularQueue {
    private int front;
    private int rear;
    private int[] circle;

    public CircularQueue(int k) {
        //浪费掉一个存储空间
        circle = new int[k+1];
    }

	//入队列
    public boolean enQueue(int value) {
        if (isFull()) {
            return false;
        }
        circle[rear] = value;
        //因为是循环队列,不能写++,要以取模的方式
        rear = (rear + 1) % circle.length;
        return true;
    }
	
	//出队列
    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % circle.length;
        return true;
    }
	
	//返回队头元素
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return circle[front];
    }

	//返回队尾元素
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        return circle[(rear - 1 + circle.length) % circle.length];
    }

    public boolean isEmpty() {
        return rear == front;
    }

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

    
}

3.  双端队列 (Deque)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

Deque是一个接口,使用时必须创建LinkedList的对象。

在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。

Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

4. 栈和队列的互相实现

用栈实现队列:

class MyQueue {
    public Stack<Integer> stack1;
    public Stack<Integer> stack2;
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
 
    public void push(int x) {
        stack1.push(x);
    }
 
    public int pop() {
        if (stack2.isEmpty()) {
in2out();
        }
        return stack2.pop();
    }
 
    public int peek() {
        if (stack2.isEmpty()){
in2out();
        }
        return stack2.peek();
    }
 
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
    private void in2out() {
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop());
        }
    }
}

用队列实现栈:

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;
 
    public MyStack() {
        queue1 = new LinkedList<Integer>();
        queue2 = new LinkedList<Integer>();
    }
    
    public void push(int x) {
        queue2.offer(x);
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }
    
    public int pop() {
        return queue1.poll();
    }
    
    public int top() {
        return queue1.peek();
    }
    
    public boolean empty() {
        return queue1.isEmpty();
    }
}


http://www.kler.cn/a/162778.html

相关文章:

  • uni-app中使用 unicloud 云开发平台③
  • 图论基本术语
  • 欧国联的规则,你都了解吗?
  • 大数据面试题--kafka夺命连环问(后10问)
  • ESLint 使用教程(七):ESLint还能校验JSON文件内容?
  • C指针创建三维数组
  • kafka高吞吐、低延时、高性能的实现原理
  • yarn和npm的区别
  • QToolTip 是 Qt 框架中用于显示工具提示(Tooltip)的类
  • LLaMA-Factory微调ChatGLM3报错: Segmentation fault (core dumped)
  • 工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计
  • 一文详解Java单元测试Junit
  • SEO优化是什么,如何进行SEO优化
  • python的websocket方法教程
  • MySQL中是如何insert数据的
  • 2024年网络安全行业前景和技术自学
  • C语言-字符串函数
  • 在jupyter notebook中修改其他文件的解决方案
  • 前端学习系列之CSS
  • 蛇形矩阵
  • 正则表达式:字符串处理的瑞士军刀
  • 低代码:美味膳食或垃圾食品?
  • Java 11 到 Java 21:无缝迁移的可视化指南
  • c语言-动态内存管理
  • tanstack/react-query使用手册
  • Redis数据已经删除了,为什么内存占用还是很高?