栈和队列是数据结构中非常常见和基础的线性表,在某些场合栈和队列使用很多,因此本篇主要介绍栈和队列,并用Java实现基本的栈和队列,同时用栈和队列相互实现。

栈:栈是一种基于“后进先出”策略的线性表。在插入时(入栈),最先插入的元素在栈尾,最后插入的元素在栈顶;在删除时(出栈),最后插入的元素先出栈,最先插入的元素最后出栈。由此可见,对栈的插入和删除操作都是在栈顶位置进行的。

在Java中,提供了一个类Stack<E>来实现栈的这些特性,并提供了一些常用的方法来对栈进行操作。

Stack<E>源码:

  1. package java.util;
  2. public
  3. class Stack<E> extends Vector<E> {
  4. /**
  5. * Creates an empty Stack.
  6. */
  7. public Stack() {
  8. }
  9. //入栈
  10. public E push(E item) {
  11. addElement(item);
  12. return item;
  13. }
  14. //出栈
  15. public synchronized E pop() {
  16. E obj;
  17. int len = size();
  18. obj = peek();
  19. removeElementAt(len - 1);
  20. return obj;
  21. }
  22. //查看栈顶元素,但不会出栈
  23. public synchronized E peek() {
  24. int len = size();
  25.  
  26. if (len == 0)
  27. throw new EmptyStackException();
  28. return elementAt(len - 1);
  29. }
  30. //判断栈是否为空
  31. public boolean empty() {
  32. return size() == 0;
  33. }
  34. //在栈中查找某个元素,返回其位置
  35. public synchronized int search(Object o) {
  36. int i = lastIndexOf(o);
  37. if (i >= 0) {
  38. return size() - i;
  39. }
  40. return -1;
  41. }
  42. }

通过源码可以看出,类Stack实际上继承了Vector类,而Vector是一个基于数组的集合类,它里面的元素都存储在“protected Object[] elementData;”这个数组里面,而变量“protected int elementCount;”用于统计数组元素个数。通过上面源码来看Stack类里面的push方法,实际上是通过Vector类里面的addElement(item);方法向数组里添加元素(如下面代码),添加之前先判断数组是不是满了,如果满了先进行扩容操作,防止发生溢出(数组下标越界)。然后将新元素添加到数组elementData,元素个数+1。

  1. public synchronized void addElement(E obj) { //向数组里面添加元素
  2. modCount++;
  3. ensureCapacityHelper(elementCount + 1); //当数组填满时,对数组“扩容”,防止溢出
  4. elementData[elementCount++] = obj; //数组添加新元素,元素个数加1
  5. }

再来看Stack类里面的pop方法,先通过peek方法获取数组最后的元素(len - 1位置),然后通过Vector类里面的removeElementAt(int index);方法将数组末尾的元素置空(null)并使元素个数-1。将数组末尾元素置空的目的是防止发生对象游离。Java的垃圾收集策略是回收所有无法被访问的对象的内存,而我们在执行pop操作时,被弹出的元素的引用仍然存在于数组中,但这个元素实际上已经是一个“孤儿”了,它永远都不会被访问了。但它依然占着内存,Java的垃圾收集器不知道这一点,这种情况又称为内存泄漏。因此将将数组末尾的元素置空(null)可以防止这一情况发生。

实际上我们也可以通过一个普通的数组来实现栈的操作,下面代码实现了这一点。obj数组存放栈中的元素,index记录元素个数。入栈时数组obj从前往后依次添加元素,每添加一个元素,就判断数组是否满了,如果满了,则扩容为原来的2倍,防止发生溢出(数组下标越界)。扩容时,新建一个容量为原来两倍的新数组,将原来数组中的元素逐个复制到新数组中,然后将新数组赋给原来的数组obj,完成数组扩容。出栈时,数组obj从后往前依次取出元素,获取元素值以后,将数组中出栈元素位置置空(null),避免对象游离,原因如上所述。每出栈一次,判断数组空间的使用率是否小于一半,如果小于一半,则缩小为原来的一半(缩容),减少空间浪费。缩容操作与扩容操作原理类似,不再赘述。代码如下所示:

  1. /*
  2. * 数组实现栈
  3. */
  4. public class ArrayToStack {
  5.  
  6. private Object[] obj;
  7. private int index; //元素个数,注意不是数组长度
  8.  
  9. public ArrayToStack(){
  10. obj = new Object[4]; //数组初始容量4
  11. index = 0;
  12. }
  13. //入栈,数组obj从前往后依次添加元素
  14. public void push(Object obj){
  15. this.obj[index++] = obj;
  16. if(index == this.obj.length){ //如果数组满了,则扩容为原来的2倍,防止溢出
  17. this.obj = resize(this.obj.length); //将扩容后的数组赋给obj
  18. }
  19. }
  20. //出栈,数组obj从后往前依次取出元素
  21. public Object pop(){
  22. if(index == 0) return null;
  23. Object data = this.obj[index-1]; //先取出出栈元素
  24. this.obj[index-1] = null; //将数组中出栈元素位置置空(null),避免对象游离
  25. index--;
  26. if(index == this.obj.length/2-1){ //如果数组空间使用率小于一半,则缩小为原来的一半,减少空间浪费
  27. this.obj = reduce(this.obj.length); //将缩小后的数组赋给obj
  28. }
  29. return data;
  30. }
  31.  
  32. public boolean isEmpty(){
  33. return index == 0;
  34. }
  35. //元素个数
  36. public int size(){
  37. return index;
  38. }
  39.  
  40. public void display(){
  41. for(int i=0;i<index;i++){
  42. System.out.print(obj[i]+" ");
  43. }
  44. System.out.println();
  45. }
  46. //数组扩容
  47. private final Object[] resize(int size){
  48. Object[] newobj = new Object[size<<1]; //扩容为原来的2倍
  49. for(int i=0;i<index;i++){
  50. newobj[i] = this.obj[i];
  51. }
  52. return newobj;
  53. }
  54. //数组缩小(缩容)
  55. private final Object[] reduce(int size){
  56. Object[] newobj = new Object[size>>1]; //缩小为原来的一半
  57. for(int i=0;i<index;i++){
  58. newobj[i] = this.obj[i];
  59. }
  60. return newobj;
  61. }
  62.  
  63. public static void main(String[] args) {
  64. ArrayToStack stack = new ArrayToStack();
  65. //入栈
  66. stack.push(0);
  67. stack.push(1);
  68. stack.push(2);
  69. System.out.println(stack.obj.length); //4 数组还未扩容
  70. stack.push(3); //入栈顺序:0、1、2、3
  71. System.out.println(stack.obj.length); //8 数组进行了扩容
  72. System.out.println(stack.isEmpty()); //false
  73. System.out.println(stack.size()); //
  74. stack.display(); //0 1 2 3
  75. //出栈
  76. System.out.println(stack.pop()); //
  77. System.out.println(stack.obj.length); //4 数组进行了缩容
  78. System.out.println(stack.pop()); //
  79. System.out.println(stack.pop()); //
  80. System.out.println(stack.pop()); //0 出栈顺序:3、2、1、0
  81. System.out.println(stack.pop()); //null
  82. System.out.println(stack.isEmpty()); //true
  83. }
  84. }

除了数组,链表也是实现栈的很好的方式,详情可参考前面一篇“数据结构之链表及其Java实现”中,用单向链表实现栈。

队列:队列是一种基于“先进先出”策略的线性表。插入操作时(入列),每次都在队尾插入一个新元素;删除操作时(出列),每次都在队头插入一个新元素。由此可见,队列的插入操作是在队尾位置进行的,删除操作是在队头位置进行的。

Java提供了一个队列接口Queue<E>,但这个接口不太常用,往往通过其他方式实现队列的操作。链表是实现队列的很好的方式,详情可参考前面一篇“数据结构之链表及其Java实现”中,用双端链表实现队列。除此之外,还可以通过两个栈实现队列。

栈和队列相互实现

两个栈实现队列:两个栈实现队列的原理,可参考前面一篇“剑指offer题目系列二”中“6、用两个栈实现队列”。下面提供两种方式:一种通过Java提供的Stack类来实现队列,另一种通过上面数组实现的栈来实现队列。

通过Java提供的Stack类来实现队列,代码如下:

  1. /*
  2. * 两个栈实现队列
  3. */
  4. import java.util.Stack;
  5.  
  6. public class TwoStackToQueue {
  7.  
  8. private Stack<Object> stack1 = new Stack<Object>(); //存放入列元素
  9. private Stack<Object> stack2 = new Stack<Object>(); //存放出列元素
  10. private int size; //元素数量
  11.  
  12. public TwoStackToQueue(){
  13. size = 0;
  14. }
  15.  
  16. //入列
  17. public void appendTail(Object obj){
  18. stack1.push(obj); //将新入列的元素存放在stack1
  19. size++;
  20. }
  21. //出列
  22. public Object deleteHead(){
  23. if(this.isEmpty()) return null;
  24. if(stack2.empty()){ //如果stack2不为空,则直接出列
  25. while(!stack1.empty()){ //如果stack2为空,先将stack1中的元素出栈,同时进入stack2
  26. stack2.push(stack1.pop());
  27. }
  28. }
  29. size--;
  30. return stack2.pop(); //stack2出栈,完成出列操作
  31. }
  32.  
  33. //判断队列是否为空
  34. public boolean isEmpty(){
  35. return stack1.empty() && stack2.empty();
  36. }
  37.  
  38. public int size(){
  39. return size;
  40. }
  41.  
  42. public static void main(String[] args) {
  43. TwoStackToQueue queue = new TwoStackToQueue();
  44. System.out.println(queue.isEmpty()); //true
  45. System.out.println(queue.deleteHead()); //null
  46. //入列
  47. queue.appendTail(0);
  48. queue.appendTail(1);
  49. queue.appendTail(2);
  50. queue.appendTail(3);
  51. System.out.println(queue.isEmpty()); //false
  52. System.out.println(queue.size()); //4
  53. //出列
  54. System.out.println(queue.deleteHead()); //
  55. System.out.println(queue.deleteHead()); //
  56. System.out.println(queue.deleteHead()); //
  57. queue.appendTail(1);
  58. System.out.println(queue.deleteHead()); //
  59. System.out.println(queue.deleteHead()); //
  60. System.out.println(queue.isEmpty()); //true
  61. System.out.println(queue.size()); //
  62. }
  63. }

通过上面数组实现的栈来实现队列,代码如下:

  1. /*
  2. * 数组实现队列:利用ArrayToStack类中数组实现的栈来间接实现队列。
  3. * 直接用数组实现队列也能,但很繁琐,用链表实现队列更容易
  4. */
  5. public class ArrayToQueue {
  6.  
  7. private ArrayToStack stack1 = new ArrayToStack(); //存放入列元素
  8. private ArrayToStack stack2 = new ArrayToStack(); //存放出列元素
  9. private int size; //元素数量
  10.  
  11. public ArrayToQueue(){
  12. size = 0;
  13. }
  14.  
  15. //入列
  16. public void appendTail(Object obj){
  17. stack1.push(obj); //将新入列的元素存放在stack1
  18. size++;
  19. }
  20. //出列
  21. public Object deleteHead(){
  22. if(this.isEmpty()) return null;
  23. if(stack2.isEmpty()){ //如果stack2不为空,则直接出列
  24. while(!stack1.isEmpty()){ //如果stack2为空,先将stack1中的元素出栈,同时进入stack2
  25. stack2.push(stack1.pop());
  26. }
  27. }
  28. size--;
  29. return stack2.pop(); //stack2出栈,完成出列操作
  30. }
  31.  
  32. //判断队列是否为空
  33. public boolean isEmpty(){
  34. return stack1.isEmpty() && stack2.isEmpty();
  35. }
  36.  
  37. public int size(){
  38. return size;
  39. }
  40.  
  41. public static void main(String[] args) {
  42. TwoStackToQueue queue = new TwoStackToQueue();
  43. System.out.println(queue.isEmpty()); //true
  44. System.out.println(queue.deleteHead()); //null
  45. //入列
  46. queue.appendTail(0);
  47. queue.appendTail(1);
  48. queue.appendTail(2);
  49. queue.appendTail(3);
  50. System.out.println(queue.isEmpty()); //false
  51. System.out.println(queue.size()); //4
  52. //出列
  53. System.out.println(queue.deleteHead()); //
  54. System.out.println(queue.deleteHead()); //
  55. System.out.println(queue.deleteHead()); //
  56. queue.appendTail(1);
  57. System.out.println(queue.deleteHead()); //
  58. System.out.println(queue.deleteHead()); //
  59. System.out.println(queue.isEmpty()); //true
  60. System.out.println(queue.size()); //
  61. }
  62. }

两个队列实现栈:除了两个栈可以实现队列以外,反过来,两个队列也可以实现一个栈。定义两个队列que1、que2,size变量记录元素数量。入栈时,将新入栈的元素放入que1;出栈时,que1、que2交替进行:如果que1不为空,将que1除队尾最后一个元素外的其余元素出列,放入que2中,然后将que1队尾最后一个元素返回(出列),完成出栈操作,此时que1为空;如果que2不为空,将que2除队尾最后一个元素外的其余元素出列,放入que1中,然后将que2队尾最后一个元素返回(出列),完成出栈操作,此时que2为空。

下面代码利用上面“通过Java提供的Stack类来实现队列”的两个队列,来实现栈。

  1. /*
  2. * 两个队列实现栈
  3. */
  4. public class TwoQueueToStack {
  5.  
  6. TwoStackToQueue que1 = new TwoStackToQueue();
  7. TwoStackToQueue que2 = new TwoStackToQueue();
  8. private int size; //元素数量
  9.  
  10. public TwoQueueToStack(){
  11. size = 0;
  12. }
  13. //入栈
  14. public void pushStack(Object obj){
  15. que1.appendTail(obj); //将新入栈的元素放入que1
  16. size++;
  17. }
  18.  
  19. //出栈,出栈时que1、que2交替进行
  20. public Object popStack(){
  21. if(this.isEmptyStack()) return null;
  22. if(!que1.isEmpty()){
  23. while(que1.size() != 1){ //将que1除队尾最后一个元素外的其余元素出列,放入que2中
  24. que2.appendTail(que1.deleteHead());
  25. }
  26. size--;
  27. return que1.deleteHead(); //que1队尾最后一个元素出列,完成出栈操作,此时que1为空
  28. }else{
  29. while(que2.size() != 1){ //将que2除队尾最后一个元素外的其余元素出列,放入que1中
  30. que1.appendTail(que2.deleteHead());
  31. }
  32. size--;
  33. return que2.deleteHead(); //que2队尾最后一个元素出列,完成出栈操作,此时que2为空
  34. }
  35. }
  36.  
  37. public boolean isEmptyStack(){
  38. return que1.size()==0 && que2.size()==0;
  39. }
  40.  
  41. public int size(){
  42. return size;
  43. }
  44.  
  45. public static void main(String[] args) {
  46. TwoQueueToStack stack = new TwoQueueToStack();
  47. System.out.println(stack.isEmptyStack()); //true
  48. System.out.println(stack.popStack()); //null
  49. //入栈
  50. stack.pushStack(0);
  51. stack.pushStack(1);
  52. stack.pushStack(2);
  53. stack.pushStack(3);
  54. System.out.println(stack.isEmptyStack()); //false
  55. System.out.println(stack.size()); //4
  56. //出栈
  57. System.out.println(stack.popStack()); //
  58. System.out.println(stack.popStack()); //
  59. System.out.println(stack.popStack()); //
  60. stack.pushStack(2);
  61. System.out.println(stack.popStack()); //
  62. System.out.println(stack.popStack()); //
  63. System.out.println(stack.isEmptyStack()); //true
  64. System.out.println(stack.size()); //
  65. }
  66. }

转载请注明出处 http://www.cnblogs.com/Y-oung/p/8893829.html

工作、学习、交流或有任何疑问,请联系邮箱:yy1340128046@163.com  微信:yy1340128046

数据结构之栈和队列及其Java实现的更多相关文章

  1. 学习javascript数据结构(一)——栈和队列

    前言 只要你不计较得失,人生还有什么不能想法子克服的. 原文地址:学习javascript数据结构(一)--栈和队列 博主博客地址:Damonare的个人博客 几乎所有的编程语言都原生支持数组类型,因 ...

  2. python数据结构之栈与队列

    python数据结构之栈与队列 用list实现堆栈stack 堆栈:后进先出 如何进?用append 如何出?用pop() >>> >>> stack = [3, ...

  3. 数据结构 1 线性表详解 链表、 栈 、 队列 结合JAVA 详解

    前言 其实在学习数据结构之前,我也是从来都没了解过这门课,但是随着工作的慢慢深入,之前学习的东西实在是不够用,并且太皮毛了.太浅,只是懂得一些浅层的,我知道这个东西怎么用,但是要优化.或者是解析,就不 ...

  4. 算法_栈与队列的Java链表实现

    链表是一个递归的数据结构,它或者为null,或者是指向一个结点的引用,该结点含有一个泛型的元素和指向另一个链表的引用.可以用一个内部类来定义节点的抽象数据类型: private class Node ...

  5. [ACM训练] 算法初级 之 数据结构 之 栈stack+队列queue (基础+进阶+POJ 1338+2442+1442)

    再次面对像栈和队列这样的相当基础的数据结构的学习,应该从多个方面,多维度去学习. 首先,这两个数据结构都是比较常用的,在标准库中都有对应的结构能够直接使用,所以第一个阶段应该是先学习直接来使用,下一个 ...

  6. python数据结构之栈、队列的实现

    这个在官网中list支持,有实现. 补充一下栈,队列的特性: 1.栈(stacks)是一种只能通过访问其一端来实现数据存储与检索的线性数据结构,具有后进先出(last in first out,LIF ...

  7. PHP数据结构:栈、队列、堆、固定数组

    数据结构:栈 队列: 堆: 固定尺寸的数组:

  8. 算法与数据结构(二) 栈与队列的线性和链式表示(Swift版)

    数据结构中的栈与队列还是经常使用的,栈与队列其实就是线性表的一种应用.因为线性队列分为顺序存储和链式存储,所以栈可以分为链栈和顺序栈,队列也可分为顺序队列和链队列.本篇博客其实就是<数据结构之线 ...

  9. python——python数据结构之栈、队列的实现

    这个在官网中list支持,有实现. 补充一下栈,队列的特性: 1.栈(stacks)是一种只能通过访问其一端来实现数据存储与检索的线性数据结构,具有后进先出(last in first out,LIF ...

随机推荐

  1. linux shell每天一阅 -- 安装nginx以及apache

    当然这个博客原代码是转载大神的... 自动安装Nginx脚本,采用case方式,选择方式,也可以根据实际需求改成自己想要的脚本mynginx.sh #!/bin/sh ###nginx install ...

  2. DOS下常用命令

    0,想进入某个驱动器,直接输入盘符即可.如:“d:”1,CD--进入指定目录 2,cls--清除显示器屏幕上的内容,使DOS提示符到屏幕左上角. 3,time--显示和设置DOS的系统时间 4,dir ...

  3. python 切片&迭代

    Python提供了切片(Slice)操作符L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']取前3个元素>>> L[0:3]['Mich ...

  4. BIND简易教程(1):安装及基本配置

    首先,为什么说是简易教程呢?因为BIND的功能实在太多,全写出来的话要连载好久,我觉得我没有那么多精力去写:而我了解的仅仅是有限的一点点,不敢造次.百度上的文章也是一抓一大把呐!所以,教点基本使用方法 ...

  5. UVa 11584 - Partitioning by Palindromes(线性DP + 预处理)

    链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

  6. Cocos2dx打包apk时变更NDK引发问题及解决

    现在官方的Cocos Studio已经支持打包apk文件,写该随笔的时候还没试过官方的打包功能,所以就按自己的学习顺序先把打包的心得写下. 问题及最终解决方案: 其中耗时最长的问题就是ndk-r10改 ...

  7. 2018 Multi-University Training Contest 4 Problem L. Graph Theory Homework 【YY】

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6343 Problem L. Graph Theory Homework Time Limit: 2000 ...

  8. ASP.NET SingalR + MongoDB 实现简单聊天室(一):搭建基本框架

    ASP.NET SingalR不多介绍.让我介绍不如看官网,我这里就是直接上源代码,当然代码还是写的比较简单的,考虑的也少,希望各位技友多多提意见. 先简单介绍聊天室功能: 用户加入聊天室,自动给用户 ...

  9. iOS 帧动画之翻转和旋转动画

    记录两个比较简单的动画,一个是翻转的动画,一个是旋转的动画. 旋转动画: 1 [UIView animateWithDuration:3 animations:^{ if (formView) { f ...

  10. 【转】优秀的Java程序员必须了解GC的工作原理

    一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率 ,才能提高整个应 ...