栈,队列,优先队列

319 阅读4分钟

3.栈:

  • 栈是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出

    的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一

    个数据被第一个读出来)。

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

image.png

  • 栈API设计:

    类名Stack
    构造方法Stack:创建Stack对象
    成员方法1.public boolean isEmpty() 2.public int size() 3.public T pop() 4.public void push(T t)
    成员变量1.private Node head:记录首结点 2.private int N:当前栈的元素个数
  • 利用链表实现。入栈:在第一个结点处插入元素,出栈:依次弹出第一个结点

  • 栈的链式代码实现:

    public class aStack<T> implements Iterable<T> {
        //记录首结点
        private Node head;
        //栈中元素的个数
        private int N;
    ​
        public aStack(){
            head = new Node(null,null);
            N=0;
        }
    ​
        //判断当前栈中元素个数是否为0
        public boolean isEmpty(){
            return N==0;
        }
        //把t元素压入栈
        public void push(T t){
            Node oldNext = head.next;
            Node node = new Node(t, oldNext);
            head.next = node;    //入栈元素与头结点相连,入栈元素指向上一个结点
            //个数+1
            N++;
        }
        //弹出栈顶元素,弹出的是头结点的下一个结点
        public T pop(){
            Node oldNext = head.next;
            if (oldNext==null){
                return null;
            }
            //删除首个元素
            head.next = head.next.next;
            //个数-1
            N--;
            return oldNext.item;
        }
        //获取栈中元素的个数
        public int size(){
            return N;
        }
    ​
        @Override
        public Iterator<T> iterator() {
            return new SIterator();
        }
    ​
        private class SIterator implements Iterator<T>{
            private Node n = head;
            @Override
            public boolean hasNext() {
                return n.next!=null;
            }
    ​
            @Override
            public T next() {
                Node node = n.next;
                n = n.next;
                return node.item;
            }
        }
        private class Node{
            public T item;
            public Node next;
    ​
            public Node(T item, Node next) {
                this.item = item; this.next = next;
            }
        }
    }
    
  • 测试:

    public class test {
        public static void main(String[] args) {
            aStack<String> stack=new aStack<>();
            stack.push("a");
            stack.push("b");
            stack.push("c");
            stack.push("d");
            for (String str : stack) {
                System.out.print(str+" ");
            }
            System.out.println("-----------------------------");
            String result = stack.pop();
            System.out.println("弹出了元素:"+result);
            System.out.println(stack.size());
        }
    }
    

1.括号匹配问题:

  • 问题描述

    给定一个字符串,里边可能包含"()"小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。
    例如:
        "(上海)(长安)":正确匹配 
        "上海((长安))":正确匹配 
        "上海(长安(北京)(深圳)南京)":正确匹配 
        "上海(长安))":错误匹配 
        "((上海)长安":错误匹配
    
  • 如果遇到 '(' ,将它存入栈中;如果遇到 ')',将 '(' 出栈。

  • 两种情况错误匹配,遇到 ')' 栈中弹出的 ')' 为null;循环完后,栈中还有元素

  • 括号匹配问题的代码实现:

    public class BracketsMatch {
        public static void main(String[] args) {
            String str = "(上海(长安)())";
            boolean match = isMatch(str);
            System.out.println(str+"中的括号是否匹配:"+match);
        }
        public static boolean isMatch(String str){
            Stack<Character> stack= new Stack<>();
            for(int i=0;i<str.length();i++){
                char c=str.charAt(i);
                if(c=='('){
                    stack.push(c);
                }
                if(c==')'){
                    Character pop = stack.pop();
                    if(pop==null){
                        return false;
                    }
                }
            }
            if(stack.size()==0) {
                return true;
            }else{
                return false;
            }
        }
    }
    

2.逆波兰表达式问题:

  • 逆波兰表达式求值问题是我们计算机中经常遇到的一类问题,要研究明白这个问题,首先我们得搞清楚什么是逆波

    兰表达式?要搞清楚逆波兰表达式,我们得从中缀表达式说起。

  • 中缀表达式:

    • 中缀表达式就是我们平常生活中使用的表达式,例如:1+3*2,2-(1+3)等等,中缀表达式的特点是:二元运算符总

      是置于两个操作数中间。

      中缀表达式是人们最喜欢的表达式方式,因为简单,易懂。但是对于计算机来说就不是这样了,因为中缀表达式

      运算顺序不具有规律性。不同的运算符具有不同的优先级,如果计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作。

  • 逆波表达式(后缀表达式):

    • 逆波兰表达式是波兰逻辑学家J・卢卡西维兹(J・ Lukasewicz)于1929年首先提出的一种表达式的表示方法,后缀表达式的特点:运算符总是放在跟它相关的操作数之后

      中缀表达式逆波兰表达式
      a+bab+
      a+(b-c)abc-+
      a+(b-c)*dabc-d*+
      a*(b-c)+dabc-*d+
    • 逆波兰表达式求值问题代码实现:

      将操作数压入栈中,将操作符当做条件判断

    public class ReversePolishNotation {
        public static void main(String[] args) {
            //中缀表达式3*(17-15)+18/6的逆波兰表达式如下
            String[] notation = {"3", "17", "15", "-", "*","18", "6","/","+"};
            int result = caculate(notation);
            System.out.println("逆波兰表达式的结果为:"+result);
        }
        public static int caculate(String[] notaion){
            Stack<Integer> stack=new Stack<>();
            Integer pop1;
            Integer pop2;
            Integer result;
            for(String s:notaion){
                switch (s){
                    case "+":
                        pop1 = stack.pop();
                        pop2 = stack.pop();
                        result=pop2+pop1;
                        stack.push(result);
                        break;
                    case "-":
                        pop1 = stack.pop();
                        pop2 = stack.pop();
                        result=pop2-pop1;
                        stack.push(result);
                        break;
                    case "*":
                        pop1 = stack.pop();
                        pop2 = stack.pop();
                        result=pop2*pop1;
                        stack.push(result);
                        break;
                    case "/":
                        pop1 = stack.pop();
                        pop2 = stack.pop();
                        result=pop2/pop1;
                        stack.push(result);
                        break;
                    default:
                        stack.push(Integer.valueOf(s));
                        break;
                }
            }
            return stack.pop();
        }
    }
    

3.链式栈的实际应用:

//创建对象
Stack<String> stack=new Stack<>();
//方法:
isEmpty() 判断当前栈是否为空
peek() 获得当前栈顶元素
pop() 获得当前栈顶元素并删除
push(E object) //将元素加入栈顶
search(Object o)  //查找元素在栈中的位置,由栈低向栈顶方向数
isEmpty()  //判断是否为空

4.队列:

  • 队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它

    按照先进先出的原则存储数据,先进入的数据,在读取数据时先读被读出来。

image.png

  • 队列的API设计:

    类名Queue
    构建方法Queue():创建Queue对象
    成员方法1.public boolean isEmpty() :判断队列是否为空,是返回true,否返回false 2.public int size() :获取队列中元素的个数 3.public T dequeue() :从队列中拿出一个元素 4.public void enqueue(T t) :往队列中插入一个元素
    成员变量1.private Node head:记录首结点 2.private int N:当前栈的元素个数 3.private Node last:记录最后一个结点
  • 队列链表实现:

    public class Queue<T> implements Iterable<T> {
        //记录首结点
        private Node<T> head;
        //记录最后一个结点
        private Node<T> last;
        //记录队列中元素的个数
        private int N;
    ​
        public Queue() {
            head = new Node(null,null);
            last=null;
            N=0;
        }
        //判断队列是否为空
        public boolean isEmpty(){
            return N==0;
        }
        //返回队列中元素的个数
        public int size(){
            return N;
        }
        //向队列中插入元素t
        public void enqueue(T t){
            if (last==null) {
                last = new Node(t, null);
                head.next = last;
            }else{
                Node oldLast = last;
                last = new Node(t,null);
                oldLast.next=last;
            }
            //个数+1
            N++;
        }
        //从队列中拿出一个元素
        public T dequeue(){
            if (isEmpty()){
                return null;
            }
            Node oldFirst = head.next;
            head.next = oldFirst.next;
            N--;
            if (isEmpty()){
                last=null;
            }
            return (T) oldFirst.item;
        }
    ​
        @Override
        public Iterator<T> iterator() {
            return new QIterator();
        }
        private class QIterator implements Iterator<T>{
            private Node n = head;
            @Override
            public boolean hasNext() {
                return n.next!=null;
            }
            @Override
            public T next() {
                Node node = n.next;
                n = n.next;
                return (T) node.item;
            }
        }
        //结点类
        private static class Node<T> {
            //存储元素
            public T item;
            //指向下一个结点
            public Node next;
    ​
            public Node(T item, Node next){
                this.item=item;
                this.next=next;
            }
        }
    }
    
  • 测试:

public class test {
    public static void main(String[] args) {
        Queue<String> queue = new Queue<>();
        queue.enqueue("a");
        queue.enqueue("b");
        queue.enqueue("c");
        queue.enqueue("d");
        for (String str : queue) {
            System.out.print(str+" ");
        }
        System.out.println();
        System.out.println("-----------------------------");
        String result = queue.dequeue();
        System.out.println("出列了元素:"+result);
        System.out.println(queue.size());
    }
}

1.链式队列实际应用:

//创建对象
Queue<Integer> queue=new LinkedList<>();
queue.add(1);   //往队列尾部插入元素
queue.poll();   //出队列
queue.size();  //队列长度
queue.isEmpty()  //判断队列是否为空
queue.peek()    //返回队首元素,但不出队
queue.offer(3)   //往队列尾部插入元素    

1.优先队列:

  • 底层使用堆实现,利用堆排序的原理

image.png

  • 优先队列按照其作用不同,可以分为以下两种:

    • 最大优先队列:可以获取并删除队列中最大的值
    • 最小优先队列:可以获取并删除队列中最小的值

1.最大优先队列:

  • 最大优先队列API设计:

    类名MaxPriorityQueue
    构造方法MaxPriorityQueue(int capacity):创建容量为capacity的MaxPriorityQueue对象
    成员方法1.private boolean less(int i,int j):判断堆中索引i处的元素是否小于索引j处的元素 2.private void exch(int i,int j):交换堆中i索引和j索引处的值 3.public T delMax():删除队列中最大的元素,并返回这个最大元素 4.public void insert(T t):往队列中插入一个元素 5.private void swim(int k):使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置 6.private void sink(int k):使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置 7.public int size():获取队列中元素的个数 8.public boolean isEmpty():判断队列是否为空
    成员变量1.private T[] imtes : 用来存储元素的数组 2.private int N:记录堆中元素的个数
  • 最大优先队列代码实现:

public class 最大优先队列<T extends Comparable<T>> {
    //存储堆中的元素
    private T[] items;
    //记录堆中元素的个数
    private int N;

    public 最大优先队列(int capacity) {
        items = (T[]) new Comparable[capacity+1];
        N = 0;
    }

    //获取队列中元素的个数
    public int size() {
        return N;
    }
    //判断队列是否为空
    public boolean isEmpty() {
        return N == 0;
    }
    //判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }
    //交换堆中i索引和j索引处的值
    private void exch(int i, int j) {
        T tmp = items[i];
        items[i] = items[j];
        items[j] = tmp;
    }
    //往堆中插入一个元素
    public void insert(T t) {
        items[++N] = t; swim(N);
    }
    //删除堆中最大的元素,并返回这个最大元素
    public T delMax() {
        T max = items[1];
        //交换索引1处和索引N处的值
        exch(1, N);
        //删除最后位置上的元素
        items[N] = null;
        N--;
        //个数-1
        sink(1);
        return max;
    }
    //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void swim(int k) {
        //如果已经到了根结点,就不需要循环了
        while (k > 1) {
            //比较当前结点和其父结点
            if (less(k / 2, k)) {
                //父结点小于当前结点,需要交换
                exch(k / 2, k);
            }
            k = k / 2;
        }
    }
    //使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k) {
        //如果当前已经是最底层了,就不需要循环了
        while (2 * k <= N) {
            //找到子结点中的较大者
            int max = 2 * k;
            if (2 * k + 1 <= N) {
                //存在右子结点
                if (less(2 * k, 2 * k + 1)) {
                    max = 2 * k + 1;
                }
            }
            //比较当前结点和子结点中的较大者,如果当前结点不小,则结束循环
            if (!less(k, max)) {
                break;
            }
            //当前结点小,则交换,
            exch(k, max);
            k = max;
        }
    }
}
  • 测试:

    public class 最大测试 {
        public static void main(String[] args) {
            String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
            最大优先队列<String> maxpq = new 最大优先队列<>(20);
            for (String s : arr) {
                maxpq.insert(s);
            }
            System.out.println(maxpq.size());
            String del;
            while(!maxpq.isEmpty()){
                del = maxpq.delMax();
                System.out.print(del+",");
            }
        }
    }
    

2.最小优先队列:

  • 构建大根堆,然后和尾元素交换

3.索引优先队列:

4.优先队列的实例应用:

1.创建优先队列:

//创建优先队列(自然排序)                                                 

Queue<Integer> Queue = new PriorityQueue<>();          
                                                               
//创建优先队列(自定义排序)                                                
static Comparator<Integer> cmp = new Comparator<Integer>() { 
    public int compare(Integer e1, Integer e2) {                   
    return e2 - e1;                                                
}                                                              
Queue<Integer> Queue = new PriorityQueue<>(cmp);
//从大到小排序
PriorityQueue<Integer> q = new PriorityQueue<>((o1,o2)->o2-o1);

2.常用方法:

1.  peek()//返回队首元素
2.  poll()//返回队首元素,队首元素出队列
3.  add()//添加元素
4.  size()//返回队列元素个数
5.  isEmpty()//判断队列是否为空,为空返回true,不空返回false

3.实例代码:

//自定义比较器,降序排列                                                  |
| - | -------------------------------------------------------------- |
|   | static Comparator<Integer> cmp = new Comparator<Integer>() { |
|   | public int compare(Integer e1, Integer e2) {                   |
|   | return e2 - e1;                                                |
|   | }                                                              |
|   | };                                                             |
|   | public static void main(String[] args) {                      |
|   | //不用比较器,默认升序排列                                                 |
|   | Queue<Integer> q = new PriorityQueue<>();                     |
|   | q.add(3);                                                      |
|   | q.add(2);                                                      |
|   | q.add(4);                                                      |
|   | while(!q.isEmpty())                                            |
|   | {                                                              |
|   | System.out.print(q.poll()+" ");                                |
|   | }                                                              |
|   | /**                                                          |
|   | * 输出结果                                                        |
|   | * 2 3 4                                                       |
|   | */                                                            |
|   | //使用自定义比较器,降序排列                                                |
|   | Queue<Integer> qq = new PriorityQueue<>(cmp);                 |
|   | qq.add(3);                                                     |
|   | qq.add(2);                                                     |
|   | qq.add(4);                                                     |
|   | while(!qq.isEmpty())                                           |
|   | {                                                              |
|   | System.out.print(qq.poll()+" ");                               |
|   | }                                                              |
|   | /**                                                          |
|   | * 输出结果                                                        |
|   | * 4 3 2                                                       |
|   | */                                                            |
|   | }