1.. 栈的特点:
  • 栈也是一种线性结构;
  • 相比数组,栈所对应的操作是数组的子集;
  • 栈只能从一端添加元素,也只能从这一端取出元素,这一端通常称之为"栈顶";
  • 向栈中添加元素的过程,称之为"入栈",从栈中取出元素的过程称之为"出栈";
  • 栈的形象化描述如下图:
  • "入栈"的顺序若为1-2-3-4,那么出栈的顺序只能为4-3-2-1,即,栈是一种"后进先出"(Last In First Out)的数据结构;
  • 从用户的角度,只能看到栈顶元素,其它元素对用户是不可见的;
  • 在计算机世界里,"栈"有着不可思意的作用;
2.. 简单举例"栈"的应用
  • 应用程序中的"撤销"(Undo)操作的底层原理就是通过"栈"来实现的;
  • 程序调用的系统栈,通过下图简单示例:
3.. 栈的实现
  • 任务目标如下:
    1. Stack<E>
    2. ·void push(E) //入栈
    3. ·E pop() // 出栈
    4. ·E peek() // 查看栈顶元素
    5. ·int getSize() // 查看栈中共有多少元素
    6. ·boolean isEmpty() // 查看栈是否为空
  • 需要提一下,从用户的角度来看,只要实现上述操作就好,具体底层实现,用户并不关心,实际上,底层确实有多种实现方式。
  • 我们准备在之前实现的动态数组基础上,来实现"栈"这种数据结构。
  • 先定义一个接口Interface,如下:
    1. public interface Stack<E> { // 支持泛型
    2. int getSize();
    3.  
    4. boolean isEmpty();
    5.  
    6. void push(E e);
    7.  
    8. E pop();
    9.  
    10. E peek();
    11. }
  • 然后实现一个ArrayStack类,如下:
    1. public class ArrayStack<E> implements Stack<E> {
    2. Array<E> array;
    3.  
    4. //构造函数
    5. public ArrayStack(int capacity) {
    6. array = new Array<>(capacity);
    7. }
    8.  
    9. //无参数构造函数
    10. public ArrayStack() {
    11. array = new Array<>();
    12. }
    13.  
    14. //实现getSize方法
    15. @Override
    16. public int getSize() {
    17. return array.getSize();
    18. }
    19.  
    20. //实现isEmpty方法
    21. @Override
    22. public boolean isEmpty() {
    23. return array.isEmpty();
    24. }
    25.  
    26. //实现getCapacity方法
    27. public int getCapacity() {
    28. return array.getCapacity();
    29. }
    30.  
    31. //实现push方法
    32. @Override
    33. public void push(E e) {
    34. array.addLast(e);
    35. }
    36.  
    37. //实现pop方法
    38. @Override
    39. public E pop() {
    40. return array.removeLast();
    41. }
    42.  
    43. //实现peek方法
    44. public E peek() {
    45. return array.getLast();
    46. }
    47.  
    48. //方便打印测试
    49. @Override
    50. public String toString() {
    51. StringBuilder res = new StringBuilder();
    52. res.append("Stack: ");
    53. res.append('[');
    54. for (int i = 0; i < array.getSize(); i++) {
    55. res.append(array.get(i));
    56. if (i != array.getSize() - 1) {
    57. res.append(", ");
    58. }
    59. }
    60. res.append("] top");
    61. return res.toString();
    62. }
    63. }
  • Array类的业务逻辑如下:
    1. public class Array<E> {
    2.  
    3. private E[] data; //设置为private,不希望用户从外部直接获取这些信息,防止用户篡改数据
    4. private int size;
    5.  
    6. //构造函数,传入数组的容量capacity构造Array
    7. public Array(int capacity) {
    8. data = (E[]) new Object[capacity];
    9. size = 0;
    10. }
    11.  
    12. //无参数构造函数,默认数组容量capacity=10
    13. public Array() {
    14. this(10); //这里的capacity是IDE自动添加的提示信息,实际不存在
    15. }
    16.  
    17. //获取数组中的元素个数
    18. public int getSize() {
    19. return size;
    20. }
    21.  
    22. //获取数组的容量
    23. public int getCapacity() {
    24. return data.length;
    25. }
    26.  
    27. //判断数组是否为空
    28. public boolean isEmpty() {
    29. return size == 0;
    30. }
    31.  
    32. //向数组末尾添加一个新元素e
    33. public void addLast(E e) {
    34. add(size, e);
    35. }
    36.  
    37. //向数组开头添加一个新元素e
    38. public void addFirst(E e) {
    39. add(0, e);
    40. }
    41.  
    42. //在index位置插入一个新元素e
    43. public void add(int index, E e) {
    44.  
    45. if (index < 0 || index > size) {
    46. throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size");
    47. }
    48. if (size == data.length) {
    49. resize(2 * size); //扩大为原容量的2倍
    50. }
    51. for (int i = size - 1; i >= index; i--) {
    52. data[i + 1] = data[i];
    53. }
    54. data[index] = e;
    55. size++;
    56. }
    57.  
    58. //获取index位置的元素
    59. public E get(int index) {
    60. if (index < 0 || index >= size) {
    61. throw new IllegalArgumentException("Get failed. Index is illegal.");
    62. }
    63. return data[index];
    64. }
    65.  
    66. //获取最后一个元素
    67. public E getLast() {
    68. return get(size - 1);
    69. }
    70.  
    71. //获取开头的元素
    72. public E getFirst() {
    73. return get(0);
    74. }
    75.  
    76. //修改index位置的元素为e
    77. public void set(int index, E e) {
    78. if (index < 0 || index >= size) {
    79. throw new IllegalArgumentException("Set failed. Index is illegal.");
    80. }
    81. data[index] = e;
    82. }
    83.  
    84. //查找数组中是否存在元素e
    85. public boolean contains(E e) {
    86. for (int i = 0; i < size; i++) {
    87. if (data[i].equals(e)) {
    88. return true;
    89. }
    90. }
    91. return false;
    92. }
    93.  
    94. //查看数组中元素e的索引,若找不到元素e,返回-1
    95. public int find(E e) {
    96. for (int i = 0; i < size; i++) {
    97. if (data[i].equals(e)) {
    98. return i;
    99. }
    100. }
    101. return -1;
    102. }
    103.  
    104. //删除掉index位置的元素,并且返回删除的元素
    105. public E remove(int index) {
    106.  
    107. if (index < 0 || index >= size) {
    108. throw new IllegalArgumentException("Remove failed. Index is illegal.");
    109. }
    110.  
    111. E ret = data[index];
    112.  
    113. for (int i = index + 1; i < size; i++) {
    114. data[i - 1] = data[i];
    115. }
    116. size--; //data[size]会指向一个类对象,这部分空间不会被释放loitering objects
    117. data[size] = null;
    118.  
    119. if (size == data.length / 4 && data.length / 2 != 0) {
    120. resize(data.length / 2); //被利用的空间等于总空间的一半时,将数组容量减少一半
    121. }
    122. return ret;
    123. }
    124.  
    125. //删除掉数组开头的元素,并返回删除的元素
    126. public E removeFirst() {
    127. return remove(0);
    128. }
    129.  
    130. //删除掉数组末尾的元素,并返回删除的元素
    131. public E removeLast() {
    132. return remove(size - 1);
    133. }
    134.  
    135. //如果数组中有元素e,那么将其删除,否则什么也不做
    136. public void removeElement(E e) {
    137. int index = find(e);
    138. if (index != -1) {
    139. remove(index);
    140. }
    141. }
    142.  
    143. @Override
    144. public String toString() { //覆盖父类的toString方法
    145.  
    146. StringBuilder res = new StringBuilder();
    147. res.append(String.format("Array: size=%d, capacity=%d\n", size, data.length));
    148. res.append('[');
    149. for (int i = 0; i < size; i++) {
    150. res.append(data[i]);
    151. if (i != size - 1) {
    152. res.append(", ");
    153. }
    154. }
    155. res.append(']');
    156. return res.toString();
    157. }
    158.  
    159. private void resize(int newCapacity) {
    160. E[] newData = (E[]) new Object[newCapacity];
    161. for (int i = 0; i < size; i++) {
    162. newData[i] = data[i];
    163. }
    164. data = newData;
    165. }
    166. }

4.. 对我们实现的栈进行测试:

    1. public class Main {
    2.  
    3. public static void main(String[] args) {
    4.  
    5. ArrayStack<Integer> stack = new ArrayStack<>();
    6.  
    7. //测试入栈push
    8. for (int i = 0; i < 5; i++) {
    9. stack.push(i);
    10. System.out.println(stack);
    11. }
    12.  
    13. //测试出栈
    14. stack.pop();
    15. System.out.println(stack);
    16. }
    17. }
  • 输出结果如下:
    1. Stack: [0] top
    2. Stack: [0, 1] top
    3. Stack: [0, 1, 2] top
    4. Stack: [0, 1, 2, 3] top
    5. Stack: [0, 1, 2, 3, 4] top
    6. Stack: [0, 1, 2, 3] top

5.. 栈的时间复杂度分析

    1. Stack<E>
    2. ·void push(E) O(1) 均摊
    3. ·E pop() O(1) 均摊
    4. ·E peek() O(1)
    5. ·int getSize() O(1)
    6. ·boolean isEmpty() O(1)
6.. 栈的另外一个应用——括号匹配
  • 业务逻辑如下:
    1. import java.util.Stack;
    2.  
    3. class Solution {
    4. public boolean isValid(String s) {
    5. Stack<Character> stack = new Stack<>();
    6. for (int i = 0; i < s.length(); i++) {
    7. char c = s.charAt(i);
    8. if (c == '(' || c == '[' || c == '{') {
    9. stack.push(c);
    10. } else {
    11. if (stack.isEmpty()) {
    12. return false;
    13. }
    14. char topChar = stack.pop();
    15. if (topChar == '(' && c != ')') {
    16. return false;
    17. }
    18. if (topChar == '[' && c != ']') {
    19. return false;
    20. }
    21. if (topChar == '{' && c != '}') {
    22. return false;
    23. }
    24. }
    25. }
    26. return stack.isEmpty(); //这里很巧妙
    27. }
    28.  
    29. //测试
    30. public static void main(String[] args){
    31. System.out.println((new Solution()).isValid("()"));
    32. System.out.println((new Solution()).isValid("()[]}{"));
    33. System.out.println((new Solution()).isValid("({[]})"));
    34. System.out.println((new Solution()).isValid("({)}[]"));
    35.  
    36. }
    37. }

第二十三篇 玩转数据结构——栈(Stack)的更多相关文章

  1. 第二十七篇 玩转数据结构——集合(Set)与映射(Map)

          1.. 集合的应用 集合可以用来去重 集合可以用于进行客户的统计 集合可以用于文本词汇量的统计   2.. 集合的实现 定义集合的接口 Set<E> ·void add(E) ...

  2. 第三十三篇 玩转数据结构——红黑树(Read Black Tree)

    1.. 图解2-3树维持绝对平衡的原理: 2.. 红黑树与2-3树是等价的 3.. 红黑树的特点 简要概括如下: 所有节点非黑即红:根节点为黑:NULL节点为黑:红节点孩子为黑:黑平衡 4.. 实现红 ...

  3. Python开发【第二十三篇】:持续更新中...

    Python开发[第二十三篇]:持续更新中...

  4. 第二十五篇 玩转数据结构——链表(Linked List)

          1.. 链表的重要性 我们之前实现的动态数组.栈.队列,底层都是依托静态数组,靠resize来解决固定容量的问题,而"链表"则是一种真正的动态数据结构,不需要处理固定容 ...

  5. 第二十六篇 玩转数据结构——二分搜索树(Binary Search Tree)

          1.. 二叉树 跟链表一样,二叉树也是一种动态数据结构,即,不需要在创建时指定大小. 跟链表不同的是,二叉树中的每个节点,除了要存放元素e,它还有两个指向其它节点的引用,分别用Node l ...

  6. 第二十八篇 玩转数据结构——堆(Heap)和有优先队列(Priority Queue)

          1.. 优先队列(Priority Queue) 优先队列与普通队列的区别:普通队列遵循先进先出的原则:优先队列的出队顺序与入队顺序无关,与优先级相关. 优先队列可以使用队列的接口,只是在 ...

  7. 第二十四篇 玩转数据结构——队列(Queue)

          1.. 队列基础 队列也是一种线性结构: 相比数组,队列所对应的操作数是队列的子集: 队列只允许从一端(队尾)添加元素,从另一端(队首)取出元素: 队列的形象化描述如下图: 队列是一种先进 ...

  8. 第二十九篇 玩转数据结构——线段树(Segment Tree)

          1.. 线段树引入 线段树也称为区间树 为什么要使用线段树:对于某些问题,我们只关心区间(线段) 经典的线段树问题:区间染色,有一面长度为n的墙,每次选择一段墙进行染色(染色允许覆盖),问 ...

  9. C# 数据结构 栈 Stack

    栈和队列是非常重要的两种数据结构,栈和队列也是线性结构,线性表.栈和队列这三种数据结构的数据元素和元素的逻辑关系也相同 差别在于:线性表的操作不受限制,栈和队列操作受限制(遵循一定的原则),因此栈和队 ...

随机推荐

  1. jQuery---突出展示案例

    突出展示案例 <!DOCTYPE html> <html> <head lang="en"> <meta charset="UT ...

  2. java - GC垃圾收集器详解(一)

    概要 该图标记了在jdk体系中所使用到的垃圾收集器及对应的关系图.图片上方为年轻代的垃圾收集器而图片下方是老年代的垃圾收集器.当选择某一个区域的垃圾收集器时会自动选择另外一个区域的另一个垃圾收集器.例 ...

  3. 虚拟化技术xen的简介和安装

    虚拟化技术的分类: 1,模拟:Emulation ​ Qemu,PearPC,Bochs 2,完全虚拟化:Full Virtualization,Native Virtualization ​ HVM ...

  4. maven-设置aliyun远程库

    maven默认的远程库下载起来非常慢,习惯改成aliyun的库. 一.修改maven配置 打开maven配置文件setting.xml,改mirror <mirrors> <mirr ...

  5. python xlrd 模块(获取Excel表中数据)

    python xlrd 模块(获取Excel表中数据) 一.安装xlrd模块   到python官网下载http://pypi.python.org/pypi/xlrd模块安装,前提是已经安装了pyt ...

  6. caffe+win10+git使用sh文件

    在windows下是否可以执行sh文件呢,搜了一下,可以安装了git就可以执行,当然这不是唯一答案. 然后联想到caffe下有一些.sh文件可以尝试,就用create_mnist.sh尝试把. cre ...

  7. Ionic 使用 NFC

    Ionic 使用 NFC 哎哟喂,因为项目需要使用 Ionic 调用手机 NFC 功能,踩了好多坑,真的是,不过终于不负众望拿到了id.现在就记录一下我的步骤和踩过的坑! 步骤 我装的Ionic可能是 ...

  8. Selenium3+python自动化009-iframe定位

    iframe 一.frame:HTML页面中的一种框架,主要作用是在当前页面中指定区域显示另一页面元素: 二.操作Frame中的页面元素 定位元素: 1.id定位driver.switch_to.fr ...

  9. PyQt5-QDateEdit的简单使用

    使用PyQt5开发图形界面,里面使用日期框,这里把这个QDateEdit组件命名为:beginDate from PyQt5.QtCore import QDate 1.初始化赋值,不设置则默认为20 ...

  10. hzq84621巨佬的语录

    摘自诸中培训讲图论时: 1.光图论考不出什么东西,一般作为DP的附庸出现. 2.如果不是骗骗不承认SPFA的外国人,一般能用dij就用dij. 3.那个东西(指bellman-ford)除了判负环没什 ...