线性结构与非线性结构
线性结构
1: 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系(如:y=f(x))
2 : 线性结构包含两种存储结构, 顺序存储结构(数组)和链式存储结构(链表) 。顺序存储称为顺序表,其存储元素是连续的,链式存储称为链表,链表中的元素不一定是连续的 元素节点存放数据元素及相邻元素的地址信息。
3 常见的线性结构有:数组,队列,链表,和栈
非线性结构
1: 与线性结构相对应,非线性结构数据元素不存在一对一的对应关系,常见的结构有:多维数组,广义表,树结构,图结构
稀疏数组和队列
稀疏数组的定义:
image.png当一个数组的大部分元素是某个确定的值(0,空,或者其他),可以使用稀疏数组来保存当前数组(如果数据趋向于两个数呢? 这个我暂时还不知道)。
稀疏数组中的第一个元素记录原数组的行列及有效数据个数,后面一次记录原数组的有效数据
二维数组与稀疏数组互相转换
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
链表分带头结点的链表和不带头结点的链表
实现链表功能--尾部追加数据节点和单链表的遍历
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
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;
}
}
中缀表达式转后缀表达式及逆波兰计数器
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
网友评论