数据结构与算法分析(Java语言描述)—— 栈

1. 栈的实现

使用 deque 接口来实现栈,deque 接口被 LinkedList 所实现,是一个双向链表,允许在前端后后端插入和删除的操作。
ArrayList 和 LinkedList 能在 99% 的情况下是栈实现的最好方式,因为栈操作是常数时间操作,所以,除非在独特环境下,否则是不会有明显改进的。

import java.util.Deque;
import java.util.LinkedList;

public class Stack<T> {
    Deque<T> stack ;
    Stack(){
        stack =  new LinkedList<>();
    }

    public void push(T t){
      stack.push(t);
    }
    public T pop(){
       return stack.pop();
    }
    public T top(){
        return stack.peek();
    }
    public int size(){
        return stack.size();
    }
    public boolean isEmpty(){
        return stack.isEmpty();
    }
    public String print(){
        return stack.toString();
    }

}

2. 栈的应用实例

2.1 平衡符号

序列 [ ( ) ] 是合法的,但是 [ ( ] 是不合法的

算法描述

做一个空栈,读入字符直到文件结尾。如果字符是一个开放符号,则将其推入到栈。如果字符是一个封闭符号,则当栈空时报错。否则,将栈元素弹出。如果弹出的符号不是对应的开放符号,则报错。在文件末尾,如果栈非空则报错。

它是线性的,事实上我们只进行了一趟检验。

public static void testbanlanceCharacter(){
  char[] characters = {'{','3','(',')',']','}'};

  Stack<Character> stack = new Stack<>();

  for (int i = 0; i < characters.length; i++){
      if(characters[i]=='{' || characters[i]=='[' || characters[i]=='(')
          stack.push(characters[i]);
      if(characters[i]=='}' || characters[i]==']' || characters[i]==')')
          if(stack.isEmpty()){
              System.out.println(characters[i]+"无匹配项,乃是孤狼");
              return;
          }
          if(characters[i]=='}' && stack.pop()!='{')
              System.out.println(characters[i]+" 无匹配项,乃是孤狼"+ i);
          if(characters[i]==']' && stack.pop()!='[')
              System.out.println(characters[i]+" 无匹配项,乃是孤狼"+ i);
          if(characters[i]==')' && stack.pop()!='(')
              System.out.println(characters[i] +" 无匹配项,乃是孤狼"+ i);

          else if(i == characters.length-1 && !stack.isEmpty()){
              System.out.println("栈非空 报错");
              stack.print();
          }
   }
}

] 无匹配项,乃是孤狼4
} 无匹配项,乃是孤狼

2.2 后缀表达式

4.99*1.06+5.99+6.99*1.06=? -> 4.99 1.16 * 5.99 + 6.99 1.06 * +
这个记法叫作 后缀逆波兰 记法。计算这个问题最容易的方法是使用一个栈。当见到一个数时就把它推入栈中,在遇到一个运算符时,该运算符就作用于从该栈弹出的两个数上,再将所得结果推入栈中。

算法描述

例如后缀表达式 6 5 2 3 + 8 * + 3 + * 计算如下:前四个字符放入栈中,此时栈变成了 3 2 5 6,下面读到了一个 + ,所以 3 和 2 从栈中弹出并且它们的和 5 被压入栈中。 此时变成: 5 5 6 ,接着 8 进栈,此时变成: 8 5 5 6,现在见到一个 * 号,因此 8 和 5 弹出并且 5* 8 = 40 进栈, 此时变成:40 5 6,接着又见到一个 + 号,因此 40 和 5 被弹出并且 5 + 40 = 45 进栈,现在变成: 45 6,现在将 3 压入,此时变成:3 45 6 ,然后 + 使得 3 和 45 相加变成 48 压入,此时变成:48 6 ,最后,遇到一个 * 号,从栈中弹出 48 和 6;将结果 6 * 48 = 288 压入栈中

计算一个后缀变大时花费的时间是 O(N),因为对输入中的每个元素的处理都是由一些栈操作组成,从而花费常数时间。注意,当一个表达式以后缀记号给出时,没有必要知道任何优先的原则,这是一个明显的优点。

public class PostfixNotation {
    private static final char ADD = '+';
    private static final char SUB = '-';
    private static final char MULTIPLY = '*';
    private static final char DIVIDE = '/';

    Stack<Integer> stack;

    PostfixNotation(){
        stack = new Stack<>();
    }

    public int calculatePostfixNotation(){
        char[]arr = {'6','5','2','3','+','8','*','+','3','+','*'};

        for (char ch : arr) {
            //判断是数字的情况
            if(Character.isDigit(ch)){
                //先将字符转换为字符串,再转化为 int
                String t = String.valueOf(ch);
                int i = Integer.parseInt(t);
                stack.push(i);
            }

            //判断是符号的情况
            else {
                int right = stack.pop();
                int left = stack.pop();
                int temp = 0;
                switch (ch){
                    case ADD :
                        temp = left + right;
                        stack.push(temp);
                        break;
                    case SUB :
                        temp = left - right;
                        stack.push(temp);
                        break;
                    case MULTIPLY :
                        temp = left * right;
                        stack.push(temp);
                        break;
                    case DIVIDE :
                        temp = left / right;
                        stack.push(temp);
                        break;
                    default:
                        System.out.println("运算符号不合法");
                        break;
                }
            }
        }
        return stack.pop();
    }
}
public class Test {
    public static void main(String[] args) {
        PostfixNotation p = new PostfixNotation();
        int result = p.calculatePostfixNotation();
        System.out.println(result);//288
    }
}

2.3 中缀到后缀的转换

算法:

中缀表达式转后缀表达式的方法:
1.遇到操作数:直接输出(添加到后缀表达式中)
2.栈为空时,遇到运算符,直接入栈
3.遇到左括号:将其入栈
4.遇到右括号:执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出。
5.遇到其他运算符:加减乘除:弹出所有优先级大于或者等于该运算符的栈顶元素【栈内的栈顶运算符>=遇到的运算符,就弹出】,然后将该运算符入栈
6.最终将栈中的元素依次出栈,输出。

例如:【5+4*6/2+3+(4*5)/5】 =24

转化之后的后缀表达式:【5 4 6 * 2 / + 3 + 4 5 *5 / +】

import java.util.HashMap;
import java.util.Map;

public class Postfix {

    //用于展示后缀表达式的输出流
    private StringBuilder output = new StringBuilder();
    //实例化栈
    private Stack<Character> stack = new Stack<>();
    //用于记录操作符的优先级的 map
    private  final Map<Character,Integer> priority = new HashMap<>();
     {
        priority.put('+' , 1);
        priority.put('-' , 1);
        priority.put('*' , 2);
        priority.put('/' , 2);
        priority.put('(' , 0);//(优先级最高,这里做特殊处理,下边会有相应处理
    }

    /**
     * 中缀表达式转为后缀表达式
     */
    public  void toinFix(String arr){
        //去除两端空格
        char [] c = arr.trim().toCharArray();
        //接受变量的临时变量
        char temp;

        for (char ch : c) {
            //如果遍历到的元素是字母
            if(Character.isLetter(ch))
                output.append(ch);
            //如果遍历到的元素是字符
            else{
                if(stack.isEmpty() || ch == '(')
                    stack.push(ch);
                else if(ch == ')')
                    while ((temp=stack.pop()) != '(')
                        output.append(temp);
                else{
                    //当前操作符优先级大于栈顶元素则直接压栈
                    //当前操作符优先级小于等于栈顶元素则将栈顶元素弹出,压入当前操作符
                    if(priority.get(ch) > priority.get(stack.top())){
                        stack.push(ch);
                    }
                    else if(priority.get(ch) <= priority.get(stack.top())) {
                        while (!stack.isEmpty() && (priority.get(ch) <= priority.get(stack.top())))
                            output.append(stack.pop());
                        stack.push(ch);
                    }

                }
            }
        }
        while (!stack.isEmpty()){
            output.append(stack.pop());
        }
        System.out.println(output.toString());
    }
}
public class Test {
    public static void main(String[] args) {
        //将中缀表达式转为后缀表达式
        Postfix po = new Postfix();
        po.toinFix("a+b*c-(d*e+f)*g");
        //abc*+de*f+g*-
    }

}

2.4 方法调用

当存在方法调用的时候,需要存储所有重要的信息,诸如存储器的值(对应变量的名字)和返回地址(它可从程序计数器中得到,一般情况是在一个寄存器中)等,都要以抽象的形式存在于“一张纸上”,并被置于一个堆的顶部。然后控制转移到新方法,该方法自由地用它的一些值来代替这些寄存器。如果它又进行其他的方法调用,那么它也遵循相同的过程,当该方法要返回时,它查看堆顶的那张“纸”并复原所有的寄存器,然后进行返回转移。
显然,所有全部工作均可由一个栈来完成,而这正是在实现递归的每一种程序设计语言实际发生的事实。所存储的信息称为“活动记录”或者叫做“栈帧” 。在实际计算机中的栈常常是从内存分区的高端向下生长,而在许多非 java 系统中是不检测溢出的。栈空间用尽溢出是非常致命的错误。
在不进行栈溢出检测的语言和系统中,程序将会崩溃而没有明显的说明,在 java 中会抛出一个异常。

public static <T> void printList(Iterator <T> itr){
    if( !itr.hasNext() )
        return;
    System.out.println( itr.next );
    printList( itr );
}

分析:
1.不幸的是,如果这个集合有20000个元素需要打印,那么就要有嵌套调用的20000个活动记录的栈。一般这些活动记录由于它们包含了全部信息而特别巨大,因此可能会发生栈溢出的情况。
2.这个程序称为尾递归使用不当的例子.尾递归涉及在最后一行的递归调用。尾递归可以通过将代码放到 while 循环中并用每个方法参数的一次赋值代替递归调用而被手工消除。它模拟了递归调用,因为他什么也不用存储。

public static <T> void printList(Iterator <T> itr){
    while(true){
        if( itr.hasNext() ){
            return;
        System.out.println(itr.next());
        }
    }
}

递归总能被彻底消除,只是指出,非递归程序一般来说比等价的递归程序要快,但是速度优势的代价是使得程序的清晰性受到了影响

3. 栈的算法练习

3.1 用一个栈实现另一个栈的排序

【题目】

一个栈元素的类型为整型,现在想将该栈从顶到底按从大到小的顺序排序,只许申请一个栈,可以申请新的变量,但不能申请额

外的数据结构,如何完成排序?

【解答】

将要排序的栈记为stack,申请的辅助栈记为help,在stack上执行pop操作,弹出的元素记为cur

1.如果cur小于或者等于help栈顶元素,则将cur直接压入help

2.如果cur大于help栈顶元素,则将help的元素逐一弹出,逐一压入stack,直到cur小于或者等于help栈顶元素,再将cur压入help。

一直执行以上操作,直到stack栈中的全部元素都压入到help中,最后将help中的所有元素逐一压入stack。

public class SortStack {  
    public static void sortStackByStack(Stack<Integer> stack){  
        Stack<Integer> help = new Stack<>();  
        while(!stack.isEmpty()){  
            int cur = stack.pop();  
            while(!help.isEmpty() && cur <= help.peek()){  
                stack.push(help.pop());  
            }  
            help.push(cur);  
        }  
        while(!help.isEmpty())  
            stack.push(help.pop());  
    }  
}  

猜你喜欢

转载自blog.csdn.net/baidu_37181928/article/details/80766655