數(shù)據(jù)結(jié)構(gòu)與算法分析一

線性結(jié)構(gòu)與非線性結(jié)構(gòu)

線性結(jié)構(gòu)

1: 線性結(jié)構(gòu)作為最常用的數(shù)據(jù)結(jié)構(gòu),其特點是數(shù)據(jù)元素之間存在一對一的線性關(guān)系(如:y=f(x))
2 : 線性結(jié)構(gòu)包含兩種存儲結(jié)構(gòu), 順序存儲結(jié)構(gòu)(數(shù)組)鏈式存儲結(jié)構(gòu)(鏈表) 寸痢。順序存儲稱為順序表,其存儲元素是連續(xù)的熙暴,鏈式存儲稱為鏈表卓缰,鏈表中的元素不一定是連續(xù)的 元素節(jié)點存放數(shù)據(jù)元素及相鄰元素的地址信息。
3 常見的線性結(jié)構(gòu)有:數(shù)組掌挚,隊列雨席,鏈表,和棧

非線性結(jié)構(gòu)

1: 與線性結(jié)構(gòu)相對應吠式,非線性結(jié)構(gòu)數(shù)據(jù)元素不存在一對一的對應關(guān)系陡厘,常見的結(jié)構(gòu)有:多維數(shù)組,廣義表特占,樹結(jié)構(gòu)糙置,圖結(jié)構(gòu)

稀疏數(shù)組和隊列

稀疏數(shù)組的定義:

當一個數(shù)組的大部分元素是某個確定的值(0,空,或者其他)是目,可以使用稀疏數(shù)組來保存當前數(shù)組(如果數(shù)據(jù)趨向于兩個數(shù)呢谤饭? 這個我暫時還不知道)。

image.png

稀疏數(shù)組中的第一個元素記錄原數(shù)組的行列及有效數(shù)據(jù)個數(shù)懊纳,后面一次記錄原數(shù)組的有效數(shù)據(jù)
二維數(shù)組與稀疏數(shù)組互相轉(zhuǎn)換

    public static void main(String[] args) {
        // 創(chuàng)建一個原始的二維數(shù)組 11 * 11
        // 0: 表示沒有棋子揉抵, 1 表示 黑子 2 表藍子
        int chessArr1[][] = new int[11][11];
        chessArr1[1][2] = 1;
        chessArr1[2][3] = 2;
        chessArr1[4][5] = 2;
        // 輸出原始的二維數(shù)組
        System.out.println("原始的二維數(shù)組~~");
        for (int[] row : chessArr1) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();
        }

        // 將二維數(shù)組 轉(zhuǎn) 稀疏數(shù)組的思
        // 1. 先遍歷二維數(shù)組 得到非0數(shù)據(jù)的個數(shù)
        int sum = 0;
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if (chessArr1[i][j] != 0) {
                    sum++;
                }
            }
        }

        // 2. 創(chuàng)建對應的稀疏數(shù)組
        int sparseArr[][] = new int[sum + 1][3];
        // 給稀疏數(shù)組賦值
        sparseArr[0][0] = 11;
        sparseArr[0][1] = 11;
        sparseArr[0][2] = sum;
        
        // 遍歷二維數(shù)組,將非0的值存放到 sparseArr中
        int count = 0; //count 用于記錄是第幾個非0數(shù)據(jù)
        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];
                }
            }
        }
        
        // 輸出稀疏數(shù)組的形式
        System.out.println();
        System.out.println("得到稀疏數(shù)組為~~~~");
        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();
        
        //將稀疏數(shù)組 --》 恢復成 原始的二維數(shù)組
        /*
         *  1. 先讀取稀疏數(shù)組的第一行嗤疯,根據(jù)第一行的數(shù)據(jù)冤今,創(chuàng)建原始的二維數(shù)組,比如上面的  chessArr2 = int [11][11]
            2. 在讀取稀疏數(shù)組后幾行的數(shù)據(jù)茂缚,并賦給 原始的二維數(shù)組 即可.
         */
        
        //1. 先讀取稀疏數(shù)組的第一行戏罢,根據(jù)第一行的數(shù)據(jù),創(chuàng)建原始的二維數(shù)組
        
        int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
        
        //2. 在讀取稀疏數(shù)組后幾行的數(shù)據(jù)(從第二行開始)脚囊,并賦給 原始的二維數(shù)組 即可
        
        for(int i = 1; i < sparseArr.length; i++) {
            chessArr2[sparseArr[i][0]][sparseArr[i][1]] =  sparseArr[i][2];
        }
        
        // 輸出恢復后的二維數(shù)組
        System.out.println();
        System.out.println("恢復后的二維數(shù)組");
        
        for (int[] row : chessArr2) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();
        }
    }

隊列

隊列是一個有序列表帖汞,使用使用數(shù)組和鏈表來實現(xiàn),隊列遵循先進先出原則 FIFO凑术。

數(shù)組模擬環(huán)形隊列

待補充
鏈表

鏈表是有序的列表(邏輯上有序,內(nèi)存上無序)所意。
單向鏈表內(nèi)存結(jié)構(gòu)如下

image.png

單向鏈表邏輯結(jié)構(gòu)如下
image.png

鏈表分帶頭結(jié)點的鏈表不帶頭結(jié)點的鏈表

實現(xiàn)鏈表功能--尾部追加數(shù)據(jù)節(jié)點和單鏈表的遍歷

class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一個節(jié)點
    //構(gòu)造器
    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, "", "");//頭結(jié)點不存儲數(shù)據(jù)

    public HeroNode getHead() {//返回頭結(jié)點
        return head;
    }

    public void addHero(HeroNode heroNode) {
        HeroNode temp = head;//新建一個臨時變量催首,類似于游標 用作遍歷
        while(true) {
            if(temp.next==null) {//如果當前節(jié)點的next等于空 說明當前節(jié)點就是尾節(jié)點
                break;
            }
            temp = temp.next;//游標后移
        }
        temp.next  = heroNode;//加入數(shù)據(jù)
    }
    
    public void list() {
        if(head.next==null) {
            System.out.println("鏈表為空,無數(shù)據(jù)");
            return ;
        }
        HeroNode temp = head.next;
        while(true) {
            if(temp==null) {//如果當前節(jié)點的next等于空 說明當前節(jié)點就是尾節(jié)點
                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();
    }   
}

根據(jù)節(jié)點的權(quán)重(編號)來給節(jié)點加入到正確的位置泄鹏,及單鏈表的一些常用操作郎任,及鏈表的反轉(zhuǎn),獲取倒數(shù)第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("倒數(shù)第1個元素節(jié)點數(shù)據(jù)為:"+getLastIndexNode(linkedList.getHead(), 1));
        
        System.out.println("反轉(zhuǎn)前的鏈表的數(shù)據(jù)為");
        linkedList.list();
        SingleLinkedList list = revorse(linkedList.getHead());
        System.out.println("反轉(zhuǎn)后的鏈表的數(shù)據(jù)為");
        list.list();
        
        System.out.println("逆序打印");
        reversePrint(linkedList.getHead());
        
    }
    
    // 獲取鏈表的有效數(shù)據(jù)長度(不包含鏈表頭)
    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;
    }
    
    /**(新浪面試題)
     * 獲取鏈表的倒數(shù)第index個節(jié)點 
     * 思路:倒數(shù)第index個= 正數(shù)第(length+1-index)個
     */
    public static HeroNode getLastIndexNode(HeroNode head,int index) {
        if(head.next ==null) {
            System.out.println("空鏈表备籽,數(shù)據(jù)找不到");
        }
        int length = getLength(head);
        if(index <=0 || index > length) {
            System.out.println("參數(shù)不合法或者index大于鏈表長度,獲取失敗");
            return null;
        }else {
            HeroNode temp = head;
            for(int i =0; i<length-index+1 ;i++) {
                temp  = temp.next;
            }
            return temp;
        }
    }
    
    /** (百度面試題)
     * 根據(jù)老鏈表的數(shù)據(jù)舶治,反轉(zhuǎn)出一個新的鏈表,要求元素順序與老鏈表順序相反
     * 思路:從頭遍歷老鏈表 根據(jù)老元素復制出新元素车猬,然后每復制出一個元素都插入新鏈表的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;//緩存下一個被操作的數(shù)據(jù)
                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;
    }
    
    /**(騰訊面試題)
     * 鏈表的逆序打印
     * 思路:先反轉(zhuǎn) 再正序打印霉猛, 或者正向遍歷 壓入棧中,然后依次出棧
     */
    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, "", "");//頭結(jié)點不存儲數(shù)據(jù)

    public HeroNode getHead() {//返回頭結(jié)點
        return head;
    }

    public void addHeroByOrder(HeroNode heroNode) {//根據(jù)編號順序  加入節(jié)點,如果原編號已經(jīng)存在珠闰,那么取消添加操作
        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("節(jié)點已經(jīng)存在惜浅,添加失敗");
        }else {
            heroNode.next = temp.next;//這兩行代碼即可從尾部添加數(shù)據(jù) 也可從中間插入數(shù)據(jù)
            temp.next = heroNode;
        }
    }
    
    // 根據(jù)編號修改節(jié)點數(shù)據(jù)
    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("數(shù)據(jù)未找到,更新失敗");
        }
    }
    
    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("未找到這個數(shù)據(jù)坛悉,刪除失敗");
        }
        
    }
    
    public void list() {
        if(head.next==null) {
            System.out.println("鏈表為空,無數(shù)據(jù)");
            return ;
        }
        HeroNode temp = head.next;
        while(true) {
            if(temp==null) {//如果當前節(jié)點的next等于空 說明當前節(jié)點就是尾節(jié)點
                break;
            }
            System.out.println(temp);
            temp = temp.next;//游標后移
        }
    }
}

//定義HeroNode 承绸, 每個HeroNode 對象就是一個節(jié)點
class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一個節(jié)點
    //構(gòu)造器
    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 + "]";
    }
}

雙向鏈表
單向鏈表的缺點:

  • 只能向一個方向遍歷,而雙向鏈表遍歷是雙向的
  • 單向鏈表不能自我刪除 刪除時军熏,我們總需要找到待刪除節(jié)點的前一個節(jié)點temp,利用temp.next = temp.next.next;來實現(xiàn)刪除轩猩,而雙向鏈表的刪除則簡單一點 我們先找到待刪除節(jié)點temp,執(zhí)行temp.pre.next=temp.next, 再執(zhí)行 temp.next.pre=temp.pre;
    其他與單向鏈表不一致的地方:
  • 添加節(jié)點,1 尾部添加, 先找到尾部節(jié)點temp羞迷,然后執(zhí)行 temp.next = heroNode; heroNode.pre = temp;
    2中間添加: 具體見代碼
    public void addByOrder(HeroNode2 heroNode) {
        // 因為head節(jié)點不能動界轩,因此我們需要一個輔助遍歷 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;
        }  
        /**
         * 循環(huán)退出時 temp可能為2種情況
         * 1 temp為尾節(jié)點 我們需要在尾部追加heroNode
         * 2 temp為第一個大于heroNode編號的節(jié)點衔瓮,我們需要將heroNode插入到temp 和temp.pre中間
         */
        if(flag) {
            System.out.println("編號已存在浊猾,添加失敗");
        }else {
            //中間插入  注意:這里不能使用temp.next==null 來做判斷尾部添加還是中間添加的判斷條件,
            //因為temp的編號可能大于插入數(shù)據(jù)的編號 如果temp此時也是尾節(jié)點就容易出現(xiàn)問題
            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;
            }
        }
    }

約瑟夫環(huán):
約瑟夫環(huán)(約瑟夫問題)是一個數(shù)學的應用問題:已知n個人(以編號1热鞍,2葫慎,3...n分別表示)圍坐在一張圓桌周圍。從編號為k的人開始報數(shù)薇宠,數(shù)到m的那個人出列偷办;他的下一個人又從1開始報數(shù),數(shù)到m的那個人又出列澄港;依規(guī)律重復下去椒涯,直到圓桌周圍的人全部出列。

單向鏈表實現(xiàn)約瑟夫環(huán)

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  計數(shù)的個數(shù)
     * @param nums        圈中小孩的個數(shù)回梧,用于快速校驗
     */
    public void exitBoy(int startNo, int countNo,int nums) {
        if(startNo<1||startNo>nums || countNo<1 || head ==null) {
            System.out.println("輸入的參數(shù)有誤");
            return ;
        }
        Boy  temp  = head; //每輪游戲的起始點
        Boy  tailer  = head;//尾部節(jié)點--每次都位于起始節(jié)點的后一位废岂,隨著小孩的移動祖搓,出圈,起始點和尾點都會變化
        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 小孩的數(shù)量
     */
    public void  addBoy(int nums) {
        if(nums<1) {
            System.out.println("加入的男孩數(shù)量不能小于1");
            return ;
        }
        Boy temp = null;//輔助變量 用于指向最后的節(jié)點
        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("無數(shù)據(jù)");
            return ;
        }
        Boy temp = head;
        while(true) {
            System.out.println("圈中的小孩的編號:"+temp.no);
            if(temp.next == head) {//當遍歷節(jié)點的下一個接待是頭節(jié)點 代表遍歷到為尾部了
                break;
            }
            temp = temp.next;
        }
        
    }
}

class Boy {
    public int no;// 編號
    public Boy next; // 指向下一個節(jié)點,默認null
    
    public Boy(int no) {
        this.no = no;
    }
}

使用數(shù)組來模擬棧(也可以使用鏈表來模擬棧)

  • 定義棧頂top ,初始化為-1
  • 數(shù)據(jù)入棧: top++湖苞; stack[top] = data;
  • 數(shù)據(jù)出棧: value = stack[top] ; top-- ;return value;
//定義一個 ArrayStack 表示棧
class ArrayStack {
    private int maxSize; // 棧的大小
    private int[] stack; // 數(shù)組拯欧,數(shù)組模擬棧,數(shù)據(jù)就放在該數(shù)組
    private int top = -1;// top表示棧頂财骨,初始化為-1
    
    //構(gòu)造器
    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, 將棧頂?shù)臄?shù)據(jù)返回
    public int pop() {
        //先判斷棧是否空
        if(isEmpty()) {
            //拋出異常
            throw new RuntimeException("棧空隆箩,沒有數(shù)據(jù)~");
        }
        int value = stack[top];
        top--;
        return value;
    }
    //顯示棧的情況[遍歷棧]该贾, 遍歷時,需要從棧頂開始顯示數(shù)據(jù)
    public void list() {
        if(isEmpty()) {
            System.out.println("椪觯空靶庙,沒有數(shù)據(jù)~~");
            return;
        }
        //需要從棧頂開始顯示數(shù)據(jù)
        for(int i = top; i >= 0 ; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
}

棧實現(xiàn)計數(shù)器 暫不考慮4/3的 小數(shù),直接按照int/int

image.png

public class Calculator {

    public static void main(String[] args) {
        String expression = "12*2-5+3-4";
        //創(chuàng)建兩個棧娃属,數(shù)棧六荒,一個符號棧
        ArrayStack2 numStack = new ArrayStack2(10); 
        ArrayStack2 operStack = new ArrayStack2(10);
        //定義需要的相關(guān)變量
        int index = 0;//用于掃描
        int num1 = 0; 
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' '; //將每次掃描得到char保存到ch
        String keepNum = ""; //用于拼接 多位數(shù)
        //開始while循環(huán)的掃描expression
        while(true) {
            //依次得到expression 的每一個字符
            ch = expression.substring(index, index+1).charAt(0);
            //判斷ch是什么,然后做相應的處理
            if(operStack.isOper(ch)) {//如果是運算符
                //判斷當前的符號棧是否為空
                if(!operStack.isEmpty()) {
                    //如果符號棧有操作符矾端,就進行比較,如果當前的操作符的優(yōu)先級小于或者等于棧中的操作符,就需要從數(shù)棧中pop出兩個數(shù),
                    //在從符號棧中pop出一個符號掏击,進行運算,將得到結(jié)果秩铆,入數(shù)棧砚亭,然后將當前的操作符入符號棧
                    if(operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1, num2, oper);
                        //把運算的結(jié)果如數(shù)棧
                        numStack.push(res);
                        //然后將當前的操作符入符號棧
                        operStack.push(ch);
                    } else {
                        //如果當前的操作符的優(yōu)先級大于棧中的操作符, 就直接入符號棧.
                        operStack.push(ch);
                    }
                }else {
                    //如果為空直接入符號棧..
                    operStack.push(ch); // 1 + 3
                }
            } else { //如果是數(shù)殴玛,則直接入數(shù)棧
                
                //numStack.push(ch - 48); //? "1+3" '1' => 1
                //分析思路
                //1. 當處理多位數(shù)時捅膘,不能發(fā)現(xiàn)是一個數(shù)就立即入棧,因為他可能是多位數(shù)
                //2. 在處理數(shù)滚粟,需要向expression的表達式的index 后再看一位,如果是數(shù)就進行掃描寻仗,如果是符號才入棧
                //3. 因此我們需要定義一個變量 字符串,用于拼接
                
                //處理多位數(shù)
                keepNum += ch;
                
                //如果ch已經(jīng)是expression的最后一位凡壤,就直接入棧
                if (index == expression.length() - 1) {
                    numStack.push(Integer.parseInt(keepNum));
                }else{
                
                    //判斷下一個字符是不是數(shù)字署尤,如果是數(shù)字,就繼續(xù)掃描亚侠,如果是運算符曹体,則入棧
                    //注意是看后一位,不是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;
            }
        }
        
        //當表達式掃描完畢箕别,就順序的從 數(shù)棧和符號棧中pop出相應的數(shù)和符號,并運行.
        while(true) {
            //如果符號棧為空,則計算到最后的結(jié)果, 數(shù)棧中只有一個數(shù)字【結(jié)果】
            if(operStack.isEmpty()) {
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1, num2, oper);
            numStack.push(res);//入棧
        }
        //將數(shù)棧的最后數(shù)究孕,pop出啥酱,就是結(jié)果
        int res2 = numStack.pop();
        System.out.printf("表達式 %s = %d", expression, res2);
    }

}

//先創(chuàng)建一個棧,直接使用前面創(chuàng)建好
//定義一個 ArrayStack2 表示棧, 需要擴展功能
class ArrayStack2 {
    private int maxSize; // 棧的大小
    private int[] stack; // 數(shù)組,數(shù)組模擬棧厨诸,數(shù)據(jù)就放在該數(shù)組
    private int top = -1;// top表示棧頂,初始化為-1
    
    //構(gòu)造器
    public ArrayStack2(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }
    
    //增加一個方法禾酱,可以返回當前棧頂?shù)闹? 但是不是真正的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, 將棧頂?shù)臄?shù)據(jù)返回
    public int pop() {
        //先判斷棧是否空
        if(isEmpty()) {
            //拋出異常
            throw new RuntimeException("棧空颤陶,沒有數(shù)據(jù)~");
        }
        int value = stack[top];
        top--;
        return value;
    }
    //顯示棧的情況[遍歷棧]颗管, 遍歷時,需要從棧頂開始顯示數(shù)據(jù)
    public void list() {
        if(isEmpty()) {
            System.out.println("椬易撸空垦江,沒有數(shù)據(jù)~~");
            return;
        }
        //需要從棧頂開始顯示數(shù)據(jù)
        for(int i = top; i >= 0 ; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
    //返回運算符的優(yōu)先級,優(yōu)先級是程序員來確定, 優(yōu)先級使用數(shù)字表示
    //數(shù)字越大搅方,則優(yōu)先級就越高.
    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 用于存放計算的結(jié)果
        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;
    }
}

中綴表達式轉(zhuǎn)后綴表達式及逆波蘭計數(shù)器

image.png


    public static void main(String[] args) {
        
        
        //完成將一個中綴表達式轉(zhuǎn)成后綴表達式的功能
        //說明
        //1. 1+((2+3)×4)-5 => 轉(zhuǎn)成  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 / + 
        //測試 
        //說明為了方便姨涡,逆波蘭表達式 的數(shù)字和符號使用空格隔開
        //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("計算的結(jié)果是=" + 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 這個棧,在整個轉(zhuǎn)換過程中,沒有pop操作,而且后面我們還需要逆序輸出
        //因此比較麻煩黍聂,這里我們就不用 Stack<String> 直接使用 List<String> s2
        //Stack<String> s2 = new Stack<String>(); // 儲存中間結(jié)果的棧s2
        List<String> s2 = new ArrayList<String>(); // 儲存中間結(jié)果的Lists2
        
        //遍歷ls
        for(String item: ls) {
            //如果是一個數(shù)汰现,加入s2
            if(item.matches("\\d+")) {
                s2.add(item);
            } else if (item.equals("(")) {
                s1.push(item);
            } else if (item.equals(")")) {
                //如果是右括號“)”,則依次彈出s1棧頂?shù)倪\算符册踩,并壓入s2,直到遇到左括號為止,此時將這一對括號丟棄
                while(!s1.peek().equals("(")) {
                    s2.add(s1.pop());
                }
                s1.pop();//!!! 將 ( 彈出 s1棧间狂, 消除小括號
            } else {
                //當item的優(yōu)先級小于等于s1棧頂運算符, 將s1棧頂?shù)倪\算符彈出并加入到s2中,再次轉(zhuǎn)到(4.1)與s1中新的棧頂運算符相比較
                //問題:我們?nèi)鄙僖粋€比較優(yōu)先級高低的方法
                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
        
    }
    
    //方法:將 中綴表達式轉(zhuǎn)成對應的List
    //  s="1+((2+3)×4)-5";
    public static List<String> toInfixExpressionList(String s) {
        //定義一個List,存放中綴表達式 對應的內(nèi)容
        List<String> ls = new ArrayList<String>();
        int i = 0; //這時是一個指針哗蜈,用于遍歷 中綴表達式字符串
        String str; // 對多位數(shù)的拼接
        char c; // 每遍歷到一個字符前标,就放入到c
        do {
            //如果c是一個非數(shù)字,我需要加入到ls
            if((c=s.charAt(i)) < 48 ||  (c=s.charAt(i)) > 57) {
                ls.add("" + c);
                i++; //i需要后移
            } else { //如果是一個數(shù)距潘,需要考慮多位數(shù)
                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;//返回
    }
    
    //將一個逆波蘭表達式炼列, 依次將數(shù)據(jù)和運算符 放入到 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,由此得出最終結(jié)果
     */
    
    public static int calculate(List<String> ls) {
        // 創(chuàng)建給棧, 只需要一個棧即可
        Stack<String> stack = new Stack<String>();
        // 遍歷 ls
        for (String item : ls) {
            // 這里使用正則表達式來取出數(shù)
            if (item.matches("\\d+")) { // 匹配的是多位數(shù)
                // 入棧
                stack.push(item);
            } else {
                // pop出兩個數(shù)泥技,并運算浆兰, 再入棧
                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中的數(shù)據(jù)是運算結(jié)果
        return Integer.parseInt(stack.pop());
    }

}

//編寫一個類 Operation 可以返回一個運算符 對應的優(yōu)先級
class Operation {
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;
    
    //寫一個方法,返回對應的優(yōu)先級數(shù)字
    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;
    }
    
}
遞歸

遞歸所遵循的規(guī)則


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末珊豹,一起剝皮案震驚了整個濱河市簸呈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌平夜,老刑警劉巖蝶棋,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忽妒,居然都是意外死亡玩裙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門段直,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吃溅,“玉大人,你說我怎么就攤上這事鸯檬【龀蓿” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵喧务,是天一觀的道長赖歌。 經(jīng)常有香客問我,道長功茴,這世上最難降的妖魔是什么庐冯? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮坎穿,結(jié)果婚禮上展父,老公的妹妹穿的比我還像新娘返劲。我一直安慰自己,他們只是感情好栖茉,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布篮绿。 她就那樣靜靜地躺著,像睡著了一般吕漂。 火紅的嫁衣襯著肌膚如雪亲配。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天痰娱,我揣著相機與錄音弃榨,去河邊找鬼。 笑死梨睁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的娜饵。 我是一名探鬼主播坡贺,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼箱舞!你這毒婦竟也來了遍坟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤晴股,失蹤者是張志新(化名)和其女友劉穎愿伴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體电湘,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡隔节,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寂呛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怎诫。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贷痪,靈堂內(nèi)的尸體忽然破棺而出幻妓,到底是詐尸還是另有隱情,我是刑警寧澤劫拢,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布肉津,位于F島的核電站,受9級特大地震影響舱沧,放射性物質(zhì)發(fā)生泄漏妹沙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一狗唉、第九天 我趴在偏房一處隱蔽的房頂上張望初烘。 院中可真熱鬧,春花似錦、人聲如沸肾筐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吗铐。三九已至东亦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唬渗,已是汗流浹背典阵。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留镊逝,地道東北人壮啊。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像撑蒜,于是被迫代替她去往敵國和親歹啼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容