【数据结构与算法】栈与队列

一、什么是栈?

1.后进者先出,先进者后出,这就是典型的“栈”结构。
2.从栈的操作特性来看,是一种“操作受限”的线性表,只允许在端插入和删除数据。

二、为什么需要栈?

1.栈是一种操作受限的数据结构,其操作特性用数组和链表均可实现。
2.但,任何数据结构都是对特定应用场景的抽象,数组和链表虽然使用起来更加灵活,但却暴露了几乎所有的操作,难免会引发错误操作的风险。
3.所以,当某个数据集合只涉及在某端插入和删除数据,且满足后进者先出,先进者后出的操作特性时,我们应该首选栈这种数据结构。

三、如何实现栈?

1.栈的API
public class Stack {
//压栈
public void push(Item item){}
//弹栈
public Item pop(){}
//是否为空
public boolean isEmpty(){}
//栈中数据的数量
public int size(){}
//返回栈中最近添加的元素而不删除它
public Item peek(){}
}

实现

2.1 数组实现(自动扩容)

时间复杂度分析:根据均摊复杂度的定义,可以得数组实现(自动扩容)符合大多数情况是O(1)级别复杂度,个别情况是O(n)级别复杂度,比如自动扩容时,会进行完整数据的拷贝。
空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间,所以O(1)级别。我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。


// 基于数组实现的顺序栈
public class ArrayStack {private String[] items;  // 数组private int count;       // 栈中元素个数private int n;           //栈的大小// 初始化数组,申请一个大小为n的数组空间public ArrayStack(int n) {this.items = new String[n];this.n = n;this.count = 0;}// 入栈操作public boolean push(String item) {// 数组空间不够了,直接返回false,入栈失败。if (count == n) return false;// 将item放到下标为count的位置,并且count加一items[count] = item;++count;return true;}// 出栈操作public String pop() {// 栈为空,则直接返回nullif (count == 0) return null;// 返回下标为count-1的数组元素,并且栈中元素个数count减一String tmp = items[count-1];--count;return tmp;}
}

2.2 链表实现

时间复杂度分析:压栈和弹栈的时间复杂度均为O(1)级别,因为只需更改单个节点的索引即可。
空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间,所以O(1)级别。我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。
实现代码:(见另一条留言)

public class StackOfLinked<Item> implements Iterable<Item> {
//定义一个内部类,就可以直接使用类型参数
private class Node{
Item item;
Node next;
}
private Node first;
private int N;
//构造器
public StackOfLinked(){}
//添加
public void push(Item item){
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
N++;
}
//删除
public Item pop(){
Item item = first.item;
first = first.next;
N--;
return item;
}
//是否为空
public boolean isEmpty(){
return N == 0;
}
//元素数量
public int size(){
return N;
}
//返回栈中最近添加的元素而不删除它
public Item peek(){
return first.item;
}
@Override
public Iterator<Item> iterator() {
return new LinkedIterator();
}
//内部类:迭代器
class LinkedIterator implements Iterator{
int i = N;
Node t = first;
@Override
public boolean hasNext() {
return i > 0;
}
@Override
public Item next() {
Item item = (Item) t.item;
t = t.next;
i--;
return item;
}
}
}

三、栈的应用

1.栈在函数调用中的应用

操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构,用来存储函数调用时的临时变量。每进入一个函数,就会将其中的临时变量作为栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。

2.栈在表达式求值中的应用(比如:34+13*9+44-12/3)

利用两个栈,其中一个用来保存操作数,另一个用来保存运算符。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素优先级高,就将当前运算符压入栈,若比运算符栈顶元素的优先级低或者相同,从运算符栈中取出栈顶运算符,从操作数栈顶取出2个操作数,然后进行计算,把计算完的结果压入操作数栈,继续比较。

3.栈在括号匹配中的应用(比如:{}{()})

用栈保存为匹配的左括号,从左到右一次扫描字符串,当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明未匹配的左括号为非法格式。

4.如何实现浏览器的前进后退功能?

我们使用两个栈X和Y,我们把首次浏览的页面依次压如栈X,当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据一次放入Y栈。当点击前进按钮时,我们依次从栈Y中取出数据,放入栈X中。当栈X中没有数据时,说明没有页面可以继续后退浏览了。当Y栈没有数据,那就说明没有页面可以点击前进浏览了。

JVM 内存管理中有个“堆栈”的概念。栈内存用来存储局部变量和方法调用,堆内存用来存储 Java 中的对象。那 JVM 里面的“栈”跟数据结构中的“栈”是不是一回事呢?

内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。
内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区,动态数据区又分为栈区和堆区。

  • 代码区:存储方法体的二进制代码。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
  • 静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。
  • 栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
  • 堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。

leetcode上关于栈的题目:20,155,232,844,224,682,49

队列

一、什么是队列?

1.先进者先出,这就是典型的“队列”结构。
2.支持两个操作:入队enqueue(),放一个数据到队尾;出队dequeue(),从队头取一个元素。
3.所以,和栈一样,队列也是一种操作受限的线性表。

二、如何实现队列?

1.队列API

public interface Queue {
public void enqueue(T item); //入队
public T dequeue(); //出队
public int size(); //统计元素数量
public boolean isNull(); //是否为空
}

2.实现

** 2.1 数组**


// 用数组实现的队列
public class ArrayQueue {// 数组:items,数组大小:nprivate String[] items;private int n = 0;// head表示队头下标,tail表示队尾下标private int head = 0;private int tail = 0;// 申请一个大小为capacity的数组public ArrayQueue(int capacity) {items = new String[capacity];n = capacity;}// 入队public boolean enqueue(String item) {// 如果tail == n 表示队列已经满了if (tail == n) return false;items[tail] = item;++tail;return true;}// 出队public String dequeue() {// 如果head == tail 表示队列为空if (head == tail) return null;// 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了String ret = items[head];++head;return ret;}// 入队操作,将item放入队尾,如图public boolean enqueue(String item) {// tail == n表示队列末尾没有空间了if (tail == n) {// tail ==n && head==0,表示整个队列都占满了if (head == 0) return false;// 数据搬移for (int i = head; i < tail; ++i) {items[i-head] = items[i];}// 搬移完之后重新更新head和tailtail -= head;head = 0;}items[tail] = item;++tail;return true;}
}

在这里插入图片描述
2.2 循环链表思想
关键队空条件 head == tail
队满判断条件 (tail+1)%n=head


public class CircularQueue {// 数组:items,数组大小:nprivate String[] items;private int n = 0;// head表示队头下标,tail表示队尾下标private int head = 0;private int tail = 0;// 申请一个大小为capacity的数组public CircularQueue(int capacity) {items = new String[capacity];n = capacity;}// 入队public boolean enqueue(String item) {// 队列满了if ((tail + 1) % n == head) return false;items[tail] = item;tail = (tail + 1) % n;return true;}// 出队public String dequeue() {// 如果head == tail 表示队列为空if (head == tail) return null;String ret = items[head];head = (head + 1) % n;return ret;}
}

2.3 链表实现

public class LinkedQueue {
//定义一个节点类
private class Node{
String value;
Node next;
}
//记录队列元素个数
private int size = 0;
//head指向队头结点,tail指向队尾节点
private Node head;
private Node tail;
//申请一个队列
public LinkedQueue(){}
//入队
public boolean enqueue(String item){
Node newNode = new Node();
newNode.value = item;
if (size == 0) head = newNode;
else tail.next = newNode;
tail = newNode;
size++;
return true;
}
//出队
public String dequeue(){
String res = null;
if(size == 0) return res;
if(size == 1) tail = null;
res = head.value;
head = head.next;
size--;
return res;
}

三、队列有哪些常见的应用?

1.阻塞队列

1)在队列的基础上增加阻塞操作,就成了阻塞队列。
2)阻塞队列就是在队列为空的时候,从队头取数据会被阻塞,因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后在返回。
3)从上面的定义可以看出这就是一个“生产者-消费者模型”。这种基于阻塞队列实现的“生产者-消费者模型”可以有效地协调生产和消费的速度。当“生产者”生产数据的速度过快,“消费者”来不及消费时,存储数据的队列很快就会满了,这时生产者就阻塞等待,直到“消费者”消费了数据,“生产者”才会被唤醒继续生产。不仅如此,基于阻塞队列,我们还可以通过协调“生产者”和“消费者”的个数,来提高数据处理效率,比如配置几个消费者,来应对一个生产者。

2.并发队列

1)在多线程的情况下,会有多个线程同时操作队列,这时就会存在线程安全问题。能够有效解决线程安全问题的队列就称为并发队列。
2)并发队列简单的实现就是在enqueue()、dequeue()方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或取操作。
3)实际上,基于数组的循环队列利用CAS原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。
3.线程池资源枯竭是的处理
在资源有限的场景,当没有空闲资源时,基本上都可以通过“队列”这种数据结构来实现请求排队。

[剑指offer][JAVA]面试题第[09]题[用两个栈实现队列][LinkedList]

主要整理参考作者:姜威
笔记整理来源: 王争 数据结构与算法之美

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/425014.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线性代数的本质笔记-更新ing

1. 序言 线性代数不光要会计算&#xff0c;还要理解掌握其几何直观。 2. 向量究竟是什么&#xff1f; 物理学&#xff1a;向量是空间中的箭头&#xff0c;具有长度和方向两个属性。计算机&#xff1a;向量是一个有序数表。比如房屋的参数信息可以根据相关属性按准许列成一个…

10项目开工会

立项启动准备 启动会的任务 如何进行项目启动

[Leetcode][第337题][JAVA][打家劫舍3][递归][动态规划]

【问题描述】[中等] 【解答思路】 1. 动态规划 第 1 步&#xff1a;状态定义 dp[node][j] &#xff1a;这里 node 表示一个结点&#xff0c;以 node 为根结点的树&#xff0c;并且规定了 node 是否偷取能够获得的最大价值。 j 0 表示 node 结点不偷取&#xff1b; j 1 表示…

二元随机变量函数的分布

在前面的文章记录了二元随机变量的定义、离散型二元随机变量的联合分布律/联合概率密度函数、边际分布律/边际概率密度函数、条件分布律/条件概率密度 &#xff0c;以及对应的 联合分布函数、边际分布函数、条件分布函数。这篇文档介绍二元随机变量函数的分布。 二元随机变量函…

第四十期:2019年度十大Web开发趋势

本文和您一起讨论那些本年度改变软件开发行业、特别是Web开发方面的十大趋势。 如今&#xff0c;随着各种新趋势的层出不穷&#xff0c;Web和移动领域的创新不仅改变了人们、乃至整个社会的日常行为习惯、以及业务处理方式&#xff0c;而且也使得开发人员能够轻松、且高效地创建…

【数据结构与算法】排序 冒泡、插入、选择 O(n^2)

冒泡、插入、选择 O(n2) 基于比较 快排、归并 O(nlogn) 基于比较 计数、基数、桶 O(n) 不基于比较 一、如何分析一个排序算法&#xff1f; 学习排序算法的思路&#xff1f;明确原理、掌握实现以及分析性能。如何分析排序算法性能&#xff1f;从执行效率、内存消耗以及稳定性…

[Leetcode][第336题][JAVA][回文对][暴力][HashSet][字典树]

【问题描述】[困难] 【解答思路】 1. 暴力&#xff08;超时&#xff09; 时间复杂度&#xff1a;O(n 2 m)&#xff0c;其中 n 是字符串的数量&#xff0c;m 是字符串的平均长度 空间复杂度&#xff1a;O(1) class Solution {public List<List<Integer>> palindr…

第十二期:面试官问你什么是消息队列?把这篇甩给他!

消息队列不知道大家看到这个词的时候&#xff0c;会不会觉得它是一个比较高端的技术&#xff0c;反正我是觉得它好像是挺牛逼的。 一、什么是消息队列&#xff1f; 消息队列不知道大家看到这个词的时候&#xff0c;会不会觉得它是一个比较高端的技术&#xff0c;反正我是觉得它…

第三章 随机变量的数字特征

数学期望 数学期望用来反映平均情况。 定义 设离散型随机变量X的分布律为P(Xxk)pk,k1,2,3...&#xff0c;若级数∑∞k1xkpk是收敛的&#xff0c;则称级数∑∞k1xkpk的值为随机变量X的数学期望。记为E(X)。E(X)∑k1∞xkpkpk可以理解为加权平均中的权值。数学期望又称为 均值。 …

python二进制、字符编码及文件操作

1. 二进制 bin()十进制转二进制 0b oct&#xff08;&#xff09;十进制转八进制 0o hex&#xff08;&#xff09;十进制转十六进制 0x&#xff0c;4个二进制对应1个16进制&#xff0c;用于网络编程&#xff0c;数据存储 print(int(110111,2)) 55 print(int(ffff,16)) 65535 p…

【数据结构与算法】【字符串匹配】Trie树

单模式串匹配 BF 算法和 RK 算法 BM 算法和 KMP 算法多模式串匹配算法 Trie 树和 AC 自动机 一、 什么是“Trie树”&#xff1f; 1. 他是一种树形结构&#xff0c;是一种专门处理字符串匹配的数据结构&#xff0c;解决在一组字符串集合中快速查找某个字符串的问题。 2. Trie…

第十三期:消灭 Java 代码的“坏味道”

代码中的"坏味道"&#xff0c;如"私欲"如"灰尘"&#xff0c;每天都在增加&#xff0c;一日不去清除&#xff0c;便会越累越多。如果用功去清除这些"坏味道"&#xff0c;不仅能提高自己的编码水平&#xff0c;也能使代码变得"精白…

[Leetcode][第100题][JAVA][相同的树][二叉树][深度遍历][递归]

【问题描述】[中等] 【解答思路】 深度遍历/递归 终止条件与返回值&#xff1a; 当两棵树的当前节点都为 null 时返回 true 当其中一个为 null 另一个不为 null 时返回 false 当两个都不为空但是值不相等时&#xff0c;返回 false 执行过程&#xff1a;当满足终止条件时进…

第十四期:5 个 JS 不良编码习惯,你占几个呢?

在阅读JavaScript代码时&#xff0c;你是否有过这种感觉&#xff1a;你几乎不明白代码的作用&#xff1f;代码使用了很多 JavaScript 技巧&#xff1f;命名和编码风格太过随意&#xff1f; 这些都是不良编码习惯的征兆。 在阅读JavaScript代码时&#xff0c;你是否有过这种感觉…

第十五期:详解Java集合框架,让你全面掌握!

一、Java集合框架概述 集合可以看作是一种容器&#xff0c;用来存储对象信息。所有集合类都位于java.util包下&#xff0c;但支持多线程的集合类位于java.util.concurrent包下。 数组与集合的区别如下&#xff1a; 1&#xff09;数组长度不可变化而且无法保存具有映射关系的…

[Leetcode][第98 450 700 701题][JAVA][二叉搜索树的合法性、增、删、查][递归][深度遍历]

【二叉搜索树定义】&#xff08;BST&#xff09; 二叉搜索树&#xff08;Binary Search Tree&#xff0c;简称 BST&#xff09;是一种很常用的的二叉树。它的定义是&#xff1a;一个二叉树中&#xff0c;任意节点的值要大于等于左子树所有节点的值&#xff0c;且要小于等于右边…

关于CNN的权重共享,CNN到底学到了什么?

CNN的fliter里的每个值都是学习出来的不是事先设定好的。 经过fliter处理后得到是特征图(feature map) 卷积减少权重参数的本质&#xff1a; 权重共享&#xff0c;不同的fliter会在某些神经元上权重共享。 到底fliter&#xff0c;到底CNN学到了什么&#xff1f; 底层的flite…

复盘二进制的习题(1)

本文是对近期二进制专题的leetcde习题的复盘。文中的解决思路来源于leetcode的讨论&#xff0c;以及一些网页。 342 判断一个整数(32bits)是否是4的次幂。  写出4i,i0,1,2,3,4...的二进制表示&#xff0c;查找规律。会发现这些数的特征是 a 都>0&#xff1b;b 只有一位是…