数据结构与算法(五)——栈

概述

  1. 栈是一个先入后出的有序列表,和队列正好相反
  2. 栈是限制线性表中的元素的插入和删除都只能在线性表的同一段进行的一种特殊线性表。允许插入和删除的一端称作栈顶,另一端作为固定的一段,称为栈底
  3. 根据栈的定义可知,最先放入栈中的元素在栈底,最后入栈的元素在栈顶,而出栈是正好相反,最后放入的元素最先出栈

栈的应用

  1. 子程序的调用:在调用子程序钱,会先将下一个指令的地址存到堆栈中,知道子程序执行完后再将地址取出,从而回到原来的程序中
  2. 处理递归调用:和子程序调用类似,只是除了下一个指令的地址外,也将参数、局域变量等跟随指令一起存入堆栈中
  3. 表达式的转换:中缀表达式转后追表达式与求值
  4. 二叉树的遍历
  5. 图的深度优先搜索算法

逻辑结构示意图

在这里插入图片描述

代码实现

数组方式

/***
 * 数组模拟栈
 * @author laowa
 *
 */
class ArrayStack{
	/**
	 * 栈的最大容量
	 */
	private int maxSize;
	/**
	 * 存放栈中数据的数组
	 */
	private int[] array;
	/**
	 * 栈顶指针,初始化为-1,指向的是当前栈顶的元素
	 */
	private int top = -1;
	
	public ArrayStack(int maxSize) {
		this.maxSize = maxSize;
		this.array = new int[maxSize];
	}
	
	/**
	 * 入栈
	 * @param num 待入栈的数据
	 */
	public void push(int num) {
		if(isFull()) {
			System.out.println("栈已满");
			return;
		}
		//先将栈顶指针向数组后面移动一位到新的空间,然后将新数据存放在新的有效空间
		array[++top]=num;
	}
	
	/**
	 * 出栈
	 * @return 出栈的元素
	 */
	public int pop() {
		if(isEmpty()) {
			throw new RuntimeException("栈中没有任何元素");
		}
		//获取栈中顶部的数据然后将栈顶指针向栈底移动一位
		return array[top--];
	}
	
	/**
	 * 查看栈顶的元素
	 * @return 栈顶的元素
	 */
	public int peek() {
		if(isEmpty()) {
			throw new RuntimeException("栈中没有任何元素");
		}
		//查看栈顶的元素不用移动栈顶指针
		return array[top];
	}
	
	/**
	 * 按顺序打印栈
	 */
	public void list() {
		if(isEmpty()) {
			System.out.println("栈为空");
			return;
		}
		//为了不影响原有的栈结构,创建一个辅助变量来辅助遍历
		int helper = top;
		while(helper!=-1) {
			System.out.println(array[helper--]);
		}
	}
	
	/**
	 * 栈的判满
	 * @return 栈是否已满
	 */
	public boolean isFull() {
		//top指向的是栈顶的元素的数组下标,当栈顶的元素数组下标为数组长度-1,即数组已满
		//因为出栈后,虽然不在栈中,但是仍然在数组中,只是通过top指针来逻辑指定栈的内容,所以不能通过数组的长度来判满
		return top==maxSize-1;
	}
	
	/**
	 * 栈的判空
	 * @return 栈是否为空
	 */
	public boolean isEmpty() {
		//如果栈顶指向了数组第一个元素的前一位,即栈顶没有数据,表示栈已空
		return top==-1;
	}
}

链表方式

/**
 * 单链表模拟栈
 * 
 * @author laowa
 *
 */
class SingleLinkedListStack {

	/**
	 * 存放栈中数据的链表
	 */
	private SingleLinkedList data;
	/**
	 * 栈顶指针,指向链表的最后一个节点
	 */
	private Node top;

	/**
	 * 构造器,初始化时栈顶指向单链表的头节点
	 */
	public SingleLinkedListStack() {
		data = new SingleLinkedList();
		top = data.getHead();
	}

	/**
	 * 入栈
	 * 
	 * @param node
	 *            待入栈的节点
	 */
	public void push(Node node) {
		// 将新的节点拼接在链表后面,即top节点后面,然后将栈顶往后移
		top.next = node;
		top = top.next;
	}

	/**
	 * 出栈
	 * 
	 * @return 出栈的节点
	 */
	public Node pop() {
		if (isEmpty()) {
			throw new RuntimeException("栈为空");
		}
		// 因为单链表无法自我删除,需要创建一个辅助节点来遍历单链表知道top节点的前一个节点来将栈顶节点删除
		Node helper = data.getHead();
		while (helper.next != top) {
			helper = helper.next;
		}
		// 先取出栈顶节点
		Node res = top;
		// 将栈顶节点删除,然后移动top到helper节点
		helper.next = top.next;
		top = helper;
		return res;
	}

	/**
	 * 查看栈顶节点
	 * 
	 * @return 栈顶节点
	 */
	public Node peek() {
		if (isEmpty()) {
			throw new RuntimeException("栈为空");
		}
		return top;
	}

	/**
	 * 打印栈,即单链表的逆序打印
	 */
	public void list() {
		if (isEmpty()) {
			System.out.println("栈为空");
			return;
		}
		Stack<Node> stack = new Stack<>();
		Node helper = data.getHead();
		while (helper.next != null) {
			stack.add(helper.next);
			helper = helper.next;
		}
		while (!stack.isEmpty()) {
			System.out.println(stack.pop());
		}
	}

	/**
	 * 判空
	 * 
	 * @return 栈是否为空
	 */
	public boolean isEmpty() {
		// 如果栈顶为头节点,表示栈中没有有效节点,栈为空
		return top == data.getHead();
	}
}

/***
 * 单链表
 * 
 * @author laowa
 *
 */
class SingleLinkedList {
	/**
	 * 链表的头节点,不存放任何数据,作用就是作为链表的头
	 */
	private Node head = new Node(0, "", "");

	public Node getHead() {
		return this.head;
	}

	/**
	 * 增加节点(增加到链表尾部)
	 * 
	 * @param node
	 *            节点
	 */
	public void add(Node node) {
		// 要找到链表的最后一个节点,然后将最后一个节点的next指向新节点
		// 遍历链表之前,需要创建一个辅助节点helper,因为头节点不能移动,头节点一旦移动链表就无法使用了
		Node helper = head;
		while (true) {
			// 如果下一个不为空,则指向下一个节点
			if (helper.next != null) {
				helper = helper.next;
			} else {
				// 最后一个节点为空,找到最后一个节点跳出循环
				break;
			}
		}
		// 将最后一个节点的next指针指向新的节点
		helper.next = node;
	}

	/**
	 * 打印链表信息
	 */
	public void list() {
		// 链表判空
		if (head.next == null) {
			System.out.println("链表为空");
			return;
		}
		Node helper = head.next;
		while (true) {
			// 打印节点信息
			System.out.println(helper);
			if (helper.next != null) {
				helper = helper.next;
			} else {
				break;
			}
		}
	}

	/**
	 * 按照编号顺序条件添加
	 * 
	 * @param node
	 *            待添加的节点
	 */
	public void addByOrder(Node node) {
		Node helper = head;
		boolean isExist = false;// 作为标志量,标志当前链表是否已经存在该节点
		while (true) {
			if (helper.next == null) {
				break;
			}
			// 当下一个节点比新节点的编号大时,将新节点插在下一个节点之前
			if (helper.next.no > node.no) {
				break;
			} else if (helper.next.no == node.no) {
				// 如果下一个节点和新节点编号一样,说明该节点已存在
				isExist = true;
				break;
			}
			// 如果下一个节点比新节点的编号小,则新节点应该在下一个节点的后面,所以继续往后找
			helper = helper.next;
		}
		// 存在则不插入
		if (isExist) {
			System.out.printf("编号为%d节点已存在\n", node.no);
		} else {
			// 不存在该节点,则表示当前helper几点的后一个位置就是需要插入的位置,先把新节点指向helper.next再将helper.next指向新节点
			node.next = helper.next;
			helper.next = node;
		}
	}

	/**
	 * 通过节点编号修改链表中的节点
	 * 
	 * @param node
	 *            新节点
	 */
	public void update(Node node) {
		Node helper = head;
		boolean isExist = false;// 标志量,判断链表中是否存在待修改的这个节点
		while (true) {
			// 如果链表为空,则表示不存再该节点
			if (helper.next == null) {
				break;
			} else if (helper.next.no == node.no) {
				// 如果下一个节点的编号与待修改的节点编号相同,则表示已存在该节点,跳出循环
				isExist = true;
				break;
			}
			// 否则继续往下找
			helper = helper.next;
		}
		if (isExist) {
			helper.next.name = node.name;
			helper.next.nickName = node.nickName;
		} else {
			System.out.printf("没有找到编号为%d的节点\n", node.no);
		}
	}

	/**
	 * 通过编号删除节点
	 * 
	 * @param no
	 *            节点编号
	 */
	public void delete(int no) {
		Node helper = head;
		boolean isExist = false;// 标志量,判断节点是否存在
		while (true) {
			if (helper.next == null) {
				break;
			} else if (helper.next.no == no) {
				// 当下一个节点的编号是待删除的节点编号时,跳出循环
				isExist = true;
				break;
			}
			helper = helper.next;
		}
		if (isExist) {
			// 将待删除的节点前一个节点直接指向待删除节点的下一个节点,待删除的节点失去了栈空间的指针,将被垃圾回收机制回收
			helper.next = helper.next.next;
		} else {
			System.out.printf("编号为%d的节点不存在", no);
		}
	}

}

/***
 * 单向链表节点
 * 
 * @author laowa
 *
 */
class Node {
	int no;
	String name;
	String nickName;
	/**
	 * 指向下一个节点
	 */
	Node next;

	public Node(int no, String name, String nickName) {
		this.no = no;
		this.name = name;
		this.nickName = nickName;
		this.next = null;
	}

	@Override
	public String toString() {
		return "no=" + no + "\tname=" + name + "\tnickName=" + nickName;
	}

}

栈解决表达式求值问题

	/**
	 * 计算器,表达式求值
	 * 
	 * @param s
	 *            待求值的表达式
	 */
	public static int calculator(String s) {
		int length = s.length();
		// 创建数栈
		ArrayStack numStack = new ArrayStack(length);
		// 创建符号栈
		ArrayStack oprStack = new ArrayStack(length);

		// 遍历表达式
		for (int i = 0; i < length; i++) {
			// 使用int接收char会被转成对应的ascii码
			int ch = s.charAt(i);
			if (isOper(ch)) {
				// 如果遍历到的字符是一个操作符
				if (oprStack.isEmpty()) {
					// 如果符号栈为空,则直接入符号栈
					oprStack.push(ch);
				} else {
					// 先比较符号栈和当前字符的优先级
					if (priority(ch) < priority(oprStack.peek())) {
						//循环符号栈,直到符号栈为空,或者符号栈中的符号高于或等于当前字符
						//避免出现多个*,/符号夹在+,-符号中间,要把所有*/符号都计算完
						while (!oprStack.isEmpty()&&priority(ch) < priority(oprStack.peek())) {
							// 如果栈中符号的优先级更高则先将栈中符号进行运算,保证最后符号栈中所有符号的优先级按照从高到低排序,最后出栈运算时结果才正确
							numStack.push(cal(numStack.pop(), numStack.pop(), oprStack.pop()));
						}
						// 将新符号入栈
						oprStack.push(ch);
					} else {
						// 如果当前符号的优先级比符号栈顶符号的优先级高,或两个优先级相同则直接入栈
						oprStack.push(ch);
					}
				}
			} else {
				// 因为int转char会被转成ascii码,所以要得到原数字需要-48
				ch = ch - 48;
				// 如果遍历到的字符时一个数字,查看下一位字符是符号还是数字,处理多位数的情况
				// 如果i已经是最后一位则不进行这一步
				if (i != length - 1) {
					while (true) {
						// 获取下一位的字符
						int next = s.charAt(i + 1);
						// 当下一位的字符不是数字
						if (isOper(next)) {
							break;
						} else {
							// 将多位数结合
							ch = ch * 10 + next - 48;
							i++;
							// 如果i已经到最后一位了,即上方的next获取的就是最后一位符号了,说明字符串已经遍历完了跳出循环
							if (i == length - 1) {
								// i++跳出外层遍历字符串的循环
								i++;
								break;
							}
						}
					}
				}

				// 数字入数栈
				numStack.push(ch);
			}
		}
		// 表达式遍历完成之后,依次从符号栈取出一个符号,从数栈取出两个数,进行计算后存入数栈,直到符号栈为空
		while (!oprStack.isEmpty()) {
			numStack.push(cal(numStack.pop(), numStack.pop(), oprStack.pop()));
		}
		// 最后数栈中还剩下一个数,即最后的结果
		return numStack.pop();

	}

	/**
	 * 判断字符是否是一个符号
	 * 
	 * @param ch
	 *            字符
	 * @return 是否时一个操作符,简单实现只识别四则基本运算
	 */
	public static boolean isOper(int ch) {
		return ch == '+' || ch == '-' || ch == '*' || ch == '/';
	}

	/**
	 * 获取操作符的优先级
	 * 
	 * @param opr
	 *            操作符
	 * @return 一个正整数,数值越大优先级越高
	 */
	public static int priority(int opr) {
		if (!isOper(opr)) {
			throw new RuntimeException("不是一个操作符");
		}
		if (opr == '+' || opr == '-') {
			return 0;
		}
		return 1;
	}

	/**
	 * 计算,
	 * 
	 * @param num1
	 *            数字栈中取出的第一个数字
	 * @param num2
	 *            数字栈中取出的第二个数字
	 * @param oper
	 *            操作符
	 * @return 运算结果
	 */
	public static int cal(int num1, int num2, int oper) {
		// 由于栈的先进后出特性,后出栈的数字应当在表达式的前面,所以减法和触发运算时,应当注意num2在前
		switch (oper) {
		case '+':
			return num1 + num2;
		case '-':
			return num2 - num1;
		case '*':
			return num1 * num2;
		case '/':
			return num2 / num1;
		}
		return 0;
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值