学习算法与数据结构已经一段时间了,之前学习了现在已经遗忘了一些,现在从头开始以自己的理解重新整理一遍,加深自己的印象。
稀疏数组
正常的二维数组会存在大部分重复的元素,而这些大量重复的元素在存储的过程中是没有必要且浪费空间的,所以有了稀疏数组。
稀疏数组:把大量重复的元素视为无效元素,创建一个N行3列的二维数组来存数据,第一行第一列存原始数组的行,第二列存原始数据的列,第三列存原始数据的有效数据个数,稀疏数组的行数为有效数据个数的行数+1。接下来每一行存原始数组的数据所在行列和值。
例如:
原始数组:
0 0 0 0 0
1 0 0 1 0
0 0 0 0 0
0 0 0 0 0
稀疏数组:
4 5 2
1 0 1
1 3 1 (行列是以0开始计算)
JAVA代码实现:
// 创建一个原始的二维数组,模拟需要转化成稀疏数组的数组,值为0视为无效数据
int[][] chessArr = new int[11][11];
chessArr[1][2] = 1;
chessArr[2][3] = 2;
// 将二维数组转为稀疏数组,sum统计有效数据个数
int sum = 0;
for (int[] is : chessArr) {
for (int is2 : is) {
if (is2 != 0) {
sum++;
}
}
}
// 初始化一个稀疏数组,并将第0行分别存入原始数组的行,列,有效数据个数
int[][] sparseArr = new int[sum + 1][3];
sparseArr[0][0] = chessArr.length;
sparseArr[0][1] = chessArr[0].length;
sparseArr[0][2] = sum;
// count代表稀疏数组的索引
int count = 0;
// 遍历原始数组
for (int i = 0; i < chessArr.length; i++) {
for (int j = 0; j < chessArr[i].length; j++) {
if (chessArr[i][j] != 0) {
count++;
// 将不为0的数据的行,列,值存入稀疏数组
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr[i][j];
}
}
}
// 将数组模拟存进文件中
File file = new File("D:/sparseArr.txt");
if (!file.exists()) {
file.createNewFile();
}
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
StringBuilder sb = new StringBuilder();
// 将稀疏数组按照制表符进行分割
for (int i = 0; i < sparseArr.length; i++) {
sb.append(sparseArr[i][0]).append("\t").append(sparseArr[i][1]).append("\t").append(sparseArr[i][2])
.append("\r\n");
}
bw.write(sb.toString());
bw.flush();
bw.close();
// 解析稀疏数组
File file = new File("D:/sparseArr.txt");
// 从文件中读取数组出来
BufferedReader br = new BufferedReader(new FileReader(file));
// 读取文件中第一行数据,创建出原始数组
String line = br.readLine();
String[] datas = line.split("\t");
// 行 列 值
int row = Integer.parseInt(datas[0]);
int col = Integer.parseInt(datas[1]);
int val;
int[][] sparseArr = new int[row][col];
// 循环读取每一行,将文件中数据还原
while ((line = br.readLine()) != null) {
datas = line.split("\t");
row = Integer.parseInt(datas[0]);
col = Integer.parseInt(datas[1]);
val = Integer.parseInt(datas[2]);
sparseArr[row][col] = val;
}
br.close();
栈
一种数据结构,实现了栈中数据可以先进后出。
使用数组实现栈
JAVA代码实现:
class ArrayStack {
// 栈的大小
private int maxSize;
// 数组模拟
private int[] stack;
// 栈顶
private int top = -1;
// 初始化数组
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
// 判断栈是不是满了
public boolean isFull() {
return top == maxSize - 1;
}
// 判断栈是否为null
public boolean isEmpty() {
return top == -1;
}
// 入栈,栈顶加1
public void push(int value) {
if (isFull()) {
System.out.println("满了");
return;
}
top++;
stack[top] = value;
}
// 出栈,栈顶减1
public void pop() {
if (isEmpty()) {
System.out.println("空的");
return;
}
System.out.println(stack[top]);
top--;
}
// 显示栈
public void show() {
if (isEmpty()) {
System.out.println("没有");
return;
}
for (int i = top; i >= 0; i--) {
System.out.println(stack[i]);
}
}
}
使用链表实现栈
class LinkedListStack<E> {
// 栈顶索引
private int top = 0;
// 链表(自己实现的链表,后面在写链表)
private SingleLinkedList<E> list = new SingleLinkedList<E>();
// 判断是否为空
public boolean isEmpty() {
return list.size() == 0;
}
// 添加元素
public void push(E value) {
top++;
list.add(value);
}
// 弹出元素
public E pop() {
if (isEmpty()) {
throw new RuntimeException("空的");
}
E value = list.getNode(top).data;
list.delete(top);
top--;
return value;
}
}
使用栈来实现一个加减乘除的计算器
思路:
- 初始化两个栈,一个运算符栈,一个数栈。
- 遍历字符串,判断当前字符是符号还是数字,数字直接进数栈,符号栈为空,直接进符号栈,否则判断符号的优先级,当前字符优先级小于等于符号栈中的优先级,则从数栈弹出两个元素与符号栈中弹出的字符进行运算,运算结果入数栈。
- 字符串遍历完成后,如果符号栈不为空,依次弹出数字字符进行运算。
举个例子:1+23
首先扫描第一个字符,发现是数字1,进入数栈,发现第二个字符为符号+,符号栈也为空,+入栈,第三个数字进入数栈,第4个发现为号,判断当前符号优先级是否大于栈中的符号,大于+,所以直接入栈,最后一个数字入数栈。符号栈不为空,从数栈弹出3和2,符号栈弹出,运算结果6放入数栈,继续从数栈和符号栈弹出,进行加法,符号栈为空,数栈中最后的结果则为运算结果。
JAVA代码实现:
// 其中需要先准备好判断是否为符号,符号优先级,以及运算的方法
// 判断是不是运算符
public static boolean isOper(char oper) {
return oper == '+' || oper == '-' || oper == '*' || oper == '/' || oper == '(' || oper == ')';
}
// 判断运算符优先级的方法 数字越高优先级越大
public static int priority(char oper) {
if (oper == '*' || oper == '/') {
return 1;
} else if (oper == '+' || oper == '-') {
return 0;
} else if (oper == '(') {
return 2;
} else {
return -1;
}
}
// 进行计算的方法
public static int calculation(int num1, int num2, char 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;
}
// 初始化两个栈,数栈,符号栈
Stack<Integer> numStack = new Stack<Integer>();
Stack<Character> opers = new Stack<Character>();
// 测试的字符串
String expression = "(1+3)*2-2";
// 遍历字符串的索引 数栈弹出数据 符号字符 运算结果 当前字符
int index = 0;
int num1 = 0;
int num2 = 0;
char oper = ' ';
int res = 0;
char nowChar = ' ';
// 进行遍历
while (index < expression.length()) {
// 获取当前字符
nowChar = expression.charAt(index);
// 判断当前字符是不是运算符
if (isOper(nowChar)) {
// 判断运算符栈中是不是为null
if (!opers.isEmpty()) {
// 如果当前字符为右括号,循环处理,直到遇见左括号
if (nowChar == ')') {
while (opers.peek() != '(') {
// 数栈弹出两个元素,符号栈弹出一个元素,算出结果,结果入数栈
num1 = numStack.pop();
num2 = numStack.pop();
oper = opers.pop();
res = calculation(num1, num2, oper);
numStack.push(res);
}
// 弹出左括号
opers.pop();
} else {
// 不是右括号处理
// 判断运算符栈中的运算符与当前运算符的优先级
// 如果当前运算符的优先级小于等于栈中的运算符,出栈进行运算,否则入栈
// 如果栈中符号为左括号也直接入栈
if (priority(nowChar) <= priority(opers.peek()) && opers.peek() != '(') {
num1 = numStack.pop();
num2 = numStack.pop();
oper = opers.pop();
res = calculation(num1, num2, oper);
numStack.push(res);
opers.push(nowChar);
} else {
// 当前运算符优先级小于等于栈中运算符,或者符号栈中为左括号
opers.push(nowChar);
}
}
} else {
// 符号栈为空,直接入栈
opers.push(nowChar);
}
} else {
// 不是运算符
// 判断下一位字符是否是数字,数字需要继续遍历
StringBuilder num = new StringBuilder();
num.append(nowChar);
// 当下一个索引不超过字符串索引范围且下一个不是字符,循环
while (index + 1 != expression.length() && !isOper(expression.charAt(index + 1))) {
index++;
char nowNum = expression.charAt(index);
num.append(nowNum);
}
int completeNum = Integer.parseInt(num.toString());
numStack.push(completeNum);
}
// 执行完后 索引自增
index++;
}
// 判断符号栈是否为空,不为空重复运算
while (!opers.isEmpty()) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = opers.pop();
res = calculation(num1, num2, oper);
numStack.push(res);
}
System.out.printf("计算式%s = %d", expression, numStack.pop());
前缀,中缀,后缀表达式
经过上面的运算,上面的表达式并不利于计算机进行计算,所以出现了前缀,后缀表达式。
中缀表达式:上面的那种易于我们理解的表达式称为中缀表达式。例如:( 3 + 5 ) * 8 - 4 * 2。
前缀表达式(也叫波兰表达式):一种特殊的运算表达式。以上面的表达式举例:-*+358*42就是前缀表达式,一个简单的方法可以快速写出该表达式,先把35842全部写出,然后看3+5是优先计算的,就在35的前面写一个+,然后乘8也是先计算的,就在+前面写*,后面的42要优先计算,于是在4的前面写,减法是最后运算的,于是在表达式最前面写-,于是就变成了-+35842。至于为什么这样写,后面讲后缀表达式的时候说。
后缀表达式(也叫逆波兰表达式):跟前缀表达式表示差不多,只是符号写在了数字后面,上面的用后缀表达式表示为:35+842-,后缀表达式比较好写,从左至右,看见3+5优先计算就写35+然后后面8也要先计算,于是先写数字,后写,发现后面-最后计算,于是先写42然后在跟*,最后跟-。
这种表达式跟使用程序计算有关,具体计算如下:
以后缀为例,从前向后扫描一个后缀表达式(当然该表达式区分数字的,后面实现会使用集合来装),发现数字直接入栈,发现运算符弹出两个数字运算后入栈,一直到扫描结束,栈中的数据就是运算结果。前缀表达式则是从尾向前扫描,其余操作一致。
JAVA代码实现:
public static void main(String[] args) {
String expression = "( 3 + 5 ) * 8 - 4 * 2";
List<String> lastStr = getLastStr(getList(expression));
int result = calculation(lastStr);
System.out.printf("\u001b[1;35m表达式%s = %d", expression, result);
}
/**
* 传入一个表达式字符串,转成一个list集合
*
* @param expression 表达式字符串
* @return 表达式的集合
*/
public static List<String> getList(String expression) {
List<String> list = new ArrayList<String>();
// 为了方便,这里用空格隔开了表达式
String[] strings = expression.split(" ");
for (String s : strings) {
list.add(s);
}
return list;
}
/**
* 中缀表达式转为list后缀表达式集合
*
* @param list 中缀表达式的集合
* @return 后缀表达式的集合
*/
public static List<String> getLastStr(List<String> list) {
// 创建一个存储符号的栈
Stack<String> opers = new Stack<String>();
// 用来保存后缀表达式的集合
List<String> suffix = new ArrayList<String>();
for (String item : list) {
// 如果是一个数字,直接加入集合
if (item.matches("\\d+")) {
suffix.add(item);
} else {
// 如果符号栈为空,直接符号入栈
if (opers.isEmpty()) {
opers.push(item);
} else {
// 不为空,针对每种符号做处理
switch (item) {
case "(":
// 左括号直接入栈
opers.push(item);
break;
case "+":
// +判断符号栈第一个是不是左括号,是的话直接入栈
if (opers.peek().equals("(")) {
opers.push(item);
} else {
// 否则在集合中加入符号栈中第一个字符 再把当前字符直接入栈
suffix.add(opers.pop());
opers.push(item);
}
break;
case "-":
// 同+
if (opers.peek().equals("(")) {
opers.push(item);
} else {
suffix.add(opers.pop());
opers.push(item);
}
break;
case "*":
// 判断符号栈中符号是不是左括号,+和- 是的话直接入符号栈
if (opers.peek().equals("(") || opers.peek().equals("+") || opers.peek().equals("-")) {
opers.push(item);
} else {
// 否则弹出栈中符号,栈中加入当前符号
suffix.add(opers.pop());
opers.push(item);
}
break;
case "/":
// 同*
if (opers.peek().equals("(") || opers.peek().equals("+") || opers.peek().equals("-")) {
opers.push(item);
} else {
suffix.add(opers.pop());
opers.push(item);
}
break;
case ")":
// 匹配到右括号后,一直循环直到遇见左括号
while (!opers.peek().equals("(")) {
suffix.add(opers.pop());
}
// 弹出左括号
opers.pop();
break;
}
}
}
}
// 上面执行完后,把符号栈中所有符号全部添加进集合
while (!opers.isEmpty()) {
suffix.add(opers.pop());
}
return suffix;
}
/**
* 计算后缀表达式
*
* @param list 后缀表达式的集合
* @return 计算结果
*/
public static int calculation(List<String> list) {
// 定义结果接收
int result = 0;
// 定义数栈存储元素
Stack<String> numStack = new Stack<String>();
// 遍历后缀表达式集合
for (String st : list) {
// 判断如果是数字直接入栈
if (st.matches("\\d+")) {
numStack.push(st);
} else {
// 不是数字从数栈依次弹出两个数字
int num2 = Integer.parseInt(numStack.pop());
int num1 = Integer.parseInt(numStack.pop());
// 判断当前符号,进行相应的计算后入栈
switch (st) {
case "+":
result = num1 + num2;
numStack.push(result + "");
break;
case "-":
result = num1 - num2;
numStack.push(result + "");
break;
case "*":
result = num1 * num2;
numStack.push(result + "");
break;
case "/":
result = num1 / num2;
numStack.push(result + "");
break;
}
}
}
// 返回结果
result = Integer.parseInt(numStack.pop());
return result;
}
数组模拟队列
队列:一种先进先出的数据结构,类似于生活中的排队。
数组模拟队列的思路:
1.需要先初始化一个队列头和队列尾两个索引下标。
2.数组模拟队列需要实现循环使用,所以计算头尾索引下标需要使用头尾索引下标+1与数组长度取模进行运算。
JAVA代码实现:
// 写一个数组队列类
class ArrayQueue {
// 队列的大小
private int maxSize;
// 队列头
private int head;
// 队列尾
private int tail;
// 模拟队列
private int[] arr;
// 初始化一个队列
public ArrayQueue(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[maxSize];
}
// 判断队列有没有满
public boolean isFull() {
// 用的数组循环模拟队列,所以用尾索引+1取模队列大小等于头索引的时候,队列就已经满了
// 例如:队列大小为4,添加了4个数据,尾索引变成了3,头索引还是0,3+1 % 4 = 0,当前队列就满了
return (tail + 1) % maxSize == head;
}
// 判断队列是否空,当头等于尾的时候就是空队列
public boolean isEmpty() {
return tail == head;
}
// 添加数据到队列
public void addQueue(int n) {
if (isFull()) {
System.out.println("队列满,不能加入数据");
return;
}
// 使用尾索引添加数据
arr[tail] = n;
// 取模实现循环使用 因为当出队列以后,队列会变空,然而单纯自增,数组长度不够使用
tail = (tail + 1) % maxSize;
}
// 获取队列的数据,出队列
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列为空,不能取出数据");
}
int result = arr[head];
// 计算方式同添加到队列
head = (head + 1) % maxSize;
return result;
}
// 显示所有的数据
public void showQueue() {
if (isEmpty()) {
System.out.println("空的");
return;
}
for (int i = head; i < head + getValidCount(); i++) {
System.out.println(arr[i % maxSize]);
}
}
public int getValidCount() {
return (tail + maxSize - head) % maxSize;
}
// 显示头部数据
public int headQueue() {
if (isEmpty()) {
throw new RuntimeException("空的");
}
return arr[head];
}
}
以上皆为自己学习整理,不保证自己理解完全正确,仅供参考。