数据结构与算法分析一

2019-07-09  本文已影响0人  Teemo_fca4
线性结构与非线性结构

线性结构

1: 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系(如:y=f(x))
2 : 线性结构包含两种存储结构, 顺序存储结构(数组)链式存储结构(链表) 。顺序存储称为顺序表,其存储元素是连续的,链式存储称为链表,链表中的元素不一定是连续的 元素节点存放数据元素及相邻元素的地址信息。
3 常见的线性结构有:数组,队列,链表,和栈

非线性结构

1: 与线性结构相对应,非线性结构数据元素不存在一对一的对应关系,常见的结构有:多维数组,广义表,树结构,图结构

稀疏数组和队列

稀疏数组的定义:

当一个数组的大部分元素是某个确定的值(0,空,或者其他),可以使用稀疏数组来保存当前数组(如果数据趋向于两个数呢? 这个我暂时还不知道)。

image.png

稀疏数组中的第一个元素记录原数组的行列及有效数据个数,后面一次记录原数组的有效数据
二维数组与稀疏数组互相转换

    public static void main(String[] args) {
        // 创建一个原始的二维数组 11 * 11
        // 0: 表示没有棋子, 1 表示 黑子 2 表蓝子
        int chessArr1[][] = new int[11][11];
        chessArr1[1][2] = 1;
        chessArr1[2][3] = 2;
        chessArr1[4][5] = 2;
        // 输出原始的二维数组
        System.out.println("原始的二维数组~~");
        for (int[] row : chessArr1) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();
        }

        // 将二维数组 转 稀疏数组的思
        // 1. 先遍历二维数组 得到非0数据的个数
        int sum = 0;
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if (chessArr1[i][j] != 0) {
                    sum++;
                }
            }
        }

        // 2. 创建对应的稀疏数组
        int sparseArr[][] = new int[sum + 1][3];
        // 给稀疏数组赋值
        sparseArr[0][0] = 11;
        sparseArr[0][1] = 11;
        sparseArr[0][2] = sum;
        
        // 遍历二维数组,将非0的值存放到 sparseArr中
        int count = 0; //count 用于记录是第几个非0数据
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if (chessArr1[i][j] != 0) {
                    count++;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessArr1[i][j];
                }
            }
        }
        
        // 输出稀疏数组的形式
        System.out.println();
        System.out.println("得到稀疏数组为~~~~");
        for (int i = 0; i < sparseArr.length; i++) {
            System.out.printf("%d\t%d\t%d\t\n", sparseArr[i][0], sparseArr[i][1], sparseArr[i][2]);
        }
        System.out.println();
        
        //将稀疏数组 --》 恢复成 原始的二维数组
        /*
         *  1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的  chessArr2 = int [11][11]
            2. 在读取稀疏数组后几行的数据,并赋给 原始的二维数组 即可.
         */
        
        //1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
        
        int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
        
        //2. 在读取稀疏数组后几行的数据(从第二行开始),并赋给 原始的二维数组 即可
        
        for(int i = 1; i < sparseArr.length; i++) {
            chessArr2[sparseArr[i][0]][sparseArr[i][1]] =  sparseArr[i][2];
        }
        
        // 输出恢复后的二维数组
        System.out.println();
        System.out.println("恢复后的二维数组");
        
        for (int[] row : chessArr2) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();
        }
    }

队列

队列是一个有序列表,使用使用数组和链表来实现,队列遵循先进先出原则 FIFO。

数组模拟环形队列

待补充
链表

链表是有序的列表(逻辑上有序,内存上无序)。
单向链表内存结构如下

image.png
单向链表逻辑结构如下
image.png
链表分带头结点的链表不带头结点的链表

实现链表功能--尾部追加数据节点和单链表的遍历

class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点
    //构造器
    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }
    //为了显示方法,我们重新toString
    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
    }
}

class SingleLinkedList {
    private HeroNode head = new HeroNode(0, "", "");//头结点不存储数据

    public HeroNode getHead() {//返回头结点
        return head;
    }

    public void addHero(HeroNode heroNode) {
        HeroNode temp = head;//新建一个临时变量,类似于游标 用作遍历
        while(true) {
            if(temp.next==null) {//如果当前节点的next等于空 说明当前节点就是尾节点
                break;
            }
            temp = temp.next;//游标后移
        }
        temp.next  = heroNode;//加入数据
    }
    
    public void list() {
        if(head.next==null) {
            System.out.println("链表为空,无数据");
            return ;
        }
        HeroNode temp = head.next;
        while(true) {
            if(temp==null) {//如果当前节点的next等于空 说明当前节点就是尾节点
                System.out.println("到尾部了");
                break;
            }
            System.out.println(temp);
            temp = temp.next;//游标后移
        }
    }
}

public class SingleLinkedListDemo {

    public static void main(String[] args) {
        SingleLinkedList linkedList  = new SingleLinkedList();
        HeroNode h1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode h2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode h3 = new HeroNode(3, "吴用", "智多星");
        HeroNode h4 = new HeroNode(4, "林冲", "豹子头");
        HeroNode h5 = new HeroNode(5, "武松", "行者武松");
        HeroNode h6 = new HeroNode(6, "鲁智深", "花和尚");
        HeroNode h7 = new HeroNode(7, "公孙胜", "入云龙");
        
        linkedList.addHero(h1);
        linkedList.addHero(h2);
        linkedList.addHero(h3);
        linkedList.addHero(h4);
        linkedList.addHero(h5);
        linkedList.addHero(h6);
        linkedList.addHero(h7);
        linkedList.list();
    }   
}

根据节点的权重(编号)来给节点加入到正确的位置,及单链表的一些常用操作,及链表的反转,获取倒数第N个元素等等

public class SingleLinkedListDemo {

    public static void main(String[] args) {
        SingleLinkedList linkedList  = new SingleLinkedList();
        HeroNode h1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode h2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode h3 = new HeroNode(3, "吴用", "智多星");
        HeroNode h4 = new HeroNode(4, "林冲", "豹子头");
        HeroNode h5 = new HeroNode(5, "武松", "行者武松");
        HeroNode h6 = new HeroNode(6, "鲁智深", "花和尚");
        HeroNode h7 = new HeroNode(7, "公孙胜", "入云龙");
        
        linkedList.addHeroByOrder(h1);
        linkedList.addHeroByOrder(h2);
        linkedList.addHeroByOrder(h4);
        linkedList.addHeroByOrder(h7);
        linkedList.addHeroByOrder(h7);
        linkedList.addHeroByOrder(h3);
        linkedList.addHeroByOrder(h5);
        linkedList.addHeroByOrder(h6);
        linkedList.list();
        
        System.out.println("更新开始");
        HeroNode newNode = new HeroNode(8, "有用", "小星星");
        linkedList.update(newNode);
        linkedList.list();
        
        System.out.println("删除开始");
        linkedList.del(6);
        linkedList.del(2);
        
        linkedList.list();
        
        int result = getLength(linkedList.getHead());
        System.out.println("链表长度:"+result);
        
        System.out.println("倒数第1个元素节点数据为:"+getLastIndexNode(linkedList.getHead(), 1));
        
        System.out.println("反转前的链表的数据为");
        linkedList.list();
        SingleLinkedList list = revorse(linkedList.getHead());
        System.out.println("反转后的链表的数据为");
        list.list();
        
        System.out.println("逆序打印");
        reversePrint(linkedList.getHead());
        
    }
    
    // 获取链表的有效数据长度(不包含链表头)
    public static int getLength(HeroNode head) {
        HeroNode temp = head;
        int result = 0;
        while(true) {
            if(temp.next ==null) {
                break;
            }
            result++;
            temp = temp.next;
        }
        return result;
    }
    
    /**(新浪面试题)
     * 获取链表的倒数第index个节点 
     * 思路:倒数第index个= 正数第(length+1-index)个
     */
    public static HeroNode getLastIndexNode(HeroNode head,int index) {
        if(head.next ==null) {
            System.out.println("空链表,数据找不到");
        }
        int length = getLength(head);
        if(index <=0 || index > length) {
            System.out.println("参数不合法或者index大于链表长度,获取失败");
            return null;
        }else {
            HeroNode temp = head;
            for(int i =0; i<length-index+1 ;i++) {
                temp  = temp.next;
            }
            return temp;
        }
    }
    
    /** (百度面试题)
     * 根据老链表的数据,反转出一个新的链表,要求元素顺序与老链表顺序相反
     * 思路:从头遍历老链表 根据老元素复制出新元素,然后每复制出一个元素都插入新链表的head与第一个元素之间
     */
    public static  SingleLinkedList revorse(HeroNode head) {
        int length = getLength(head);
        if(length==0) {
            return null;
        }else if(length==1) {
            SingleLinkedList result = new SingleLinkedList();
            result.getHead().next = copyNode(head.next);
            return result;
        }else {
            HeroNode temp = head.next;
            SingleLinkedList result = new SingleLinkedList();
            HeroNode newHead = result.getHead();
            while(true) {
                if(temp == null) {
                    break;
                }
                HeroNode waitNode = temp.next;//缓存下一个被操作的数据
                HeroNode addNode = copyNode(temp);
                addNode.next =  newHead.next;
                newHead.next = addNode;
                temp = waitNode;
            }
            return result;
        }
    }
    
    public static HeroNode copyNode(HeroNode origin) {
        HeroNode newNode = new HeroNode(origin.no, origin.name, origin.nickname);
        return newNode;
    }
    
    /**(腾讯面试题)
     * 链表的逆序打印
     * 思路:先反转 再正序打印, 或者正向遍历 压入栈中,然后依次出栈
     */
    public static void reversePrint(HeroNode head) {
        SingleLinkedList list = revorse(head);
        HeroNode temp = list.getHead().next;
        while(true) {
            if(temp ==null) {
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
}

class SingleLinkedList {
    private HeroNode head = new HeroNode(0, "", "");//头结点不存储数据

    public HeroNode getHead() {//返回头结点
        return head;
    }

    public void addHeroByOrder(HeroNode heroNode) {//根据编号顺序  加入节点,如果原编号已经存在,那么取消添加操作
        HeroNode temp = head;
        boolean flag = false;
        while(true) {
            if(temp.next ==null) {
                break;
            }
            if(temp.next.no == heroNode.no) {
                flag = true;
                break;
            }else if(temp.next.no > heroNode.no) {
                break;
            }
            temp = temp.next;
        }
        if(flag) {
            System.out.println("节点已经存在,添加失败");
        }else {
            heroNode.next = temp.next;//这两行代码即可从尾部添加数据 也可从中间插入数据
            temp.next = heroNode;
        }
    }
    
    // 根据编号修改节点数据
    public void update(HeroNode heroNode) {
        if(head.next == null) {
            System.out.println("链表为空,操作失败");
            return;
        }
        HeroNode temp = head;
        boolean flag = false;//是否找到标识
        while(true) {
            if(temp==null) {
                break;
            }
            if(temp.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        
        if(flag) {
            temp.name = heroNode.name;
            temp.nickname = heroNode.nickname;
        }else {
            System.out.println("数据未找到,更新失败");
        }
    }
    
    public void del(int no) {
        if(head.next == null) {
            System.out.println("链表为空,操作失败");
            return;
        }
        HeroNode temp = head;
        boolean flag = false;
        while(true) {
            if(temp == null) {
                break;
            }
            if(temp.next.no == no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if(flag) {
            temp.next = temp.next.next;//删除
        }else {
            System.out.println("未找到这个数据,删除失败");
        }
        
    }
    
    public void list() {
        if(head.next==null) {
            System.out.println("链表为空,无数据");
            return ;
        }
        HeroNode temp = head.next;
        while(true) {
            if(temp==null) {//如果当前节点的next等于空 说明当前节点就是尾节点
                break;
            }
            System.out.println(temp);
            temp = temp.next;//游标后移
        }
    }
}

//定义HeroNode , 每个HeroNode 对象就是一个节点
class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点
    //构造器
    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }
    //为了显示方法,我们重新toString
    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
    }
}

双向链表
单向链表的缺点:

    public void addByOrder(HeroNode2 heroNode) {
        // 因为head节点不能动,因此我们需要一个辅助遍历 temp
        HeroNode2 temp = head;
        // 遍历链表,找到最后
        boolean flag = false;
        while (true) {
            if(temp.no == heroNode.no) {
                flag = true;
                break;
            }else if(temp.no>heroNode.no) {
                break;
            }
            // 找到链表的最后
            if (temp.next == null) {//
                break;
            }
            // 如果没有找到最后, 将将temp后移
            temp = temp.next;
        }  
        /**
         * 循环退出时 temp可能为2种情况
         * 1 temp为尾节点 我们需要在尾部追加heroNode
         * 2 temp为第一个大于heroNode编号的节点,我们需要将heroNode插入到temp 和temp.pre中间
         */
        if(flag) {
            System.out.println("编号已存在,添加失败");
        }else {
            //中间插入  注意:这里不能使用temp.next==null 来做判断尾部添加还是中间添加的判断条件,
            //因为temp的编号可能大于插入数据的编号 如果temp此时也是尾节点就容易出现问题
            if(temp.no > heroNode.no) { 
                HeroNode2 pre = temp.pre;
                pre.next = heroNode;
                heroNode.next = temp;
                
                temp.pre = heroNode;
                heroNode.pre = pre;
            }else { //尾部插入
                temp.next = heroNode;
                heroNode.pre = temp;
            }
        }
    }

约瑟夫环:
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依规律重复下去,直到圆桌周围的人全部出列。

单向链表实现约瑟夫环

public class Josepfu {
    public static void main(String[] args) {
        CircleSingleLinkedList list = new CircleSingleLinkedList();
        list.addBoy(15);
        list.showBoy();
        list.exitBoy(5, 3, 15);
    }
}


class CircleSingleLinkedList{
    private Boy head = null ;
    
    /**
     * 小孩出圈
     * @param startNo  起始点
     * @param countNo  计数的个数
     * @param nums        圈中小孩的个数,用于快速校验
     */
    public void exitBoy(int startNo, int countNo,int nums) {
        if(startNo<1||startNo>nums || countNo<1 || head ==null) {
            System.out.println("输入的参数有误");
            return ;
        }
        Boy  temp  = head; //每轮游戏的起始点
        Boy  tailer  = head;//尾部节点--每次都位于起始节点的后一位,随着小孩的移动,出圈,起始点和尾点都会变化
        for(int i=0;i<nums-1;i++) {
            tailer = tailer.next;                       
        }
        
        for(int i =0;i < startNo-1; i++) { //temp 移动startNo-1次
            temp = temp.next;
            tailer = tailer.next; 
        }

        while(true) {
            if(tailer==temp) {
                System.out.println("最后剩下的小孩编号是:"+temp.no);
                break;
            }
            for(int i=0;i<countNo-1;i++) {
                temp = temp.next;
                tailer = tailer.next;
            }
            System.out.println("小孩出圈的-是:"+temp.no);
            temp = temp.next;
            tailer.next = temp;
        }
    }
    
    /**
     * 添加小孩
     * @param nums 小孩的数量
     */
    public void  addBoy(int nums) {
        if(nums<1) {
            System.out.println("加入的男孩数量不能小于1");
            return ;
        }
        Boy temp = null;//辅助变量 用于指向最后的节点
        for(int i =1;i<=nums;i++) {
            Boy boy = new Boy(i);
            if(i==1) {
                head  = boy;
                boy.next = head;
                temp = boy;
            }else {
                temp.next = boy;
                temp = boy;
                boy.next = head;
            }
        }
        int a = 11;
        
        int b =a+1;
    }
    
    /**
     * 显示小孩
     */
    public  void showBoy() {
        if(head==null) {
            System.out.println("无数据");
            return ;
        }
        Boy temp = head;
        while(true) {
            System.out.println("圈中的小孩的编号:"+temp.no);
            if(temp.next == head) {//当遍历节点的下一个接待是头节点 代表遍历到为尾部了
                break;
            }
            temp = temp.next;
        }
        
    }
}

class Boy {
    public int no;// 编号
    public Boy next; // 指向下一个节点,默认null
    
    public Boy(int no) {
        this.no = no;
    }
}

使用数组来模拟栈(也可以使用链表来模拟栈)

//定义一个 ArrayStack 表示栈
class ArrayStack {
    private int maxSize; // 栈的大小
    private int[] stack; // 数组,数组模拟栈,数据就放在该数组
    private int top = -1;// top表示栈顶,初始化为-1
    
    //构造器
    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }
    
    //栈满
    public boolean isFull() {
        return top == maxSize - 1;
    }
    //栈空
    public boolean isEmpty() {
        return top == -1;
    }
    //入栈-push
    public void push(int value) {
        //先判断栈是否满
        if(isFull()) {
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }
    //出栈-pop, 将栈顶的数据返回
    public int pop() {
        //先判断栈是否空
        if(isEmpty()) {
            //抛出异常
            throw new RuntimeException("栈空,没有数据~");
        }
        int value = stack[top];
        top--;
        return value;
    }
    //显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
    public void list() {
        if(isEmpty()) {
            System.out.println("栈空,没有数据~~");
            return;
        }
        //需要从栈顶开始显示数据
        for(int i = top; i >= 0 ; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
}

栈实现计数器 暂不考虑4/3的 小数,直接按照int/int

image.png
public class Calculator {

    public static void main(String[] args) {
        String expression = "12*2-5+3-4";
        //创建两个栈,数栈,一个符号栈
        ArrayStack2 numStack = new ArrayStack2(10); 
        ArrayStack2 operStack = new ArrayStack2(10);
        //定义需要的相关变量
        int index = 0;//用于扫描
        int num1 = 0; 
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' '; //将每次扫描得到char保存到ch
        String keepNum = ""; //用于拼接 多位数
        //开始while循环的扫描expression
        while(true) {
            //依次得到expression 的每一个字符
            ch = expression.substring(index, index+1).charAt(0);
            //判断ch是什么,然后做相应的处理
            if(operStack.isOper(ch)) {//如果是运算符
                //判断当前的符号栈是否为空
                if(!operStack.isEmpty()) {
                    //如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,
                    //在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈
                    if(operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1, num2, oper);
                        //把运算的结果如数栈
                        numStack.push(res);
                        //然后将当前的操作符入符号栈
                        operStack.push(ch);
                    } else {
                        //如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
                        operStack.push(ch);
                    }
                }else {
                    //如果为空直接入符号栈..
                    operStack.push(ch); // 1 + 3
                }
            } else { //如果是数,则直接入数栈
                
                //numStack.push(ch - 48); //? "1+3" '1' => 1
                //分析思路
                //1. 当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
                //2. 在处理数,需要向expression的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
                //3. 因此我们需要定义一个变量 字符串,用于拼接
                
                //处理多位数
                keepNum += ch;
                
                //如果ch已经是expression的最后一位,就直接入栈
                if (index == expression.length() - 1) {
                    numStack.push(Integer.parseInt(keepNum));
                }else{
                
                    //判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
                    //注意是看后一位,不是index++
                    if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))) {
                        //如果后一位是运算符,则入栈 keepNum = "1" 或者 "123"
                        numStack.push(Integer.parseInt(keepNum));
                        //重要的!!!!!!, keepNum清空
                        keepNum = "";
                        
                    }
                }
            }
            //让index + 1, 并判断是否扫描到expression最后.
            index++;
            if (index >= expression.length()) {
                break;
            }
        }
        
        //当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
        while(true) {
            //如果符号栈为空,则计算到最后的结果, 数栈中只有一个数字【结果】
            if(operStack.isEmpty()) {
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1, num2, oper);
            numStack.push(res);//入栈
        }
        //将数栈的最后数,pop出,就是结果
        int res2 = numStack.pop();
        System.out.printf("表达式 %s = %d", expression, res2);
    }

}

//先创建一个栈,直接使用前面创建好
//定义一个 ArrayStack2 表示栈, 需要扩展功能
class ArrayStack2 {
    private int maxSize; // 栈的大小
    private int[] stack; // 数组,数组模拟栈,数据就放在该数组
    private int top = -1;// top表示栈顶,初始化为-1
    
    //构造器
    public ArrayStack2(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }
    
    //增加一个方法,可以返回当前栈顶的值, 但是不是真正的pop
    public int peek() {
        return stack[top];
    }
    
    //栈满
    public boolean isFull() {
        return top == maxSize - 1;
    }
    //栈空
    public boolean isEmpty() {
        return top == -1;
    }
    //入栈-push
    public void push(int value) {
        //先判断栈是否满
        if(isFull()) {
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }
    //出栈-pop, 将栈顶的数据返回
    public int pop() {
        //先判断栈是否空
        if(isEmpty()) {
            //抛出异常
            throw new RuntimeException("栈空,没有数据~");
        }
        int value = stack[top];
        top--;
        return value;
    }
    //显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
    public void list() {
        if(isEmpty()) {
            System.out.println("栈空,没有数据~~");
            return;
        }
        //需要从栈顶开始显示数据
        for(int i = top; i >= 0 ; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
    //返回运算符的优先级,优先级是程序员来确定, 优先级使用数字表示
    //数字越大,则优先级就越高.
    public int priority(int oper) {
        if(oper == '*' || oper == '/'){
            return 1;
        } else if (oper == '+' || oper == '-') {
            return 0;
        } else {
            return -1; // 假定目前的表达式只有 +, - , * , /
        }
    }
    //判断是不是一个运算符
    public boolean isOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }
    //计算方法
    public int cal(int num1, int num2, int oper) {
        int res = 0; // res 用于存放计算的结果
        switch (oper) {
        case '+':
            res = num1 + num2;
            break;
        case '-':
            res = num2 - num1;// 注意顺序
            break;
        case '*':
            res = num1 * num2;
            break;
        case '/':
            res = num2 / num1;
            break;
        default:
            break;
        }
        return res;
    }
}

中缀表达式转后缀表达式及逆波兰计数器

image.png

    public static void main(String[] args) {
        
        
        //完成将一个中缀表达式转成后缀表达式的功能
        //说明
        //1. 1+((2+3)×4)-5 => 转成  1 2 3 + 4 × + 5 –
        //2. 因为直接对str 进行操作,不方便,因此 先将  "1+((2+3)×4)-5" =》 中缀的表达式对应的List
        //   即 "1+((2+3)×4)-5" => ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
        //3. 将得到的中缀表达式对应的List => 后缀表达式对应的List
        //   即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]
        
        String expression = "1+((2+3)*4)-5";//注意表达式 
        List<String> infixExpressionList = toInfixExpressionList(expression);
        System.out.println("中缀表达式对应的List=" + infixExpressionList); // ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
        List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
        System.out.println("后缀表达式对应的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–] 
        
        System.out.printf("expression=%d", calculate(suffixExpreesionList)); // ?
        
        
        
        /*
        
        //先定义给逆波兰表达式
        //(30+4)×5-6  => 30 4 + 5 × 6 - => 164
        // 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / + 
        //测试 
        //说明为了方便,逆波兰表达式 的数字和符号使用空格隔开
        //String suffixExpression = "30 4 + 5 * 6 -";
        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
        //思路
        //1. 先将 "3 4 + 5 × 6 - " => 放到ArrayList中
        //2. 将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈 完成计算
        
        List<String> list = getListString(suffixExpression);
        System.out.println("rpnList=" + list);
        int res = calculate(list);
        System.out.println("计算的结果是=" + res);
        
        */
    }
    
    
    
    //即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]
    //方法:将得到的中缀表达式对应的List => 后缀表达式对应的List
    public static List<String> parseSuffixExpreesionList(List<String> ls) {
        //定义两个栈
        Stack<String> s1 = new Stack<String>(); // 符号栈
        //说明:因为s2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
        //因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
        //Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2
        List<String> s2 = new ArrayList<String>(); // 储存中间结果的Lists2
        
        //遍历ls
        for(String item: ls) {
            //如果是一个数,加入s2
            if(item.matches("\\d+")) {
                s2.add(item);
            } else if (item.equals("(")) {
                s1.push(item);
            } else if (item.equals(")")) {
                //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
                while(!s1.peek().equals("(")) {
                    s2.add(s1.pop());
                }
                s1.pop();//!!! 将 ( 弹出 s1栈, 消除小括号
            } else {
                //当item的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
                //问题:我们缺少一个比较优先级高低的方法
                while(s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item) ) {
                    s2.add(s1.pop());
                }
                //还需要将item压入栈
                s1.push(item);
            }
        }
        
        //将s1中剩余的运算符依次弹出并加入s2
        while(s1.size() != 0) {
            s2.add(s1.pop());
        }

        return s2; //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
        
    }
    
    //方法:将 中缀表达式转成对应的List
    //  s="1+((2+3)×4)-5";
    public static List<String> toInfixExpressionList(String s) {
        //定义一个List,存放中缀表达式 对应的内容
        List<String> ls = new ArrayList<String>();
        int i = 0; //这时是一个指针,用于遍历 中缀表达式字符串
        String str; // 对多位数的拼接
        char c; // 每遍历到一个字符,就放入到c
        do {
            //如果c是一个非数字,我需要加入到ls
            if((c=s.charAt(i)) < 48 ||  (c=s.charAt(i)) > 57) {
                ls.add("" + c);
                i++; //i需要后移
            } else { //如果是一个数,需要考虑多位数
                str = ""; //先将str 置成"" '0'[48]->'9'[57]
                while(i < s.length() && (c=s.charAt(i)) >= 48 && (c=s.charAt(i)) <= 57) {
                    str += c;//拼接
                    i++;
                }
                ls.add(str);
            }
        }while(i < s.length());
        return ls;//返回
    }
    
    //将一个逆波兰表达式, 依次将数据和运算符 放入到 ArrayList中
    public static List<String> getListString(String suffixExpression) {
        //将 suffixExpression 分割
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<String>();
        for(String ele: split) {
            list.add(ele);
        }
        return list;
        
    }
    
    //完成对逆波兰表达式的运算
    /*
     * 1)从左至右扫描,将3和4压入堆栈;
        2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
        3)将5入栈;
        4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
        5)将6入栈;
        6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
     */
    
    public static int calculate(List<String> ls) {
        // 创建给栈, 只需要一个栈即可
        Stack<String> stack = new Stack<String>();
        // 遍历 ls
        for (String item : ls) {
            // 这里使用正则表达式来取出数
            if (item.matches("\\d+")) { // 匹配的是多位数
                // 入栈
                stack.push(item);
            } else {
                // pop出两个数,并运算, 再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
                    res = num1 + num2;
                } else if (item.equals("-")) {
                    res = num1 - num2;
                } else if (item.equals("*")) {
                    res = num1 * num2;
                } else if (item.equals("/")) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("运算符有误");
                }
                //把res 入栈
                stack.push("" + res);
            }
            
        }
        //最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }

}

//编写一个类 Operation 可以返回一个运算符 对应的优先级
class Operation {
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;
    
    //写一个方法,返回对应的优先级数字
    public static int getValue(String operation) {
        int result = 0;
        switch (operation) {
        case "+":
            result = ADD;
            break;
        case "-":
            result = SUB;
            break;
        case "*":
            result = MUL;
            break;
        case "/":
            result = DIV;
            break;
        default:
            System.out.println("不存在该运算符" + operation);
            break;
        }
        return result;
    }
    
}
递归

递归所遵循的规则


image.png
上一篇下一篇

猜你喜欢

热点阅读