線性結(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ù)呢谤饭? 這個我暫時還不知道)。
稀疏數(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)如下
單向鏈表邏輯結(jié)構(gòu)如下
鏈表分帶頭結(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
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ù)器
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ī)則