美文网首页
数据结构与算法分析一

数据结构与算法分析一

作者: Teemo_fca4 | 来源:发表于2019-07-09 16:36 被阅读0次
    线性结构与非线性结构

    线性结构

    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 + "]";
        }
    }
    

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

    • 只能向一个方向遍历,而双向链表遍历是双向的
    • 单向链表不能自我删除 删除时,我们总需要找到待删除节点的前一个节点temp,利用temp.next = temp.next.next;来实现删除,而双向链表的删除则简单一点 我们先找到待删除节点temp,执行temp.pre.next=temp.next, 再执行 temp.next.pre=temp.pre;
      其他与单向链表不一致的地方:
    • 添加节点,1 尾部添加, 先找到尾部节点temp,然后执行 temp.next = heroNode; heroNode.pre = temp;
      2中间添加: 具体见代码
        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;
        }
    }
    

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

    • 定义栈顶top ,初始化为-1
    • 数据入栈: top++; stack[top] = data;
    • 数据出栈: value = stack[top] ; top-- ;return value;
    //定义一个 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

    相关文章

      网友评论

          本文标题:数据结构与算法分析一

          本文链接:https://www.haomeiwen.com/subject/flonhctx.html