栈
用堆栈之前,我们首先介绍标准模板库中堆栈模板
通过对堆栈模板的使用,可以使我们跳过对堆栈具体实现的编码
而专注于堆栈在程序中的应用 (有兴趣可以查看源码,或者以后我会对数据结构实现继续写文章,请持续关注,本篇的内容在于竞赛和面试场上迅速入手运用)
import java.util.Stack;
- Stack< integer > stack;创建一个内部元素是integer型的栈
- stack.push(i);入栈
- stack.pop();出栈
- stack.peek();查看栈顶元素但是不移除,类似于c++ stack.top();但是c++中这个操作会弹出栈顶元素
- stack.empty() 栈是否为空
了解基本概念后 我们尝试一下基本用法。
用法一 检验括号的匹配
入门
import java.util.Stack;
public class StackTest {
public static void main(String[] args) {
Stack<Integer> stack=new Stack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack.peek());//显示栈顶元素 不弹出
printStack(stack);
System.out.println(stack.peek());//显示栈顶元素 不弹出 栈为空,故报错
}
//打印并弹出栈内所有元素
public static void printStack(Stack stack){
while (!stack.empty()) {
System.out.println(stack.pop());
}
}
}
题目一:括号匹配问题
长度不超过100的字符串中有左括号右括号大小写字母。每一个左括号都
从内向外与它右边而且距离最近的括号做匹配。
输出
第一行原字符串
第二行无法匹配的右括号下面?左括号 下面 $ 其余输出为空
样例输入
)(rttyy())sss)(
样例输出
)(rttyy())sss)(
? ? $
import java.util.Scanner;
import java.util.Stack;
public class 括号匹配问题 {
public static void main(String[] args) {
// Scanner input=new Scanner(System.in);
// char[] str=input.nextLine().toCharArray();
char[] str=")(rttyy())ss)(".toCharArray();
char[] ans=new char[str.length+1];
Stack<Character> stack=new Stack<Character>();
for (int i = 0; i < str.length; i++) {
//遇到左括号进栈
if (str[i]=='(') {
stack.push('(');
ans[i]=' ';
}else if (str[i]==')') {
//遇到右括号出栈,如果站不为空那么证明匹配输出位置对应空
if (!stack.empty()) {
stack.pop();
ans[i]=' ';
}else {//右括号不匹配,在右括号下面填?
ans[i]='?';
}
}else {//其他字符
ans[i]=' ';
}
}
//为不匹配的左括号赋值
for (int i = 0; i < ans.length; i++) {
if (ans[i]==' '||ans[i]=='?') {
System.out.print(ans[i]);
}else {
System.out.print('$');
}
}
System.out.println();
}
}
c++版本
- 头文件 #include 引入预处理
- stack stack;创建一个内部元素是int型的栈
- stack.push(i);入栈
- stack.pop();出栈
- stack.top();读取并弹出栈顶元素 java中stack.peek();是只读不弹
- stack.empty() 栈是否为空
#include<cstdio>
#include<stack>
using namespace std;
stack<int> s;
char str[110];
char ans[110];
int main(){
scanf("%s",str);
int i;
for (i = 0; i < str[i]!=0; i++) {
if (str[i]=='(') {
s.push('(');
ans[i]=' ';
}else if (str[i]==')') {
if (!s.empty()) {
s.pop();
ans[i]=' ';
}else {
ans[i]='?';
}
}else {
ans[i]=' ';
}
}
while(!s.empty()){
ans[s.top()]='$';
s.pop();
}
ans[i]=0;//为了使输出形成字符串,在其最后一个字符后面加一个空字符
puts(str);
puts(ans);
return 0;
}
题目二 网易面试题 爱编程的小易
爱编程的小易发现,当自己代码中的括号较多时,如果括号未
成对出现,或者出现的顺序错误,他的编辑器 总是能立马给出
错误提示。好奇的小易决定自己尝试实现该功能。 对于一行代
码(字符串),里面可能出现大括号”{}”、中括号”[]”和小括号
“()”,请编程判断该行代码的括号嵌 套是否正确。
“()”,”({})”,”print (‘Hello Netease’)”等都是括号的正确使
用方法,”(]”,”print (Hello Netease]”则是错误的范例
import java.util.Stack;
public class Valid_Expression {
public static boolean isValid(String expression) {
char[] chs = expression.toCharArray();
Stack<Character> stack = new Stack<>();
for (int i = 0; i < chs.length; i++) {
if (chs[i] == '{' || chs[i] == '[' || chs[i] == '(') {
stack.add(chs[i]);
}
if (chs[i] == '}' || chs[i] == ']' || chs[i] == ')') {
char match = chs[i] == '}' ? '{' : (chs[i] == ']' ? '[' : '(');
if (stack.isEmpty() || !stack.pop().equals(match)) {
return false;
}
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
String test = "{1+(2+3)+[(1+3)+(4*5)]}";
System.out.println(isValid(test));
}
}
栈的常见用法还包括前中后缀表达式的运算,这里仅及介绍中缀转后缀表达式并运算
题目三:计算中缀表达式的值
输入一个只包含+ - * / ()的非负数计算表达式,计算该表达式的值
样例输入:
(1+2)*4/8+(3+2)*4/8
样例输出:
4.0
注意:题目说的仅是非负数的运算,故没有考虑减号当做负号的情况
/*
操作步骤
(1)当读到一个操作数时,立即将它放到输出中。操作符则不立即输出,放入栈中。遇到左圆括号也推入栈中
(2)如果遇到一个右括号,那么就将栈元素弹出,将符号写出直到遇到一个对应的左括号。但是这个左括号只被弹出,并不输出
(3)在读到操作符时,如果此时栈顶操作符优先性大于或等于此操作符,弹出栈顶操作符直到发现优先级更低的元素位置。除了处理)的时候,否则决不从栈中移走”(”。操作符中,+-优先级最低,()优先级最高
(4)如果读到输入的末尾,将栈元素弹出直到该栈变成空栈,将符号写到输出中
(5)上述四个原则便可将中缀表达式转化成后缀表达式,然后对后缀表达式进行处理得到最终结果<具体方式:利用一个数据栈,将后缀表达式的数据依次入栈,遇到符号时则将栈中弹出两个数据,然后利用该符号进行计算,将计算结果再入栈,从此往复>
*/
import java.util.Scanner;
import java.util.Stack;
public class 中缀表达式求值 {
public static void main(String[] args) {
// Scanner scan=new Scanner(System.in);
// String string=scan.nextLine();
String string = "(1+2)*4/8+(3+2)*4/8";//
char[] arr1 = string.toCharArray();
// 下面程序将中缀表达式转化成后缀表达式<重点>
char a, b;
int k = 0;
char[] arr2 = new char[100];
Stack<Character> stackchar = new Stack<Character>();
Stack<Double> stackvalue = new Stack<Double>();
for (int i = 0; i < string.length(); i++) {
a = arr1[i];
if (a >= '0' && a <= '9') {
arr2[k++] = a;
} else if (a == '*' || a == '/' || a == '(') {
stackchar.push(a);
} else if (a == ')') {
b = stackchar.pop();
while (b != '(') {
arr2[k++] = b;
b = stackchar.pop();
}
} else if (a == '+' || a == '-') {
if (stackchar.isEmpty())
stackchar.push(a);
else {
do {
b = stackchar.pop();
if (b == '(')
stackchar.push(b);
else
arr2[k++] = b;
} while (!stackchar.isEmpty() && b != '(');
stackchar.push(a);
}
}
}
while (!stackchar.isEmpty()) {
b = stackchar.pop();
arr2[k++] = b;
}
// 利用后缀表达式和一个整型数据栈计算出结果
double data1, data2, data = 0;
for (int i = 0; i < k; i++) {
char ch = arr2[i];
if (ch >= '0' && ch <= '9') {
stackvalue.push(Double.parseDouble(ch + ""));// 也可以用String.valueOf(ch);
} else {
data1 = stackvalue.pop();
data2 = stackvalue.pop();
if (ch == '+') {
data = data1 + data2;
stackvalue.push(data);
} else if (ch == '-') {
data = data2 - data1;
stackvalue.push(data);
} else if (ch == '*') {
data = data1 * data2;
stackvalue.push(data);
} else if (ch == '/') {
if (data1 == 0) {
System.out.println("除数为0!");
System.exit(0);// 等价于return;
} else {
data = data2 / data1;
stackvalue.push(data);
}
}
}
}
data = stackvalue.pop();
System.out.println("Result=" + data);
}
}
队列
- import java.util.Queue;
import java.util.LinkedList;
注意:队列的使用不能像Stack一样直接new。它是一个接口,实现的时候可以用接口回调的方法创建Queue< Integer> qmax =new LinkedList< Integer>();
- 也可以直接用LinkedList new出来
- LinkedList< Integer> qmax = new LinkedList< Integer>();
import java.util.LinkedList;
import java.util.Queue;
public class QueueTest {
public static void main(String[] args) {
Queue<Integer> queue=new LinkedList<Integer>();
queue.add(1);//也叫enQueue操作
queue.add(2);
queue.add(3);
System.out.println(queue.peek());//查看队头元素
printQueue(queue);
System.out.println(queue.peek());
}
public static void printQueue(Queue queue) {
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}
双向队列 这种常用一点,适配性高
import java.util.LinkedList;
import java.util.Queue;
public class QueueTest {
public static void main(String[] args) {
//其实LinkedList也实现了 Deque双向队列接口
LinkedList<Integer> queue=new LinkedList<Integer>();
queue.add(1);//入队
//queue.add(2,1);指定位置上加 这个LinkedList相当于一个链表结构,这里当队列处理,不记它链表的特性
//queue.set(index,element)//将此列表中指定位置的元素替换为指定的元素。链表的特性
System.out.println("size:"+queue.size());
queue.addFirst(2);//队首加元素
queue.addLast(3);//队尾加元素
System.out.println("size:"+queue.size());
printQueue(queue);
queue.peek();//获取但不移除队头
// queue.peekFirst();
// queue.peekLast();
queue.poll();//获取队头,队头出队
}
public static void printQueue(Queue queue) {
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}
题目一:滑动窗口最大值
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,
那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5};
如果新来的值比队列尾部的数小,那就追加到后面,因为它可能在前面的最大值划出窗口后成为最大值
如果新来的值比尾部的大(或者等于),那就删掉尾部(因为有更大的在后面,所以它不会成为最大值,划出也是它先划出,不影响最大值),再追加到后面,循环下去直到小于
如果追加的值比的索引跟队列头部的值的索引超过窗口大小,那就删掉头部的值
其实这样每次队列的头都是最大的那个
import java.util.LinkedList;
public class SlidingWindowMaxArray {
public static int[] getMaxWindow(int[] arr, int w) {
if (arr == null || w < 1 || arr.length < w) {
return null;
}
LinkedList<Integer> qmax = new LinkedList<Integer>();
int[] res = new int[arr.length - w + 1];
int index = 0;
for (int i = 0; i < arr.length; i++) {
while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {
qmax.pollLast();
}
qmax.addLast(i);
if (qmax.peekFirst() == i - w) {
qmax.pollFirst();
}
if (i >= w - 1) {
res[index++] = arr[qmax.peekFirst()];
}
}
return res;
}
// for test
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = { 4, 3, 5, 4, 3, 3, 6, 7 };
int w = 3;
printArray(getMaxWindow(arr, w));
}
}
栈和队列综合
题目一:用数组结构实现大小固定的队列和栈
public class Array_To_Stack_Queue {
//用一个index下标记位置只对index下标的值操作,天然实现
public static class ArrayStack {
private Integer[] arr;
private Integer size;
public ArrayStack(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
}
public Integer peek() {
if (size == 0) {
return null;
}
return arr[size - 1];
}
public void push(int obj) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
arr[size++] = obj;
}
public Integer pop() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
return arr[--size];
}
}
/*
* 这里用size对end和start做了解耦
* end-->新进的数放的位置
* start-->出队的数从哪拿
* 优于那种start追end的结构,多一个空余位置判断是否满,更容易实现也容易理解
* size!=0的时候能拿数(pop)
* size!=length的时候能入队(push)
* 在已经满的队列push的时候,end位置从数组末尾跳到数组开头
*/
public static class ArrayQueue {
private Integer[] arr;
private Integer size;
private Integer first;
private Integer last;
public ArrayQueue(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
first = 0;
last = 0;
}
public Integer peek() {
if (size == 0) {
return null;
}
return arr[first];
}
//入队 size++
public void push(int obj) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
size++;
arr[last] = obj;
last = last == arr.length - 1 ? 0 : last + 1;
}
//出队size--
public Integer poll() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
size--;
int tmp = first;
first = first == arr.length - 1 ? 0 : first + 1;
return arr[tmp];
}
}
public static void main(String[] args) {
}
}
题目二:实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
【要求】
1.pop、push、getMin操作的时间复杂度都是O(1)。
2.设计的栈类型可以使用现成的栈结构
/*
* 修改基本栈
* 加一个最小数栈
* 入栈 3 2 4 1 5
* 最小栈3 2 2 1 1 //同步压 同步弹 压入的时候,最小栈每次压入的是值栈中存在的最小数
*
* 实现方法2 同样加入一个最小数栈
* 入栈 3 2 4 1 5
* 最小栈3 2 1 //压入的时候,最小栈每次只把小于等于最小栈栈顶的元素同步压入 弹出时候比较值栈和最小栈栈顶元素是否相同,相同就同步弹出
*
* peek函数返回栈顶元素
*/
import java.util.Stack;
public class Code_02_GetMinStack {
public static class MyStack1 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack1() {
this.stackData = new Stack<Integer>();
this.stackMin = new Stack<Integer>();
}
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum <= this.getmin()) {
this.stackMin.push(newNum);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
int value = this.stackData.pop();
if (value == this.getmin()) {
this.stackMin.pop();
}
return value;
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
return this.stackMin.peek();
}
}
public static class MyStack2 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack2() {
this.stackData = new Stack<Integer>();
this.stackMin = new Stack<Integer>();
}
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum < this.getmin()) {
this.stackMin.push(newNum);
} else {
int newMin = this.stackMin.peek();
this.stackMin.push(newMin);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
this.stackMin.pop();
return this.stackData.pop();
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
return this.stackMin.peek();
}
}
public static void main(String[] args) {
MyStack1 stack1 = new MyStack1();
stack1.push(3);
System.out.println(stack1.getmin());
stack1.push(4);
System.out.println(stack1.getmin());
stack1.push(1);
System.out.println(stack1.getmin());
System.out.println(stack1.pop());
System.out.println(stack1.getmin());
System.out.println("=============");
MyStack1 stack2 = new MyStack1();
stack2.push(3);
System.out.println(stack2.getmin());
stack2.push(4);
System.out.println(stack2.getmin());
stack2.push(1);
System.out.println(stack2.getmin());
System.out.println(stack2.pop());
System.out.println(stack2.getmin());
}
}
题目三:如何仅用队列结构实现栈结构?
import java.util.LinkedList;
import java.util.Queue;
public class StackAndQueueConvert {
/*
* 用两个栈实现队列
* 队A 队B
* 队A 入队 1 2 3 4 5
* 队A出队填入队B,保留队A最后一个元素
* 此时 队A 5
* 队B 1 2 3 4
* 队A最后一个数弹出
* 然后队B填入队A,队B保留最后一个元素
* 队B最后一个数弹出
* 然后队A填入队B,队A保留最后一个元素
* 队A最后一个数弹出
*/
public static class TwoStacksQueue {
private Stack<Integer> stackPush;
private Stack<Integer> stackPop;
public TwoStacksQueue() {
stackPush = new Stack<Integer>();
stackPop = new Stack<Integer>();
}
public void push(int pushInt) {
stackPush.push(pushInt);
}
public int poll() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
} else if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
return stackPop.pop();
}
public int peek() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
} else if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
return stackPop.peek();
}
}
}
题目四:如何仅用栈结构实现队列结构?
import java.util.Stack;
public class StackAndQueueConvert {
/*
* 用两个队列实现栈
* push栈和pop栈
* 压只进push,出只弹pop
* 原则一 pop栈有东西,push栈不能压入
* 原则二 push栈往pop栈压入的时候,必须一次性把所有数据压完
* 只要满足两个原则,可以在任意一个时刻判断和操作
*/
public static class TwoQueuesStack {
private Queue<Integer> queue;
private Queue<Integer> help;
public TwoQueuesStack() {
queue = new LinkedList<Integer>();
help = new LinkedList<Integer>();
}
public void push(int pushInt) {
queue.add(pushInt);
}
public int peek() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
help.add(res);
swap();
return res;
}
public int pop() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
swap();
return res;
}
private void swap() {
Queue<Integer> tmp = help;
help = queue;
queue = tmp;
}
}
}
关于dfs bfs还会用到栈和队列的知识,在图的章节会继续讨论
第三、四题其实就是面试中会考如何用栈实现bfs如何用队列实现dfs
其实就是用栈和队列相互模仿另一种数据结构。