栈:先进后出-》后进先出【栈中元素拿出来就不能放进去了!!!】
队列:先进先出
栈
12是最先放进去的,但是12在拿出来的时候是最后拿出来的-》先进后出
与栈相关的方法有四个
栈的使用
1.用递归的方式打印出链表的逆置
2.括号的匹配
第一个遇到的右括号要跟最后一个左括号匹配
Q:我们为什么要用栈来解决这个题呢?
因为栈的插入和删除的时间复杂度为O(1)更为合适
import java.util.Stack;
class Solution {
public boolean isValid(String s) {
Stack<Character> stack=new Stack();
for(int i=0;i<s.length();i++){
char ch=s.charAt(i);
if(ch=='('||ch=='{'||ch=='['){
stack.push(ch);//如果有左括号,则push进去
}
else{
if(stack.isEmpty()){//栈内为空,说明没有左括号放进去,所以不可能匹配
return false;
}
char top= stack.peek();
if(ch==')'&&top=='('||ch=='}'&&top=='{'||ch==']'&&top=='['){
stack.pop();//如果有匹配的,则一起pop出去
}else {
return false;
}
}
}
if(!stack.isEmpty()){
return false;
}
return true;
}
}
易错点记忆
概念区分
栈,虚拟机栈,栈帧
栈:数据结构栈
虚拟机栈:我们内存当中的一块区域
栈帧:我们每次调用方法的时候要在栈上开辟一块区域,我们叫它栈帧
队列
总结:我们不管是用单链表还是双链表都能实现栈和队列,但是我们使用双向链表更容易实现栈和队列
栈的题目总结,一定要注意empty()的条件以及if执行条件【是否添加括号】,不要单纯往上添加
队列的方法
1.queue表示的是普通队列,而deque表示的是双端队列;通过了解我们知道linkedlist的使用范围很广,我们可以用它来表示队列,栈等
2.队列的方法:(1)offer表示添加元素进去,切记队头进,队尾出(add也表示添加元素)
(2)poll表示拿出元素(remove同理)
(3)peek方法与栈同理,表示瞄一眼,看到的是队头元素(element同理)上面peek运行得到的是“2”
1. 用链表实现栈
顺序栈可以用stack,链式栈可以用linkedlist来实现
2.用链表实现队列
双向链表实现
也可以把linkedlist当作一个队列,从队尾进,对头出
单链表也可以实现,但是没有双向链表好实现
3.用双向链表实现队列的插入offer和删除poll方法
import java.util.Queue;
public class MyQueue {
static class ListNode {//用这个链表表示队列
private int val;
private ListNode prev;
private ListNode next;
public ListNode(int val) {
this.val = val;
}
}
//队列中的队头和队尾
private ListNode front;
private ListNode rear;
private int usedSize;
public void offer(int x) {
//用双向链表来表示队列插入元素
ListNode node = new ListNode(x);
if (front == null) {
front = rear = node;
} else {
node.next = front;
front.prev =node;
front = node;
}
usedSize++;
}
public int poll() {
int ret = rear.val;
//用双向链表来表示队列的元素删除 删除尾节点
if (front == null) {
return -1;
}
if (front == rear) {
front = null;
rear = null;
usedSize--;
return ret;
}
/*
方法1.rear=rear.prev;
rear.next=null;*/
rear.prev.next = null;
rear = rear.prev;
usedSize--;
return ret;
}
public static void main(String[] args) {
MyQueue queue=new MyQueue();
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
循环队列
注意:1.为了避免出现越界的问题,直接空出一个位置来,即在rear和front之间空出一个位置
后面的获取队尾元素的方法中,当rear不为0,则就直接rear-1
当队列长度为k的时候,为了使空余位置的方法可以成功实现,所以我们就让数组可容纳元素为k+1个,即new int[k+1]
2.代码展示:
class MyCircularQueue {
//类底下要有成员变量
private int[] elem;
private int front;//front和rear此时表示的是循环队列的数组下标
private int rear;
public MyCircularQueue(int k) {//构造函数,对变量进行初始化
this.elem=new int [k+1];
}
public boolean enQueue(int value) {
if(isFull()){
return false;
}
elem[rear]=value;
rear=(rear+1)%elem.length;
return true;
}//循环队列中不能使用rear++,因为会越界,必须使用这个
public boolean deQueue() {
if(isEmpty()){
return false;
}else{
front=(1+front)%elem.length;
return true;
}
}
public int Front() {//获取队首元素,就是根据下标来找元素,此时front表示的就是下标
if(isEmpty()){
return -1;
}
return elem[front];
}
public int Rear() {
if(isEmpty()){
return -1;
}
int index=(rear==0)?elem.length-1:rear-1;
return elem[index];
}
public boolean isEmpty() {//front和rear重合的话就表示队列为空
if(front==rear){
return true;
}
return false;
}
双端队列(Deque)
第一个是双端队列的线性实现,添加元素就可以直接使用push等方法
第二个是双端队列的链式实现,添加元素使用offer等方法
队列和栈的相互应用
用队列实现栈
注意:1.要分清pop方法和top方法的区别,top是只返回栈顶元素,所以是瞄一眼,只要把栈顶元素存在temp中,直接返回temp就可以了
2.抛出poll()方法,括号中没有变量
3.队列在初始化的时候使用的是new LinkedList
class MyStack {
private Queue<Integer> qu1;
private Queue<Integer> qu2;
public MyStack() {
qu1=new LinkedList<>();
qu2=new LinkedList<>();
}
public void push(int x) {
if(!qu1.isEmpty()){
qu1.offer(x);
}else if(!qu2.isEmpty()){
qu2.offer(x);
}else{
qu1.offer(x);
}
}
public int pop() {//将除了栈顶元素之外的元素都放在另一个为空的队列中
//最后抛出的那个就是栈顶元素
if(empty()){
return -1;
}
if(!qu1.isEmpty()){
int size=qu1.size();
for(int i=0;i<size-1;i++){
int temp=qu1.poll();
qu2.offer(temp);
}
return qu1.poll();
}else{
int size=qu2.size();
for(int i=0;i<size-1;i++){
int temp=qu2.poll();
qu1.offer(temp);
}
return qu2.poll();
}
}
public int top() {//返回栈顶元素就相当瞄一眼,所以要将所有元素从有的一边挪到空的一边
//temp仅仅记录值,最后也返回temp即可
if(empty()){
return -1;
}
int temp=-1;
if(!qu1.isEmpty()){
int size=qu1.size();
for(int i=0;i<size;i++){
temp=qu1.poll();
qu2.offer(temp);
}
return temp;
}else{
int size=qu2.size();
for(int i=0;i<size;i++){
temp=qu2.poll();
qu1.offer(temp);
}
return temp;
}
}
public boolean empty() {
return qu1.isEmpty()&&qu2.isEmpty();
}
}
用栈实现队列
注意:1.栈在初始化的时候使用的是new Stak()
class MyQueue {
private Stack<Integer> s1;
private Stack<Integer> s2;
public MyQueue() {
s1=new Stack<>();
s2=new Stack<>();
}
public void push(int x) {//入的时候统一放在s1里面;
//它这是新建的两个栈,所以放元素的时候肯定没有东西
s1.push(x);
}
public int pop() {//出的时候统一从s2中拿出
if(empty()){
return -1;
}
if(s2.isEmpty()){
while(!s1.isEmpty()){
s2.push(s1.pop());
}
}
return s2.pop();
}
public int peek() {
if(empty()){
return -1;
}
if(s2.isEmpty()){
while(!s1.isEmpty()){
s2.push(s1.pop());
}
}
return s2.peek();
}
public boolean empty() {
return s1.isEmpty()&&s2.isEmpty();
}
}
栈和队列对比总结
1.栈和队列它们插入和删除元素的时间复杂度均为O(1)
2.用无头单链表存储队列,front引用队头,back引用队尾,则在进行出队操作的时:
【1】如果队列中有多个节点的时候,只需要修改front
【2】如果队列中只有一个节点的时候,front和back都需要修改
3.队列遵循先进先出的原则,所以队列只能从队头删除元素
4.栈是尾部插入和删除,一般使用顺序表实现;队列是头部删除,尾部插入,一般使用链表实现
5.循环队列
【1】循环队列也是队列的一种,是一种特殊的线性数据结构
【2】循环队列通过设置计数的方式可以判断队列空或满——设置计数即添加一个字段来记录队列中有效元素的个数,如果队列中有效元素个数=空间总大小的时候队列满,如果队列中有效元素个数为0时队列空
【3】循环队列的队头为front,队尾为rear,循环队列长度为N,最多存储N-1个数据。其队内有效长度为(rear-front+N)%N