栈
中缀表达式(我们日常使用的式子)的简易计算器
public class Calculator {
public static void main(String[] args) {
String str = "360*360*9+265/360-360";
Calculator calculator = new Calculator();
StringBuffer keepNum = new StringBuffer();
keepNum.append("结果:");
System.out.println(keepNum);//lang包下的自动调用toString,下面的keepNum用StringBuffer,要显示调用
System.out.println(calculator.Calculator(str));
}
//中缀表达式的简易计算器
public int Calculator(String expression) {
LinkStack numStack = new LinkStack();
LinkStack operStack = new LinkStack();
int index = 0;//扫描指针
int num1 = 0;
int num2 = 0;
int oper = 0;
int result = 0;
char ch;//每次扫描表达式得到的char保存到这里
String keepNum = "";//用于拼接多位数
//扫描式子
while (true) {
ch = expression.charAt(index++);
if (!isOper(ch)) {//判断是不是数
keepNum += ch;//单位数的可能
//注意越界的问题,字符串下标从0开始
for (; index < expression.length() && !isOper(expression.charAt(index)); ++index) {
ch = expression.charAt(index);
keepNum += ch;
}
numStack.push(Integer.parseInt(keepNum));//记得转会十进制数在放进栈
//numStack.push(ch-48);//要根据ASCII表将字符转回数字,这个只适合单位数
keepNum = "";//清空,供下一轮使用
} else {
if (!operStack.isEmpty()) {
//符号栈不为空,比较符号的优先级
//如果当前运算符的优先级小于或等于栈中的运算符,要从数栈pop出两数,符号栈中pop出符号,先进行运算
if (priority(ch) <= priority((Character) operStack.peek())) {
num1 = (int) numStack.pop();
num2 = (int) numStack.pop();
oper = (char) operStack.pop();
result = calculate(num1, num2, oper);
numStack.push(result);//把结果压入栈
operStack.push(ch);//把当前运算符压入栈
} else {
operStack.push(ch);
}
} else {
//符号栈为空直接入栈
operStack.push(ch);
}
}
//判断是否扫描到最后
if (index >= expression.length()) break;
}
//扫描完,开始计算
while (true) {
if (operStack.isEmpty()) break;
num1 = (int) numStack.pop();
num2 = (int) numStack.pop();
oper = (char) operStack.pop();
result = calculate(num1, num2, oper);
numStack.push(result);//把结果压入栈
}
return (int) numStack.pop();
}
//判断是不是一个运算符
public boolean isOper(char val) {
return val == '+' || val == '-' || val == '*' || val == '/';
}
//用于计算的方法
public int calculate(int num1, int num2, int oper) {
int result = 0;
switch (oper) {
case '+':
result = num1 + num2;
break;
case '-':
result = num2 - num1;
break;
case '*':
result = num1 * num2;
break;
case '/':
result = num2 / num1;
break;
}
return result;
}
//自定义一个运算符的优先级,1表示乘除,0表示加减,其他返回-1
public int priority(char oper) {
if (oper == '*' || oper == '/') {
return 1;
} else if (oper == '+' || oper == '-') {
return 0;
} else {
return -1;
}
}
}
逆波兰计算器
中缀表达式转后缀表达式
public class CalculatorPro {
//为了后面的方便,先把中缀表达式存到List
public static List<String> toList(String s) {
List<String> list = new ArrayList<String>();
int index = 0;//扫描字符串的指针
String str;//用于拼接多位数
char c;
do {
//如果不是数
if ((c = s.charAt(index)) < 48 || (c = s.charAt(index)) > 57) {
list.add("" + c);
index++;
} else {//是数的话考虑多位数拼接
str = "";
while (index < s.length() && (c = s.charAt(index)) > 48 && (c = s.charAt(index)) < 57) {
str += c;
index++;
}
list.add(str);
}
} while (index < s.length());
return list;
}
//把中缀表达式的List转为后缀表达式的List
public static List<String> parseSuffix(List<String> list) {
Stack<String> stack = new Stack<>();//符号栈
List<String> s = new ArrayList<>();//中间结果的栈改为List,更灵活
for (String s1 : list) {
//如果是一个数直接放进s
if (s1.matches("\\d+")) {
s.add(s1);
} else if (s1.equals("(")) {
stack.push(s1);//入符号栈
} else if (s1.equals(")")) {
while (!stack.peek().equals("(")) {
s.add(stack.pop());
}
stack.pop();//去除"("
} else {
//比较当前符号的优先级,如果符号栈的优先级高,将其取出存到s,再把当前符号入栈
while (stack.size() != 0 && Operation.priority(stack.peek()) >= Operation.priority(s1)) {
s.add(stack.pop());
}
stack.push(s1);
}
}
//遍历完之后,把符号栈剩下的运算符,也存到s
while (stack.size() != 0) {
s.add(stack.pop());
}
return s;//最后得到逆波兰表达式
}
//把后缀表达式进行计算
public static int Calculate(String string) {
List<String> list = toList(string);
List<String> stringList = parseSuffix(list);
Stack<String> stack = new Stack<>();
for (String s : stringList) {
if (s.matches("\\d+")) {
stack.push(s);
} else {
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int result = 0;
switch (s) {
case "+":
result = num1 + num2;
break;
case "-":
result = num2 - num1;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num2 / num1;
break;
}
stack.push("" + result);
}
}
return Integer.parseInt(stack.pop());
}
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 priority(String oper) {
int result = 0;
switch (oper) {
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
default:
System.out.println("不存在改运算符");
break;
}
return result;
}
}
递归与回溯
迷宫
public class Maze {
public static void main(String[] args) {
//创建一个二维数组,模拟迷宫地图
int[][] map = new int[8][7];
//1表示墙,上下置为一
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//左右置为一 、
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//设置内部的挡板
map[3][1] = 1;
map[3][2] = 1;
// map[1][2] = 1;
// map[2][2] = 1;
System.out.println("地图的情况");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
//使用递归回溯给小球找路
setWay(map, 1, 1);
// setWay2(map, 1, 1);
//输出新的地图, 小球走过,并标识过的递归
System.out.println("小球走过,并标识过的 地图的情况");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
/*找到通路,返回true,否则返回false
约定: 当 map[i][j] 为 0 表示该点没有走过 当为 1 表示墙 ; 2 表示通路可以走 ; 3 表示该点已经
走过,但是走不通
按照策略 下->右->上->左 走
*/
public static boolean setWay(int[][] map, int i, int j) {
if (map[6][5] == 2) {
return true;//走到出口
} else {
if (map[i][j] == 0) {//还没走过
map[i][j] = 2;
if (setWay(map, i + 1, j)) {
return true;
} else if (setWay(map, i, j + 1)) {
return true;
} else if (setWay(map, i - 1, j)) {
return true;
} else if (setWay(map, i, j - 1)) {
return true;
} else {
map[i][j] = 3;//已经走过,或者是死路
return false;
}
} else {
//走过,是墙,死路
return false;
}
}
}
}
八皇后
public class Queue8 {
//定义一个 max 表示共有多少个皇后
int max = 8;
//定义数组 array, 保存皇后放置位置的结果,比如 arr = {0 , 4, 7, 5, 2, 6, 1, 3}
int[] array = new int[max];
static int count = 0;
static int judgeCount = 0;
public static void main(String[] args) {
//测试一把 , 8 皇后是否正确
Queue8 queue8 = new Queue8();
queue8.check(0);
System.out.printf("一共有%d 解法", count);
System.out.printf("一共判断冲突的次数%d 次", judgeCount); // 1.5w
}
//编写一个方法,放置第 n 个皇后
//特别注意: check 是 每一次递归时,进入到 check 中都有 for(int i = 0; i < max; i++),因此会有回溯
private void check(int n) {
if (n == max) { //n = 8是第九个皇后, 其实 8 个皇后就放好
print();
return;
}
//依次放入皇后,并判断是否冲突
for (int i = 0; i < max; i++) {
//先把当前这个皇后 n , 放到该行的第 1 列
array[n] = i;
//判断当放置第 n 个皇后到 i 列时,是否冲
if (judge(n)) { // 不冲突
//接着放 n+1 个皇后,即开始递归
check(n + 1); //
}
//如果冲突,就继续执行 array[n] = i; 即将第 n 个皇后,放置在本行得 后移的一个位置
}
}
//查看当我们放置第 n 个皇后, 就去检测该皇后是否和前面已经摆放的皇后冲突
/**
* @param n 表示第 n 个皇后
* @return
*/
private boolean judge(int n) {
judgeCount++;
for (int i = 0; i < n; i++) {
// 说明
//1. array[i] == array[n] 表示判断 第 n 个皇后是否和前面的 n-1 个皇后在同一列
//2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示判断第 n 个皇后是否和第 i 皇后是否在同一斜线
// n = 1 放置第 2 列 1 n = 1 array[1] = 1
// Math.abs(1-0) == 1 Math.abs(array[n] - array[i]) = Math.abs(1-0) = 1
//3. 判断是否在同一行, 没有必要,n 每次都在递增
if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
return false;
}
}
return true;
}
//写一个方法,可以将皇后摆放的位置输出
private void print() {
count++;
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
}
查找
二分查找的递归写法
public static int BinarySearch(int[] arr, int left, int right, int target) {
System.out.println("1111");
int mid = (left + right) >>> 1;
int midval = arr[mid];
if (left > right) return -1;
if (midval < target) {
return BinarySearch(arr, mid + 1, right, target);
} else if (midval > target) {
return BinarySearch(arr, left, mid - 1, target);
} else {
return mid;
}
}
//优化,有多个重复的情况
public static ArrayList<Integer> BinarySearch2(int[] arr, int left, int right, int target) {
System.out.println("2222");
int mid = (left + right) >>> 1;
int midval = arr[mid];
if (left > right) return new ArrayList();
if (midval < target) {
return BinarySearch2(arr, mid + 1, right, target);
} else if (midval > target) {
return BinarySearch2(arr, left, mid - 1, target);
} else {
ArrayList<Integer> list = new ArrayList<>();
int temp = mid - 1;
while (true) {
if (temp < 0 || arr[temp] != target) break;
list.add(temp);
temp--;
}
list.add(mid);
temp = mid + 1;
while (true) {
if (temp > arr.length || arr[temp] != target) break;
list.add(temp);
temp++;
}
return list;
}
}
二分查找改版(插值查找)
插值查找,其实就是mid改用一个公式
//自适应的二分查找,要求,数组有序,分布均匀,这时比较快,一般一次就找出,
//如果,分布不均匀,一数超大,一个很小(一个几十,一个几千)就比普通的二分要慢
//数组无序会越界异常
public static int BinarySearch3(int[] arr, int left, int right, int target) {
System.out.println("33333");
int mid = left + (right - left) * (target - arr[left]) / (arr[right] - arr[left]);
int midval = arr[mid];
if (left > right) return -1;
if (midval < target||target < arr[0]||target > arr[arr.length-1]) {
return BinarySearch3(arr, mid + 1, right, target);
} else if (midval > target) {
return BinarySearch3(arr, left, mid - 1, target);
} else {
return mid;
}
}
哈希表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
gogle 公司的一个上机题:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址.),当输入该员工的 id 时, 要求查找到该员工的 所有信息. 要求:
- 不使用数据库,速度越快越好=>哈希表(散列)
- 添加时,保证按照 id 从低到高插入 [思考:如果 id 不是从低到高插入,但要求各条链表仍是从低到
高,怎么解决?] - 使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
public class HashTabDemo {
public static void main(String[] args) {
//创建哈希表
HashTab hashTab = new HashTab(7);
//写一个简单的菜单
String key = "";
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("add: 添加雇员");
System.out.println("list: 显示雇员");
System.out.println("find: 查找雇员");
System.out.println("exit: 退出系统");
key = scanner.next();
switch (key) {
case "add":
System.out.println("输入 id");
int id = scanner.nextInt();
System.out.println("输入名字");
String name = scanner.next();
//创建 雇
Emp emp = new Emp(id, name);
hashTab.add(emp);
break;
case "list":
hashTab.list();
break;
case "find":
System.out.println("请输入要查找的 id");
id = scanner.nextInt();
hashTab.findEmpById(id);
break;
case "exit":
scanner.close();
System.exit(0);
default:
break;
}
}
}
}
//创建 HashTab 管理多条链表
class HashTab {
private EmpLinkedList[] empLinkedListArray;
private int size; //表示有多少条链表
//构造器
public HashTab(int size) {
this.size = size;
//初始化 empLinkedListArray
empLinkedListArray = new EmpLinkedList[size];
//?留一个坑, 这时不要分别初始化每个链表
for (int i = 0; i < size; i++) {
empLinkedListArray[i] = new EmpLinkedList();
}
}
//添加雇员
public void add(Emp emp) {
//根据员工的 id ,得到该员工应当添加到哪条链表
int empLinkedListNO = hashFun(emp.id);
//将 emp 添加到对应的链表中
empLinkedListArray[empLinkedListNO].add(emp);
}
//遍历所有的链表,遍历 hashtab
public void list() {
for (int i = 0; i < size; i++) {
empLinkedListArray[i].list(i);
}
}
//根据输入的 id,查找雇员
public void findEmpById(int id) {
//使用散列函数确定到哪条链表查找
int empLinkedListNO = hashFun(id);
Emp emp = empLinkedListArray[empLinkedListNO].findEmpById(id);
if (emp != null) {//找到
System.out.printf("在第%d 条链表中找到 雇员 id = %d\n", (empLinkedListNO + 1), id);
} else {
System.out.println("在哈希表中,没有找到该雇员~");
}
}
//编写散列函数, 使用一个简单取模法
public int hashFun(int id) {
return id % size;
}
}
//表示一个雇员
class Emp {
public int id;
public String name;
public Emp next; //next 默认为 null
public Emp(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
//创建 EmpLinkedList ,表示链表
class EmpLinkedList {
//头指针,执行第一个 Emp,因此我们这个链表的 head 是直接指向第一个 Emp
private Emp head; //默认 null
//添加雇员到链表
//说明
//1. 假定,当添加雇员时,id 是自增长,即 id 的分配总是从小到大
// 因此我们将该雇员直接加入到本链表的最后即可
public void add(Emp emp) {
//如果是添加第一个雇员
if (head == null) {
head = emp;
return;
}
//如果不是第一个雇员,则使用一个辅助的指针,帮助定位到最后
Emp curEmp = head;
while (true) {
if (curEmp.next == null) {//说明到链表最后
break;
}
curEmp = curEmp.next; //后移
}
//退出时直接将 emp 加入链表
curEmp.next = emp;
}
//遍历链表的雇员信息
public void list(int no) {
if (head == null) { //说明链表为空
System.out.println("第 " + (no + 1) + " 链表为空");
return;
}
System.out.print("第 " + (no + 1) + " 链表的信息为");
Emp curEmp = head; //辅助指针
while (true) {
System.out.printf(" => id=%d name=%s\t", curEmp.id, curEmp.name);
if (curEmp.next == null) {//说明 curEmp 已经是最后结点
break;
}
curEmp = curEmp.next; //后移,遍历
}
System.out.println();
}
//根据 id 查找雇员
//如果查找到,就返回 Emp, 如果没有找到,就返回 null
public Emp findEmpById(int id) {
//判断链表是否为空
if (head == null) {
System.out.println("链表为空");
return null;
}
//辅助指针
Emp curEmp = head;
while (true) {
if (curEmp.id == id) {//找到
break;//这时 curEmp 就指向要查找的雇员
}
//退出
if (curEmp.next == null) {//说明遍历当前链表没有找到该雇员
curEmp = null;
break;
}
curEmp = curEmp.next;//以后
}
return curEmp;
}
}
树结构基础
一、二叉树
为什么需要树这种数据结构
- 数组存储方式的分析
优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 - 链式存储方式的分析
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,
删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历) - 树存储方式的分析
能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tre),既可以保证数据的检索速度,同时也
可以保证数据的插入,删除,修改的速度。
二叉树的概念
- 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
- 二叉树的子节点分为左节点和右节点
- 示意图
- 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
- 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二
层的叶子节点在右边连续,我们称为完全二叉树
二叉树的遍历
- 前序遍历: 先输出父节点,再遍历左子树和右子树
- 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
- 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
- 小结: 看输出父节点的顺序,就确定是前序,中序还是后序
例子:
public class BinaryTreeDemo {
public static void main(String[] args) {
HeroNode root = new HeroNode(1, "宋江");
HeroNode node1 = new HeroNode(2, "林冲");
HeroNode node2 = new HeroNode(3, "李达");
HeroNode node3 = new HeroNode(4, "卢俊义");
HeroNode node4 = new HeroNode(6, "吴用");
HeroNode node5 = new HeroNode(5, "沙和尚");
HeroNode node6 = new HeroNode(7, "黑旋风");
BinaryTree binaryTree = new BinaryTree(root);
root.setLeft(node1);
root.setRight(node2);
node1.setLeft(node3);
node1.setRight(node5);
node2.setRight(node6);
node2.setLeft(node4);
binaryTree.preOrder();
System.out.println("------------------------");
binaryTree.infixOrder();
System.out.println("-------------------------");
binaryTree.postOrder();
}
}
//定义二叉树
class BinaryTree {
private HeroNode root;
public BinaryTree(HeroNode root) {
this.root = root;
}
public void preOrder(){
if (this.root != null){
this.root.preOrder();
}else {
System.out.println("二叉树为空");
}
}
public void infixOrder(){
if (this.root != null){
this.root.infixOrder();
}else {
System.out.println("为叉树为空");
}
}
public void postOrder(){
if (this.root != null){
this.root.postOrder();
}else {
System.out.println("二叉树为空");
}
}
}
class HeroNode {
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public void setNo(int no) {
this.no = no;
}
public void setName(String name) {
this.name = name;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public void setRight(HeroNode right) {
this.right = right;
}
public int getNo() {
return no;
}
public String getName() {
return name;
}
public HeroNode getLeft() {
return left;
}
public HeroNode getRight() {
return right;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"no\":")
.append(no);
sb.append(",\"name\":\"")
.append(name).append('\"');
sb.append('}');
return sb.toString();
}
//前序遍历
public void preOrder() {
System.out.println(this);
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
//中序遍历
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
//后序遍历
public void postOrder() {
if (this.left != null) {
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
}