目录
一.栈
1.什么是栈
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
2.栈的示意图
3.栈在生活中的例子
给枪上膛,或者后吃的东西先吐出来。
4.栈在Java中的定义和使用
栈继承Vector类,底层还是连续的存储空间,和ArrayList底层都是数组。
(1)常用方法
(2)栈的常用操作练习
import java.util.Stack;
public class StackDemo {
public static void main(String[] args) {
Stack<Integer> st = new Stack<>();
st.push(1);
st.push(2);
st.push(3);
st.push(4);
System.out.println(st.pop());//4弹出
System.out.println(st.peek());//查看栈顶元素
System.out.println(st.size());
}
}
5.栈的模拟实现
(1)问题分析
首先需要一个数组,然后再需要一个size来判断栈空间是否以满,通过上面分析,可以知道栈理解为对数组进行尾插和尾删的操作。这里还实现了一个动态扩容的方法,每次以原数组2倍的方式进行扩容。
(2)代码实现
import java.util.Arrays;
import java.util.EmptyStackException;
public class MyStack {
public static void main(String[] args) {
Stack<String> st = new Stack<>();
System.out.println(st.isEmpty());
st.push("111");
st.push("222");
st.push("333");
st.push("444");
System.out.println(st.getSize());
System.out.println(st.isEmpty());
System.out.println(st.peek());
System.out.println(st.pop());
System.out.println(st.peek());
st.pop();
st.pop();
st.pop();
st.pop();
System.out.println(st.getSize());
}
}
class Stack<E>{
E[] array;
private int size;//统计栈中元素
public Stack(){
array= (E[])new Object[2];
}
//获取栈中元素的个数
public int getSize(){
return size;
}
public boolean isEmpty(){
if(size==0){
return true;
}
return false;
}
public E pop(){
if(size>0){
size--;
return array[size];
}
throw new EmptyStackException();
}
public E push(E ele){
//确保是否可以添加
ensureCapacity(size);
array[size]= ele;
size++;
return array[size-1];
}
//查看栈顶元素
public E peek(){
if(size>0){
return array[size-1];
}
throw new EmptyStackException();//栈为空异常
}
//扩容
private void ensureCapacity(int size){
if(size==array.length){
array = Arrays.copyOf(array,size*2);
}
}
}
6.栈的简单应用(习题)
(1)简单的括号匹配
问题:给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
匹配的时候
不符合的情况:
代码实现:
public boolean isValid(String s) {
//遇到左括号进栈,遇到右括号出栈匹配
Stack<Character> st = new Stack<>();
for(int i=0;i<s.length();i++){
char ch = s.charAt(i);
//判断是否为左括号
if(ch=='(' || ch=='[' || ch=='{'){
st.push(ch);
}
//右括号没匹配完栈为空或者左右括号不匹配
else if(st.empty()|| !((ch==')' && st.peek()=='(')||
(ch==']' && st.peek()=='[')||
(ch=='}' && st.peek()=='{'))){
return false;
}else{
st.pop();//匹配,栈顶出元素
}
}
if(st.empty())//栈空
return true;
return false;//左括号多则返回否
}
(2)逆波兰表达式求值
逆波兰表达式就是后缀表达式,我们平时的在数学中的加减乘除就是一种中缀表达式,为了使计算机可以计算,所有就有了逆波兰表达式,目的就是让计算机好识别。
题目:有效的算符包括 +
、-
、*
、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
举例:((2 + 1) * 3) = 9 ------》逆波兰表达式: ["2","1","+","3","*"]
我们用代码实现就是将字符串中的逆波兰求出结果
示意图:
代码实现:
public int evalRPN(String[] tokens) {
Stack<Integer> st = new Stack<>();
for(int i=0;i<tokens.length;i++){
String tmp=tokens[i];
//判断字符产是否为算术符
if(!(tmp.equals("+")||
tmp.equals("-")||
tmp.equals("*")||
tmp.equals("/"))){
int in = Integer.parseInt(tmp);
st.push(in);//不是就入栈
}
else{
int right = st.pop();//弹出栈顶2个元素
int left = st.pop();
switch(tmp){//弹出的元素进行计算将结果进行入栈
case "+": st.push(left+right);
break;
case "-":
st.push(left-right);
break;
case "*":
st.push(left*right);
break;
case "/":
st.push(left/right);
break;
}
}
}
return st.peek();//走后栈顶元素为求解结果
}
7.递归与栈的关系
(1)关系说明
由于Java中存在虚拟机栈,而递归和栈的后进后出关系相同,我们可以将递归转化用栈来进行实现。
(2)栈,虚拟机栈,栈帧的区别
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作
虚拟机栈:是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型
栈帧:每个栈帧都对应一个被调用的方法,可以理解为方法的运行空间
二.队列
1.队列概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特点。其中进行插入操作的一端称为队尾(Tail/Rear) 出队列,进行删除操作的一端称为队头。
图解:
2.Java中队列的实现
底层是接口,需要借助链表LinkedList来进行实现,而链表又是根据双向链表进行实现的,下面我们会自己实现一个简单的队列
3.常用方法
方法 | 功能 |
boolean offer(E e) | 入队列 |
E poll() | 出队列 |
peek() | 获取队头元素 |
int size() | 获取队列中有效元素个数 |
boolean isEmpty() | 检测队列是否为空 |
4.队列常用操作练习
import java.util.LinkedList;
import java.util.Queue;
public class QueueDemo {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.offer(5);
System.out.println(queue.size());
System.out.println(queue.poll());//出队列
System.out.println(queue.peek());//查看队头元素
}
}
5.队列的模拟实现
(1)问题分析
队列一个队头标记,一个队尾标记,其中队尾添加相当于一个尾插,队头出队相当于为头删,其中还有一个size来标记队列中有多少个元素,从而进行判断队列是否为空,并且通过双向链表进行实现。
(2)代码实现
public class MyQueue {
public static void main(String[] args) {
Queue<String> st= new Queue<>();
// System.out.println(st.poll());
System.out.println(st.getSize());
st.offer("111");
st.offer("222");
st.offer("333");
System.out.println(st.getSize());
System.out.println(st.peek());
System.out.println(st.poll());
System.out.println(st.peek());
st.poll();
// st.poll();
System.out.println(st.peek());
// System.out.println(st.poll());
}
}
//原队列是接口,借助链表来进行实现
class Queue<E>{
Node<E> first;//队头
Node<E> last;//队尾
private int size;
public int getSize(){
return size;
}
//队尾中插入元素
public E offer(E ele){
//链表尾插
Node<E> newNode= new Node<>(ele);
if(first==null){
first=newNode;
last=newNode;
}else{
Node<E> cur=first;
while(cur.next!=null){
cur=cur.next;
}
cur.next=newNode;
newNode.prev=cur;
last=newNode;
}
size++;
return last.value;
}
//队头移除元素
public E poll(){
if(first==null){
return null;
}
first=first.next;
E value= first.value;
first.prev.next=null;
first.prev=null;
size--;
return value;
}
//获取队头元素,不移除
public E peek(){
if(first==null){
throw new RuntimeException("队列为空,无法获取");
}
return first.value;
}
public boolean isEmpty(){
return size==0;
}
}
class Node<E>{
E value;
Node<E> prev;
Node<E> next;
public Node(E value){
this.value=value;
}
}
6.循环队列
(1)引言
虽然Java中是通过链表来进行实现队列,但是还有另一种方法实现队列,那就是数组,由于直接使用数组会导致在删除后导致空间的浪费,所以就有了循环队列,这种方式实现的队列空间利用率是很好的。
(2)循环队列实现图解
(3)代码实现
class MyCircularQueue {
int[] arr;//数组
int count;//有效元素个数
int front;
int rear;
int size;//数组空间大小
public MyCircularQueue(int k) {
arr=new int[k];
size=arr.length;
}
//向队尾添加元素
public boolean enQueue(int value) {
if(isFull()){
return false;
}
arr[rear%size]=value;
rear++;
count++;
return true;
}
//出队列
public boolean deQueue() {
if(isEmpty()){
return false;
}
front++;
count--;
return true;
}
//获取队头元素
public int Front() {
if(isEmpty()){
return -1;
}
return arr[front%size];
}
//获取队尾元素
public int Rear() {
if(isEmpty()){
return -1;
}
return arr[(rear-1)%size];
}
//判空
public boolean isEmpty() {
return 0==count;
}
//判满
public boolean isFull() {
return size==count;
}
}
三.栈和队列的转换习题
1.用队列实现一个栈
这里使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
代码实现:
class MyStack {
Queue<Integer> s1 = new LinkedList<>();
Queue<Integer> s2 = new LinkedList<>();
public MyStack() {
}
public void push(int x) {
if(s1.size()!=0){
s1.offer(x);
}else if(s2.size()!=0){
s2.offer(x);
}else{
s1.offer(x);
}
}
public int pop() {
int val=0;//保存需要返回的值
//s1队列为空
if(!s1.isEmpty()){
while(s1.size()>1){
s2.offer(s1.poll());
}
val=s1.poll();
}else {
//s2队列不为空
while(s2.size()>1){
s1.offer(s2.poll());
}
val=s2.poll();
}
return val;
}
public int top() {
int val=0;//保存需要返回的值
//s2队列为空
if(!s1.isEmpty()){
while(s1.size()>1){
s2.offer(s1.poll());
}
val=s1.poll();
s2.offer(val);
}else {
//s2队列不为空
while(s2.size()>1){
s1.offer(s2.poll());
}
val=s2.poll();
s1.offer(val);
}
return val;
}
public boolean empty() {
return s1.size()==0 && s2.size()==0;
}
}
2.用栈实现一个队列
(1)实现目标
我们使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
)
(2)图解
(3)代码实现
class MyQueue {
Stack<Integer> st1;//入队列操作
Stack<Integer> st2;//出队列操作
public MyQueue() {
st1=new Stack<>();
st2=new Stack<>();
}
//入队列
public void push(int x) {
st1.push(x);
}
//出队列
public int pop() {
if(st2.empty()){
while(!st1.empty())
st2.push(st1.pop());
}
return st2.pop();
}
//插卡队头元素
public int peek() {
if(st2.empty()){
while(!st1.empty())
st2.push(st1.pop());
}
return st2.peek();
}
//判断队列是否为空
public boolean empty() {
return st1.empty() && st2.empty();
}
}
3.实现最小栈
(1)目的
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
(2)图解
(3 代码实现
class MinStack {
Stack<Integer> st;
public MinStack() {
st = new Stack<>();
}
//入栈
public void push(int val) {
if(st.empty()){
st.push(val);
st.push(val);
}else{
int min = getMin();
if(min>val){
st.push(val);
st.push(val);
}else{
st.push(min);
st.push(val);
}
}
}
//弹出栈顶元素
public void pop() {
st.pop();
st.pop();
}
//查看栈顶元素
public int top() {
return st.peek();
}
//得到最小值
public int getMin() {
int t = st.pop();
int min = st.peek();
st.push(t);
return min;
}
}