常见的数据集合——栈

一、简介 

栈(Stack)是一种特殊的线性数据集合,按照后进先出的规则进行操作,当我们在对栈进行入栈(push())或出栈(pop())操作时,只可以在栈顶进行操作。栈的实现结构可以是一维数组或链表来实现,用数组实现的栈叫作顺序栈,用链表实现的栈叫作链表栈。在Java中,顺序栈使用java.util.Stack类实现,链式栈使用java.util.LinkedList类实现。

顺序栈与链式栈的区别:

对于顺序栈Stack来说,它是Vector的子类,底层是通过一个数组来储存数据的,是线程安全的;

链表栈LinkedList的底层是通过一条链表来存储数据的,是线程不安全的。

二、栈中的常见方法

1、入栈

入栈操作(push)就是把新元素从栈顶放入栈中,新元素将会成为新的栈顶。

 

2、出栈 

出栈操作(pop)就是把元素从栈顶弹出,出栈元素的前一个元素将会成为新的栈顶。

三、栈的常见应用场景

1、浏览器的回退和前进

我们只需要用两个栈(Stack1和Stack2)就可以实现这个功能。假如,你浏览了3个网页A、B、C,那么我们就会把A、B、C按顺序依次压入Stack1中,我们看到的只是Stack1栈顶的C网页,但是,当你想后退回去看B网页时,你点击了后退按钮,那么我们就会将Stack1中栈顶的C网页弹出并放进Stack2中,我们看到的还是Stack1栈顶的B网页,当你又想返回C网页时,你点击了前进按钮,我们可以将Stack2中的C网页弹出并入栈至Stack1中,我们看到的就是Stack1栈顶的C网页了。

 

 2、虚拟机栈

每个线程拥有一块独立的内存空间,这块内存空间被设计成“栈”这种结构,被称为“虚拟机栈”。

JVM虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。每一次方法调用都会有一个对应的栈帧被压入 JVM Stack虚拟机栈,每一个方法调用结束后,代表该方法的栈帧会从JVM Stack虚拟机栈中弹出。

四、出栈顺序 

 假如3个元素A、B、C按照顺序进栈,那么可能出现的出栈方式如下图所示:

如上图所示,按照A、B、C顺序入栈,但不一定是三个元素连续入栈, 可能在某个元素入栈之前就有元素出栈了,因此,会有多种出栈顺序。首先,第一种情况是A、B、C三个元素连续进入栈中,然后栈顶元素C先出栈,然后依次是B、A,最终出栈顺序是C、B、A;第二种情况是A、B先进入栈中,然后,元素B先出栈,B出栈以后,如果A不出栈,那么元素C进栈,然后就会按照顺序出栈,最终出栈顺序为B、C、A;如果A出栈,然后后C进栈再出栈,最终出栈顺序B、A、C;第三种情况是A先入栈,然后A再出栈,然后B进栈,如果B出栈的话,最后C进栈,C出栈,最终出栈顺序为A、B、C;如果B不出栈,那么C进栈,然后按顺序出栈,最终出栈顺序为A、C、B。因此,共有五种出栈顺序。

五、栈的常见手撕代码

1、检查符号是否成对出现

给定一个只包括 '('')''{''}''['']' 的字符串,判断该字符串是否有效。

有效字符串需满足:

    1. 左括号必须用相同类型的右括号闭合。
    2. 左括号必须以正确的顺序闭合。

比如: "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]" 、"([)]" 则不是。

首先,我们需要知道字符串中的最后一个左括号和第一个右括号肯定是配对的,依次类推,就可以检查出结果了。因为我们每次要拿最后一个左括号,所以我们应该把所有的左括号放入一个栈中,利用栈“后进先出”的特点便于每次拿到最后一个左括号,然后把“)”、“]”、“}”当作key(键),将与之对应左括号当作value(值)放入一个Map集合中。

创建好相应的容器以后,我们要遍历字符串,如果拿到的是左括号直接存入栈中,如果是右括号,在Map中得到的左括号与栈顶元素作比较,如果不相等,就返回false,当遍历结束时,如果栈为空,则返回true。另外,还会有两种特殊情况,那就是字符串中只包含左括号或只包含右括号,如果字符串中全是左括号,那么栈永远不为空;如果字符串中全是右括号,则会出现空栈,因此我们应该在比较时判断,当空栈时,栈顶元素可以是不为“)”、“]”、“}”的任意元素,这样就可以预防空栈带来的问题。具体代码实现如下:

package com.hpc.demo01;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class Demo02 {
	public static void main(String[] args) {
		String s1 = "{[()]}";
		String s2 = "[{]}";
		String s3 = ")]}";
		String s4 = "{[(";

		System.out.println(isValid(s1));
		System.out.println(isValid(s2));
		System.out.println(isValid(s3));
		System.out.println(isValid(s4));
		
	}

	/**
	 *  检查括号是否匹配的方法
	 * @param s
	 * @return
	 */
	public static boolean isValid(String s) {
		Map<Character, Character> map = new HashMap<Character, Character>();
		// 存入字符串中的左括号
		Stack<Character> stack = new Stack<Character>();

		// key=左括号,value=右括号
		map.put('}', '{');
		map.put(']', '[');
		map.put(')', '(');

		// 遍历字符数组
		char[] chs = s.toCharArray();
		for (int i = 0; i < chs.length; i++) {
			if (map.containsKey(chs[i])) {
				// 防止空栈
				char top = stack.isEmpty() ? '#' : stack.pop();

				if (!map.get(chs[i]).equals(top)) {
					return false;
				}
			} else {
				// 左括号
				stack.push(chs[i]);
			}
		}
		//将栈中的元素全部弹出才会全部匹配
		return stack.isEmpty();
	}
}

2、反转字符串

我们可以利用栈“后进先出”的特点来实现字符串的反转,将字符串中的每个字符先全部入栈,然后再逐个出栈。具体代码实现如下:

package com.hpc.demo01;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class Demo02 {
	public static void main(String[] args) {
		
		String s="1234";
		System.out.println(reverseString(s));
	}
	

	/**
	 * 使用栈反转字符串
	 * @param s
	 * @return
	 */
	public static String reverseString(String s) {
		StringBuilder sb=new StringBuilder();
		Stack<Character> stack=new Stack<Character>();
		
		for(int i=0;i<s.length();i++) {
			stack.push(s.charAt(i));
		}
		
		while(!stack.isEmpty()) {
			sb.append(stack.pop());
		}
		return sb.toString();
	}
}

以上就是对于栈的特点以及应用场景的部分总结,当我们遇到此类问题时,利用栈的特点去解决问题会有事半功倍的效果。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值