栈和队列 【数据结构】

x33g5p2x  于2021-09-29 转载在 其他  
字(5.4k)|赞(0)|评价(0)|浏览(496)

**前言:**栈和队列都是基于List(线性表)来实现的,且其限制比List更严格(提供的操作更少),即 List 比站栈和队列更灵活
栈的实际应用场景非常多,队列的实际应用场景更多

栈 (Stack)

概念

栈是限定仅在末尾进行插入和删除操作的线性表,表尾指栈顶,而不是栈底

栈限制了线性表的插入和删除位置,它始终在栈顶进行
则:栈底是固定的,最先进栈的只能在栈底

先进后出
只支持3个核心操作:入栈,出栈,取栈顶元素

JVM 的内存区域划分(JVM内存模型),JVM虚拟机栈,此处的栈 特指JVM中的内存区域
而今天学习的栈是,数据结构中的通用概念

栈的实现

以数组为例: 相对简单,直接上代码:

  1. public class MyStack1 {
  2. //管理一些 int 元素,不考虑扩容问题
  3. private int[] array = new int[100];
  4. private int size = 0; //栈中存在多少个有效元素
  5. //入栈
  6. public void push(int x){
  7. array[size] = x;
  8. size++;
  9. }
  10. //取栈顶元素(最后进来的那个元素)
  11. public int peek(){
  12. return array[size-1];
  13. }
  14. //出栈
  15. public int pop(){
  16. int ret = array[size-1];
  17. size--;
  18. return ret;
  19. }
  20. }

队列 (Queue)

概念

先进先出
只支持3个核心操作:入队列,出队列,取队首元素

举例:食堂排队买饭,先来的同学排在前边,后来的排在后边,先来的先买到饭,后来的后买到饭

队列的实现

实现队列的方式有很多,可以使用数组来实现,也可以使用链表来实现

以链表为例不带傀儡节点

  1. public class MyQueue1 {
  2. // Node 为内部类,定义在某个类或方法中的类
  3. // static 的效果是,创建 Node 的实例不依赖 MyQueue的这个类的实例
  4. static class Node{
  5. public int val;
  6. Node next = null;
  7. //提供构造方法
  8. public Node(int val) {
  9. this.val = val;
  10. }
  11. }
  12. //创建一个链表,需要一个头节点 此处以 不带傀儡节点为例
  13. //使用链表实现队列,入队列从表头插入,出队列从表尾删除
  14. // 或 入队列从表尾插入,出队列从表头删除
  15. // 无论上述那种,都需要记录头尾
  16. private Node head = null;
  17. private Node tail = null;
  18. //以 尾入头出 为例
  19. //入队列 尾部插入
  20. public void offer(int val){
  21. Node newNode = new Node(val);
  22. if(head == null){
  23. head = newNode;
  24. tail = newNode;
  25. return;
  26. }
  27. //非空链表
  28. tail.next = newNode;
  29. tail = tail.next;
  30. }
  31. //出队列 头部删除
  32. public Integer pull(int val){
  33. //判定队列是否为空
  34. if(head == null){
  35. //若出队列失败,返回一个错误值
  36. return null;
  37. }
  38. int ret = val;
  39. head = head.next;
  40. if(head == null){
  41. //删除之后,队列变成空
  42. tail = null;
  43. }
  44. return ret;
  45. }
  46. //取队首元素
  47. public Integer peek(){
  48. if(head == null){
  49. return null;
  50. }
  51. return head.val;
  52. }
  53. }

此处出队列,返回值为Integer
.
若为 int,则返回一个错误值,例如:-1,0等
但若返回-1,就不清楚是出队列失败返回的-1,还是本来就返回的是-1,故 -1 不能做非法值
Integer 包装类,也是一个引用类型的对象,引用类型就可以是一个空引用,就可以表示非法情况
.
故:此处返回值不用 int,使用 Integer
.

以数组为例

一般我们会想到,入队列,即在数组后插入一个元素
而出队列,就会依次覆盖前一个元素(搬运操作),但这样效率会比较低

下图所展现的过程就是低效的搬运过程:

循环队列(高效操作)

不用搬运也可完成出队列操作

画图表示:

当前队列中的有效元素就算是 [head,tail)
head 始终就是队首元素,tail 始终就是队尾的下一个元素
当 head 和 tail 重合时,此时队列为空

有效元素的情况:

  • tail 在前,head 在后

  • tail 在前,head 在后

  • head 和 tail 重合

当 head 和 tail 重合时,可能是空队列也可能是满队列

  1. 空队列

  1. 满队列

模拟实现:

  1. public class MyQueue2 {
  2. private int[] array = new int[100];
  3. //size 表示元素个数
  4. private int size = 0;
  5. // [head,tail) 表示有效元素,但tail 可能在 head之前
  6. private int head = 0;
  7. private int tail = 0;
  8. //入队列
  9. public void offer(int val){
  10. //判定是否为满队列
  11. if(size == array.length){
  12. System.out.println("队列已满,无法插入!");
  13. return;
  14. }
  15. array[tail] = val;
  16. tail++;
  17. //若tail++,超出了数组的有效范围,那么让tail从0开始
  18. if(tail >= array.length){
  19. tail = 0;
  20. }
  21. size++;
  22. }
  23. //出队列
  24. public Integer poll(){
  25. //判定是否为空队列
  26. if(size == 0){
  27. System.out.println("队列为空,无法删除!");
  28. return null;
  29. }
  30. Integer ret = array[head];
  31. head++;
  32. if(head >= array.length){
  33. head = 0;
  34. }
  35. size--;
  36. return ret;
  37. }
  38. //取队首元素
  39. public Integer peek(){
  40. //判定是否为空队列
  41. if(size == 0){
  42. return 0;
  43. }
  44. return array[head];
  45. }
  46. }

栈和队列实现起来非常容易,复杂的就是结合实际场景来使用

栈和队列的方法

栈的方法

方法作用
Stack( )定义一个空栈
push( )入栈
pop( )出栈(栈顶值)
peek( )取栈顶值(不出栈)
empty( )判断栈是否为空

代码示例:

  1. public static void main(String[] args) {
  2. Stack<Integer> stack = new Stack<>();
  3. //入栈
  4. stack.push(1);
  5. stack.push(2);
  6. stack.push(3);
  7. stack.push(4);
  8. stack.push(5);
  9. System.out.println(stack.empty());
  10. //出栈
  11. int ret = stack.pop();
  12. System.out.println(ret); //5
  13. ret = stack.pop();
  14. System.out.println(ret); //4
  15. ret = stack.pop();
  16. System.out.println(ret); //3
  17. ret = stack.pop();
  18. System.out.println(ret); //2
  19. ret = stack.pop();
  20. System.out.println(ret); //1
  21. System.out.println(stack.empty());
  22. }

输出结果:

验证了栈的 先进后出 的特性

若为空栈,在进行出栈,则可能发生异常

  1. public static void main(String[] args) {
  2. Stack<Integer> stack = new Stack<>();
  3. //入栈
  4. stack.push(1);
  5. //出栈
  6. int ret = stack.pop();
  7. System.out.println(ret);
  8. ret = stack.pop();
  9. System.out.println(ret);
  10. }

标准库中的 Stack 如果为空,再次 pop / peek,都会抛出异常

队列的方法

一般建议使用 offer( ),poll( ),peek( ) 这组操作

方法作用错误处理
offer( )将指定元素插入队列成功返回 true,否则返回 false
poll( )取并移除队首元素 (出队列)队列为空,返回null
peek( )取队首元素,但不出队列队列为空,返回null
add( )入队列抛出 IllegalStateException 异常
remove( )取并移出队首元素(出队列)队列为空,抛出 NoSuchElementException 异常
element( )取队首元素,但不出队列队列为空,抛出 NoSuchElementException 异常
  • ①add( ) remove( ) element( ) 示例:
  1. public static void main(String[] args) {
  2. //初始化
  3. Queue<Integer> queue = new LinkedList<>();
  4. //入队列
  5. queue.add(1);
  6. queue.add(2);
  7. queue.add(3);
  8. queue.add(4);
  9. //取队首元素 (不出队列)
  10. System.out.println("使用element()取队首元素....");
  11. System.out.println(queue.element());
  12. System.out.println();
  13. //出队列
  14. System.out.println("使用remove()出队列....");
  15. System.out.println(queue.remove());
  16. System.out.println(queue.remove());
  17. System.out.println(queue.remove());
  18. System.out.println(queue.remove());
  19. }

输出结果:

验证了队列的 先进先出 的特性
特殊情况:

  • 队列为空,取队首元素
  1. public static void main(String[] args) {
  2. Queue<Integer> queue = new LinkedList<>();
  3. //取队首元素 (不出队列)
  4. System.out.println("使用element()取队首元素....");
  5. System.out.println(queue.element());
  6. System.out.println();
  7. }

输出结果:
.

.

  • 队列为空,出队列
  1. public static void main(String[] args) {
  2. Queue<Integer> queue = new LinkedList<>();
  3. //出队列
  4. System.out.println("使用remove()出队列....");
  5. System.out.println(queue.remove());
  6. }

输出结果:
.

  • ②offer( ),poll( ),peek( ) 示例:
  1. public static void main(String[] args) {
  2. //初始化
  3. Queue<Integer> queue = new LinkedList<>();
  4. //将指定元素插入队列
  5. queue.offer(66);
  6. queue.offer(88);
  7. queue.offer(99);
  8. System.out.println();
  9. //取队首元素
  10. System.out.println("使用peek()取队首元素....");
  11. System.out.println(queue.peek());
  12. System.out.println();
  13. //出队列
  14. System.out.println("使用poll()出队列....");
  15. System.out.println(queue.poll());
  16. System.out.println(queue.poll());
  17. System.out.println(queue.poll());
  18. }

输出结果:

特殊情况:

  • 队列为空,取队首元素
  1. public static void main(String[] args) {
  2. //初始化
  3. Queue<Integer> queue = new LinkedList<>();
  4. //取队首元素
  5. System.out.println("使用peek()取队首元素....");
  6. System.out.println(queue.peek());
  7. System.out.println();
  8. }

输出结果:
.

.

  • 队列为空,出队列
  1. public static void main(String[] args) {
  2. //初始化
  3. Queue<Integer> queue = new LinkedList<>();
  4. //出队列
  5. System.out.println("使用poll()出队列....");
  6. System.out.println(queue.poll());
  7. System.out.println();
  8. }

输出结果:
.

由上述例子可知,第①组操作失败就会抛出异常,第②组操作失败会返回一个错误值
故:一般建议使用 offer( ),poll( ),peek( ) 这组操作

下篇我们会讨论有关栈和队列的面试题~~

相关文章