栈的java实现和栈的应用

时间:2020-12-31 23:21:08

[例子和习题出自数据结构(严蔚敏版), 本人使用java进行实现.  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. ]

栈的实现

栈是一种先进后出的数据结构, 首先定义了栈需要实现的接口:

  1. public interface MyStack<T> {
  2. /**
  3. * 判断栈是否为空
  4. */
  5. boolean isEmpty();
  6. /**
  7. * 清空栈
  8. */
  9. void clear();
  10. /**
  11. * 栈的长度
  12. */
  13. int length();
  14. /**
  15. * 数据入栈
  16. */
  17. boolean push(T data);
  18. /**
  19. * 数据出栈
  20. */
  21. T pop();
  22. }

栈的数组实现, 底层使用数组:

  1. public class MyArrayStack<T> implements MyStack<T> {
  2. ];
  3. ;
  4. @Override
  5. public boolean isEmpty() {
  6. ;
  7. }
  8. @Override
  9. public void clear() {
  10. // 将数组中的数据置为null, 方便GC进行回收
  11. ; i < size; i++) {
  12. objs[size] = null;
  13. }
  14. ;
  15. }
  16. @Override
  17. public int length() {
  18. return size;
  19. }
  20. @Override
  21. public boolean push(T data) {
  22. // 判断是否需要进行数组扩容
  23. if (size >= objs.length) {
  24. resize();
  25. }
  26. objs[size++] = data;
  27. return true;
  28. }
  29. /**
  30. * 数组扩容
  31. */
  32. private void resize() {
  33. / 2 + 1];
  34. ; i < size; i++) {
  35. temp[i] = objs[i];
  36. objs[i] = null;
  37. }
  38. objs = temp;
  39. }
  40. @SuppressWarnings("unchecked")
  41. @Override
  42. public T pop() {
  43. ) {
  44. return null;
  45. }
  46. return (T) objs[--size];
  47. }
  48. @Override
  49. public String toString() {
  50. StringBuilder sb = new StringBuilder();
  51. sb.append("MyArrayStack: [");
  52. ; i < size; i++) {
  53. sb.append(objs[i].toString());
  54. ) {
  55. sb.append(", ");
  56. }
  57. }
  58. sb.append("]");
  59. return sb.toString();
  60. }
  61. }

栈的链表实现, 底层使用链表:

  1. public class MyLinkedStack<T> implements MyStack<T> {
  2. /**
  3. * 栈顶指针
  4. */
  5. private Node top;
  6. /**
  7. * 栈的长度
  8. */
  9. private int size;
  10. public MyLinkedStack() {
  11. top = null;
  12. ;
  13. }
  14. @Override
  15. public boolean isEmpty() {
  16. ;
  17. }
  18. @Override
  19. public void clear() {
  20. top = null;
  21. ;
  22. }
  23. @Override
  24. public int length() {
  25. return size;
  26. }
  27. @Override
  28. public boolean push(T data) {
  29. Node node = new Node();
  30. node.data = data;
  31. node.pre = top;
  32. // 改变栈顶指针
  33. top = node;
  34. size++;
  35. return true;
  36. }
  37. @Override
  38. public T pop() {
  39. if (top != null) {
  40. Node node = top;
  41. // 改变栈顶指针
  42. top = top.pre;
  43. size--;
  44. return node.data;
  45. }
  46. return null;
  47. }
  48. /**
  49. * 将数据封装成结点
  50. */
  51. private final class Node {
  52. private Node pre;
  53. private T data;
  54. }
  55. }

两种实现的比较, 主要比较数据入栈和出栈的速度:

  1. @Test
  2. public void testSpeed() {
  3. MyStack<Person> stack = new MyArrayStack<Person>();
  4. ;
  5. long start = System.currentTimeMillis();
  6. ; i < num; i++) {
  7. ));
  8. }
  9. long temp = System.currentTimeMillis();
  10. System.out.println("push time: " + (temp - start));
  11. while (stack.pop() != null)
  12. ;
  13. System.out.println("pop time: " + (System.currentTimeMillis() - temp));
  14. }

MyArrayStack中入栈和出栈10,000,000条数据的时间:

push time: 936
pop time: 47

将MyArrayStack改为MyLinkedStack后入栈和出栈的时间:

push time: 936

pop time: 126

可见两者的入栈速度差不多, 出栈速度MyArrayStack则有明显的优势.

为什么测试结果是这样的? 可能有些朋友的想法是数组实现的栈应该具有更快的遍历速度, 但增删速度应该比不上链表实现的栈才对. 但是栈中数据的增删具有特殊性: 只在栈顶入栈和出栈. 也就是说数组实现的栈在增加和删除元素时并不需要移动大量的元素, 只是在数组扩容时需要进行复制. 而链表实现的栈入栈和出栈时都需要将数据包装成Node或者从Node中取出数据, 还需要维护栈顶指针和前驱指针.

栈的应用举例

1. 将10进制正整数num转换为n进制

  1. private String conversion(int num, int n) {
  2. MyStack<Integer> myStack = new MyArrayStack<Integer>();
  3. Integer result = num;
  4. while (true) {
  5. // 将余数入栈
  6. myStack.push(result % n);
  7. result = result / n;
  8. ) {
  9. break;
  10. }
  11. }
  12. StringBuilder sb = new StringBuilder();
  13. // 按出栈的顺序倒序排列即可
  14. while ((result = myStack.pop()) != null) {
  15. sb.append(result);
  16. }
  17. return sb.toString();
  18. }

2. 检验符号是否匹配. '['和']', '('和')'成对出现时字符串合法. 例如"[][]()", "[[([]([])()[])]]"是合法的; "([(])", "[())"是不合法的.

遍历字符串的每一个char, 将char与栈顶元素比较. 如果char和栈顶元素配对, 则char不入栈, 否则将char入栈. 当遍历完成时栈为空说明字符串是合法的.

  1. public boolean isMatch(String str) {
  2. MyStack<Character> myStack = new MyArrayStack<Character>();
  3. char[] arr = str.toCharArray();
  4. for (char c : arr) {
  5. Character temp = myStack.pop();
  6. // 栈为空时只将c入栈
  7. if (temp == null) {
  8. myStack.push(c);
  9. }
  10. // 配对时c不入栈
  11. else if (temp == '[' && c == ']') {
  12. }
  13. // 配对时c不入栈
  14. else if (temp == '(' && c == ')') {
  15. }
  16. // 不配对时c入栈
  17. else {
  18. myStack.push(temp);
  19. myStack.push(c);
  20. }
  21. }
  22. return myStack.isEmpty();
  23. }

3. 行编辑: 输入行中字符'#'表示退格, '@'表示之前的输入全都无效.

使用栈保存输入的字符, 如果遇到'#'就将栈顶出栈, 如果遇到@就清空栈. 输入完成时将栈中所有字符出栈后反转就是输入的结果:

  1. private String lineEdit(String input) {
  2. MyStack<Character> myStack = new MyArrayStack<Character>();
  3. char[] arr = input.toCharArray();
  4. for (char c : arr) {
  5. if (c == '#') {
  6. myStack.pop();
  7. } else if (c == '@') {
  8. myStack.clear();
  9. } else {
  10. myStack.push(c);
  11. }
  12. }
  13. StringBuilder sb = new StringBuilder();
  14. Character temp = null;
  15. while ((temp = myStack.pop()) != null) {
  16. sb.append(temp);
  17. }
  18. // 反转字符串
  19. sb.reverse();
  20. return sb.toString();
  21. }