数据结构与算法--栈和队列

143 阅读4分钟

基础概念

定义

栈是一种基于先进后出(FILO) 的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。

我们称数据进入到栈的动作为压栈数据从栈中出去的动作为弹栈

图示

image.png

代码实现

栈的API设计

image.png

代码实现

package com.study.stack;

import java.util.Iterator;

// 使用链表实现栈
public class stackAPI<T> implements Iterable<T>{
    private Node head; // 头结点
    private int N;// 栈的大小

    class Node {
        T item;// 结点存储的数据
        Node next;// 下一个结点

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

    // 通过构造函数初始化头结点和栈结点
    public stackAPI() {
        this.head = new Node(null, null);
        this.N = 0;
    }

    // 栈的大小
    public int size() {
        return N;
    }

    // 栈是否为空
    public boolean isEmpty() {
        return N == 0;
    }

    // 压栈
    public void push(T t) {
        // 找到头结点指向的第一个结点
        Node oldFirst = head.next;
        // 创建新节点
        Node newNode = new Node(t, null);
        //头结点指向新节点
        head.next = newNode;
        // 新节点指向与原来的第一个结点
        newNode.next = oldFirst;
        // 栈的大小+1
        N++;
    }

    // 弹栈
    public T pop() {
        // 找到头结点指向的第一个结点
        Node oldFirst = head.next;
        // 栈为空的情况
        if (oldFirst==null){
            return null;
        }
        // 栈不为空
        // 头结点指向原来第一个结点的下一个结点
        head.next = oldFirst.next;
        // 栈的大小-1
        N--;
        return oldFirst.item;
    }

    // 遍历栈  实现Iterable接口
    @Override
    public Iterator<T> iterator() {
        return new SIterator();
    }
    private class SIterator implements Iterator{
        private Node n;
        public SIterator(){
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

        @Override
        public Object next() {
            n = n.next;
            return n.item;
        }
    }
}

测试

package com.study.stack;

public class stackTest {
    public static void main(String[] args) {
        // 创建一个栈
        stackAPI<String> stack = new stackAPI<>();
        // 测试压栈
        stack.push("a");
        stack.push("b");
        stack.push("c");
        stack.push("d");
        System.out.println("栈中的数据为:");
        for (String s : stack) {
            System.out.println(s);
        }
        System.out.println("栈的大小为:"+stack.size());
        // 测试弹栈
        String pop = stack.pop();
        System.out.println("弹出的元素是:"+pop);
        System.out.println("弹出元素后,栈的大小为:"+stack.size());
    }
}

结果

image.png

一些关于栈的经典问题

匹配括号问题

问题描述:给定一个字符串,里边可能包含"()"小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。

思路:

  1. 扫描字符串,左括号进栈,遇到右括号的话,判断当前栈内是否有左括号可以出栈,有就出栈,没有的话,直接返回括号不匹配。
  2. 扫描完字符串,查看栈的大小,如果栈的大小为0,就说明匹配,反之就是不匹配

代码实现:

package com.study.stack;
// 给定一个字符串,里边可能包含"()"小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。
public class stackProblem {
    public static void main(String[] args) {
        String s = "()(hello)(hi))";
        boolean match = match(s);
        System.out.println("字符串"+s+"中的括号是否匹配:"+match);
    }
    // 括号匹配
    public static boolean match(String s){
        // 创建栈,存放括号
        stackAPI<String> stack = new stackAPI<>();

        // 遍历字符串,如果是左括号就入栈,右括号就出栈
        for (int i = 0; i < s.length(); i++) {
            String str = s.charAt(i)+"";
            if (str.equals("(")){
                stack.push(str);
            }
            if (str.equals(")")){
                if (stack.size()==0){
                    return false;
                }
                stack.pop();
            }
        }
        // 通过栈的大小是否为0来判断是否匹配
        return stack.size()==0;
    }
}

逆波兰表达式

问题描述:给定一个只包含加减乘除四种运算的逆波兰表达式的数组表示方式,求出该逆波兰表达式的结果。

思路:

  1. 遍历数组,数字入栈,符号出栈计算,一次性出栈两个,计算结果再入栈
  2. (这是建立在给定的逆波兰表达式是正确的情况下)

代码实现

package com.study.stack;

// 给定一个只包含加减乘除四种运算的逆波兰表达式的数组表示方式,求出该逆波兰表达式的结果。
public class stackProblem2 {
    public static void main(String[] args) {
        // 中缀表达式:3*(17-15)+18/6
        String[] s = {"3","17","15","-","*","18","6","/","+"};
        int result = getResult(s);
        System.out.println("计算的结果是:"+result);
    }

    // 1. 创建栈对象
    // 2. 遍历数组,数字入栈,
    // 3. 符号出栈计算,一次性出栈两个,计算结果再入栈
    public static int getResult(String[] s){
        stackAPI<String> stack = new stackAPI<>();
        for (int i = 0; i < s.length; i++) {
            if (!s[i].equals("+")&&!s[i].equals("-")&&!s[i].equals("*")&&!s[i].equals("/")){
                stack.push(s[i]);
            }else if (s[i].equals("+")){
                int m = Integer.parseInt(stack.pop());
                int n = Integer.parseInt(stack.pop());
                String s1 = String.valueOf(n + m);
                stack.push(s1);
            }else if (s[i].equals("-")){
                int m = Integer.parseInt(stack.pop());
                int n = Integer.parseInt(stack.pop());
                String s1 = String.valueOf(n-m);
                stack.push(s1);
            }else if (s[i].equals("*")){
                int m = Integer.parseInt(stack.pop());
                int n = Integer.parseInt(stack.pop());
                String s1 = String.valueOf(n*m);
                stack.push(s1);
            }else if (s[i].equals("/")){
                int m = Integer.parseInt(stack.pop());
                int n = Integer.parseInt(stack.pop());
                String s1 = String.valueOf(n/m);
                stack.push(s1);
            }
        }
        return Integer.parseInt(stack.pop());
    }
}

队列

基础概念

定义

队列是一种基于先进先出(FIFO) 的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先读被读出来。

图示

image.png

代码实现

队列的API设计

image.png

代码实现

package com.study.queue;

import java.util.Iterator;

public class queueAPI<T> implements Iterable<T>{

    private Node head; //头结点
    private Node last; //尾结点
    private int N;//元素个数

    private class Node{
        public T item;
        public Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

    public queueAPI() {
        this.head = new Node(null,null);
        this.last = null;
        this.N = 0;
    }

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

    // 求队列的大小
    public int size(){
        return N;
    }
    //向队列中插入值
    public void enqueue(T t){
        if (last==null){//last为空,说明此时队列为空
            last = new Node(t, null);
            head.next=last;
        }else { // 队列不为空,要更换last为新创建的结点
            Node oldLast = last;
            last = new Node(t, null);
            oldLast.next=last;
        }
        // 元素加一
        N++;
    }
    // 向队列中取值
    public T dequeue(){
        if (isEmpty()){
            return null;
        }
        Node first = head.next;
        head.next = first.next;
        N--;
        if (isEmpty()){
            last=null;
        }
        return first.item;
    }
    // 遍历队列
    @Override
    public Iterator<T> iterator() {
        return new QIterator();
    }
    private class QIterator implements Iterator{
        private Node node;
        public QIterator(){
            this.node = head;
        }

        @Override
        public boolean hasNext() {
            return node.next!=null;
        }

        @Override
        public Object next() {
            node = node.next;
            return node.item;
        }
    }
}

测试

package com.study.queue;

public class queueTest {
    public static void main(String[] args) {
        queueAPI<String> q = new queueAPI<>();
        q.enqueue("1");
        q.enqueue("2");
        q.enqueue("3");
        q.enqueue("4");
        q.enqueue("5");
        System.out.println("队列的长度为:"+q.size());
        System.out.println("队列存储的数据为:");
        for (String s : q) {
            System.out.println(s);
        }
        String dequeue = q.dequeue();
        System.out.println("出队的元素为:"+dequeue);
        System.out.println("出队后的元素总数为:"+q.size());
    }
}

结果

image.png