稀疏数组、栈(用栈实现计算器)和队列

学习算法与数据结构已经一段时间了,之前学习了现在已经遗忘了一些,现在从头开始以自己的理解重新整理一遍,加深自己的印象。

稀疏数组

正常的二维数组会存在大部分重复的元素,而这些大量重复的元素在存储的过程中是没有必要且浪费空间的,所以有了稀疏数组。
稀疏数组:把大量重复的元素视为无效元素,创建一个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. 初始化两个栈,一个运算符栈,一个数栈。
  2. 遍历字符串,判断当前字符是符号还是数字,数字直接进数栈,符号栈为空,直接进符号栈,否则判断符号的优先级,当前字符优先级小于等于符号栈中的优先级,则从数栈弹出两个元素与符号栈中弹出的字符进行运算,运算结果入数栈。
  3. 字符串遍历完成后,如果符号栈不为空,依次弹出数字字符进行运算。

举个例子: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];
	}
}

以上皆为自己学习整理,不保证自己理解完全正确,仅供参考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dichotomy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值