《算法笔记》第7章 提高篇(1)——数据结构专题(1)

138 阅读2分钟

栈的应用

简单计算器的实现:

思路:将中缀表达式转化为后缀表达式,计算后缀表达式

将中缀表达式转化为后缀表达式:

  1. opStack 操作符栈
  2. queue 存放后缀表达式的队列
  3. 有括号的情况

计算后缀表达式: 1.利用quque与空的opStack即可

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

/**
* 简单计算器
*/
public class Codeup_1918 {

   String s;//读入的中缀表达式
   String[] strings;//空格分隔中缀表达式
   Stack<Node> opStack=new Stack<>();//操作符栈
   Queue<Node> queue=new LinkedList<>();//存放后缀表达式
   HashMap<Character,Integer> hm=new HashMap<>();//存放运算符优先级,乘除为1,加减为0

   public static void main(String[] args) throws IOException {

       Codeup_1918 test = new Codeup_1918();
       test.input();
       test.change();
       test.cal();
   }

   //读入与初始化
   public void input() throws IOException {
       InputStreamReader ir = new InputStreamReader(System.in);
       BufferedReader br = new BufferedReader(ir);
       s=br.readLine();
       strings=s.split(" ");

       hm.put('(',-1); //括号的优先级应该是最高的,但是我不想把括号加入到后缀表达式中,所以设定优先级为-1。当栈顶为左括号的时候,不管op的优先级是0还是1,都能直接入栈。
       hm.put('+',0);
       hm.put('-',0);
       hm.put('*',1);
       hm.put('/',1);
   }

   //将中缀表达式转换为后缀表达式
   public void change(){
       for (String string : strings) {
           Node node = new Node();
           if(string.matches("[0-9]+")){//可转化为整形
               node.num=Integer.parseInt(string);
               node.flag=true;
               queue.offer(node);//将数字加入后缀表达式
           }else if(string.charAt(0)=='('){
               node.op=string.charAt(0);
               node.flag=false;
               opStack.push(node);
           }else if(string.charAt(0)==')'){
               while(opStack.peek().op!='('){//将栈中元素弹出直到遇到左括号为止
                   queue.offer(opStack.pop());
               }
               opStack.pop();//把左括号从栈中弹出来,不想把括号放入后缀表达式中,在生成后缀表达式的过程中便利用括号决定运算顺序
           }else{//无法转化为整形则是运算符
               node.op=string.charAt(0);
               node.flag=false;
               //判断与栈顶运算符的优先级,该op高于则将op压入操作符栈,若低于或等于则将操作符栈中的操作符不断弹出到后缀表达式中,直到op的优先级高于栈顶
               while(!opStack.isEmpty()&&hm.get(opStack.peek().op)>=hm.get(node.op)){//只有op优先级高于栈顶优先级,才能将其压入操作符栈
                   queue.offer(opStack.pop());
               }
               opStack.push(node);
           }
       }
       //如果运算符栈中还有元素,则全部弹入后缀表达式
       while(!opStack.isEmpty()){
           queue.offer(opStack.pop());
       }
       System.out.println(queue);
   }

   //计算后缀表达式
   public double cal(){
       while (!queue.isEmpty()){
           Node node = queue.poll();
           if(node.flag){//操作数
               opStack.push(node);
           }else{//运算符  弹出栈中两个数,先弹出的是第二操作数,后弹出的是第一操作数,运算后将结果push入栈
               Node opNum2 = opStack.pop();
               Node opNum1 = opStack.pop();
               Node opResult = new Node();
               opStack.push(opResult);
               char op=node.op;
               if(op=='+') opResult.num=opNum1.num+opNum2.num;
               if(op=='-') opResult.num=opNum1.num-opNum2.num;
               if(op=='*') opResult.num=opNum1.num*opNum2.num;
               if(op=='/') opResult.num=opNum1.num/opNum2.num;
           }
       }
       System.out.println("后缀表达式结果为:"+opStack.peek().num);
       return opStack.peek().num;
   }
}

/**
* 关于为什么要封装?
* 因为用队列存放后缀表达式的时候,要存放数字,也要存放运算符。那么使用泛型的话,泛型的选择就成为了一个难点.如果选String,显然很不方便,单个操作符是个String,数字也是个String;
*     选int不可能,无法存放操作符;选char会面临多位数的问题
*/
class Node{
   double num;//操作数
   char op;//操作符
   boolean flag;//true表示操作数,false表示操作符

   @Override
   public String toString() {
       return "Node{" +
               "num=" + num +
               ", op=" + op +
               ", flag=" + flag +
               '}';
   }
}

链表处理

静态链表1:

class Node{
//    int address;//地址就是自己的数组下标
    char data;
    int next;
    boolean flag;//标志是否在链表上  //标记位,因为题目给出的节点不一定都在链表上
}

Node[] nodes=new Node[maxn];
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 静态链表
 * 使用数组,在retrieve上的时间复杂度会远低于ArrayList
 */
public class PAT_A1032_2 {

    //地址为5位
    final static int maxn=100001;
    static Node[] nodes=new Node[maxn];

    public static void main(String[] args) throws IOException {
        InputStreamReader ir = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(ir);
        String line = br.readLine();
        String[] s = line.split(" ");
        int begin1=Integer.parseInt(s[0]);
        int begin2=Integer.parseInt(s[1]);
        int total=Integer.parseInt(s[2]);
        for(int i=0;i<total;i++){
            String line1 = br.readLine();
            String[] s1 = line1.split(" ");
            int address=Integer.parseInt(s1[0]);
            char data=s1[1].charAt(0);
            int next=Integer.parseInt(s1[2]);
            Node node = new Node();
            node.data=data;
            node.next=next;
            nodes[address]=node;
        }
        //由begin1遍历整个链表,并把遍历到的节点的flag设置为true
        int address=-1;
        int temp=begin1;
        while(temp!=-1){
            nodes[temp].flag=true;
            temp=nodes[temp].next;
        }
        //由begin2遍历整个链表,遍历过程中判断flag,如果由等于true的,则直接返回节点地址,如果整个链表遍历完了都没有直接返回,则返回-1
        temp=begin2;
        while (temp!=-1){
            if(nodes[temp].flag){
                address=temp;
                break;
            }else{
                temp=nodes[temp].next;
            }
        }
        System.out.println(address);
        br.close();
    }
}

class Node{
//    int address;//地址就是自己的数组下标
    char data;
    int next;
    boolean flag;//标志是否在链表上
}

静态链表2:

注意点:

  1. 题目给出的节点不一定都在链表上
  2. 通过增加标志属性flag来区分有效节点与无效节点。
  3. 要给链表节点重新排序时,会涉及多层排序:(1) 先对对有效节点与无效节点排序 (2)再对有效节点之间的属性按要求排序
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Comparator;

public class PAT_A1052 {

    final static int maxn=100001;
    static Node[] nodes=new Node[maxn];

    public static void main(String[] args) throws IOException {
        for(int i=0;i<maxn;i++){
            nodes[i]=new Node();//flag=0
        }
        InputStreamReader ir = new InputStreamReader(System.in);
        BufferedReader bf = new BufferedReader(ir);
        String line = bf.readLine();
        String[] s = line.split(" ");
        int total=Integer.parseInt(s[0]);//题目给出的总结点数,这些节点并不一定都在链表上! //TODO
        int begin=Integer.parseInt(s[1]);

        for (int i = 0; i < total; i++) {
            String line1 = bf.readLine();
            String[] s1 = line1.split(" ");
            Node node = new Node();
        //    node.flag=1;//flag=1 不能这样,因为题目给出的节点不一定都是有效节点  //TODO
            int address=Integer.parseInt(s1[0]);
            nodes[address]=node;
            node.address=address;
            node.key=Integer.parseInt(s1[1]);
            node.next=Integer.parseInt(s1[2]);
        }

        //需要自己遍历一次链表筛选出有效节点  //TODO
        int temp=begin;
        int trulyTotal=0;
        while(temp!=-1){
            nodes[temp].flag=1;
            trulyTotal++;
            temp=nodes[temp].next;
        }

        if(trulyTotal==0){//特判,链表中没有节点  //TODO
            System.out.println("0 -1");
            return;
        }
        //对所有节点重新排序------>排序有易错点:(需要二级排序)  //TODO
        //                                            1.由于节点存储方式是数组,那么会有大量无效节点存在,需要对有效节点与无效节点进行区分与排序,根据flag
        //                                            2.需要根据两个有效节点之间的key排序
        MyComparator myComparator = new MyComparator();
        Arrays.sort(nodes,myComparator);
        //重新构建逻辑关系
        int newBegin=nodes[0].address;
        for(int i=0;i<trulyTotal-1;i++){
            nodes[i].next=nodes[i+1].address;
        }
        nodes[trulyTotal-1].next=-1;
        System.out.printf("%d %05d\n",total,newBegin);
        for(int i=0;i<trulyTotal;i++){
            if(nodes[i].next==-1){
                System.out.printf("%05d %d %d\n",nodes[i].address,nodes[i].key,nodes[i].next);
            }else{
                System.out.printf("%05d %d %05d\n",nodes[i].address,nodes[i].key,nodes[i].next);
            }
        }


    }
}

class MyComparator implements Comparator<Node> {  //TODO

    @Override
    public int compare(Node o1, Node o2) {
        if(o1.flag==0||o2.flag==0){
            return o2.flag-o1.flag;//一级排序
        }
        return o1.key-o2.key;//二级排序
    }
}

class Node{
    int address;
    int key;
    int next;
    int flag=0;
}