java数据结构-栈.md

2019-12-13  本文已影响0人  Vinson武

栈的定义和数据类型

栈定义

又称堆栈,一种运算受限的线性表,仅允许在表的一端进行插入和删除运算。对栈进行运算的一端称为栈顶,栈顶的第一个元素称为栈顶元素,相对地另一端称为栈底。

栈的基本操作

public E push(E item) {
        addElement(item);
        return item;
    }
public synchronized E pop() {
        E  obj;
        int len = size();

        obj = peek();  //调用peek
        removeElementAt(len - 1); //下标是从底往上算的
        return obj;
    }
public synchronized E peek() {
        int     len = size();

        if (len == 0) //空会抛异常
            throw new EmptyStackException();
        return elementAt(len - 1);
    }
public boolean empty() {
        return size() == 0;
    }
public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

栈的存储结构

  1. 顺序存储结构:可以使用一个数组stack[maxSize]和一个整型变量top来实现,数组存储栈的元素,变量表示栈顶指针。
  2. 链式存储结构:可以由结点构成的单链表实现,此时表头指针称为栈顶指针。
  3. 两种存储结构顶插入删除时间复杂度都是O(1)
  4. Java中的栈Stack.class是继承至Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable,所以内部是由数组实现的。

栈和递归

在计算机系统内,执行递归函数是通过自动使用栈来实现的,栈中每个元素包含递归函数的每个参数域、每个局部变量域和调用后的返回地址域。每次进行函数调用时,都要把相应的值压入栈,每次调用结束时,按照本次返回地址返回到指定的位置执行。

和栈相关的经典算法

  1. 倒叙打印
  2. 字符配对匹配(检查语法)
    输入一串字符,判断这个字符串的括号是否匹配,若匹配则返回1,否则返回0.

做括号进栈,右括号匹配出栈

public static int pickStr(String str) {
        if(str==null || str.length()<=0)return 0;
        int len = str.length();
        for(int i=0; i<len; i++) {
            String c = ""+str.charAt(i);
            switch(c){
            case "{":;
            case "[":;
            case "(":
                stack.push(c);
                break;
            case "}":
                if(stack.empty())return 0;//注意先判断非空
                if("{".equals(stack.peek())) {
                    stack.pop();
                }else {
                    return 0;
                }
                break;
            case "]":
                if(stack.empty())return 0;
                if("[".equals(stack.peek())) {
                    stack.pop();
                }else {
                    return 0;
                }
                break;
            case ")":
                if(stack.empty())return 0;
                if("(".equals(stack.peek())) {
                    stack.pop();
                }else {
                    return 0;
                }
                break;
            }
        }
        return stack.empty()?1:0;
    }
  1. 进制转换
    将十进制数num转换为r进制的过程为num除以基数r,得到余数y0,依次类推,然后再从高位到低位组合。
String transform(long num, int r){
    Stack<Integer> stack = new Stack<Integer>();
    while(num !=0){
        int k = num % r;
        stack.push(k);
        num = num/r;
    }
    StringBuilder sb = new StringBuilder();
    while(!stack.empty()){
        sb.append(stack.pop());
    }
    return sb.toString();
}
  1. 中缀表达式和后缀表达式
static Stack<String> stack = new Stack<String>(); //运算符栈
    public static String change(String midStr) {
        StringBuilder sb = new StringBuilder();
        if(midStr == null || midStr.length()<1) {
            return null;
        }
        int len = midStr.length();
        stack.push("@"); //给栈底放入特殊字符,最低优先级
        for(int i=0; i<len; i++) {
            String c = midStr.charAt(i)+"";
            //空格不用处理
            if(c.equals(" ")) {
                continue;
            }else if(c.equals("(")) { //左括号直接加入运算符栈
                stack.push(c);
                continue;
            }else if(c.equals(")")) { //右括号,使括号内的人留在栈中的运算符依次出栈并写入结果sb
                while(!stack.peek().equals("(")){
                    sb.append(stack.pop());
                }
                stack.pop(); //出左括号
            }else if(c.equals("+")||c.equals("-")||c.equals("*")||c.equals("/")) {//对于运算符,使暂存与栈顶且不低于c优先级的运算符依次出栈并写入结果
                String w = stack.peek();
                while(precedence(w) >= precedence(c)) {
                    sb.append(w);
                    stack.pop();
                    w=stack.peek();
                }
                stack.push(c); //符号入栈
                continue;
            }else { //此处必须为数字或小数点,否则中缀表达式错误
                char ch = midStr.charAt(i);
                if((ch<'0' || ch>'9') && ch!='.') {
                    return null;
                }
                while((ch>='0' && ch<='9') || ch=='.') { //把数值每一位依次假如结果字符串
                    sb.append(ch+"");
                    i++;
                    if(i>=len)break; //记得判断
                    ch = midStr.charAt(i);
                }
                i--;
                sb.append(" "); //数字之间以及和符号用空格隔开
                continue;
            }           
        }
        //把暂存在栈中的运算符依次退栈并写到结果
        String s = stack.pop();
        while(!s.equals("@")) {
            if(s.equals("(")){
                return null; //还有左括号,说明表达式出错
            }else {
                sb.append(s);
                s = stack.pop();
            }
        }
        return sb.toString();
    }

    private static int precedence(String w) {
        switch(w) {
        case "+":
        case "-":
            return 1;
        case "*":
        case "/":
            return 2;
        case "(":
        case "@":
        default:
                return 0;
        }
    }

change("12+(3(20/4)-8)6") 返回“12 3 20 4 /*8 -6 *+”

static Stack<Double> stack = new Stack<Double>(); //存储操作数和中间结果
    public static double compute(String str) {
        if(str==null || str.length()<=0) {
            return 0.0;
        }
        double x,y; //保存符点数
        int len = str.length();
        for(int i=0; i<len; i++) {
            if(str.charAt(i) == ' ') { //空格不做处理
                i++;
                continue;
            }
            switch(str.charAt(i)) {
                case '+':
                    x = stack.pop() + stack.pop(); //栈顶两个加法,赋值给x
                    break;
                case '-':
                    x = stack.pop();
                    x = stack.pop() - x; //先进栈到减后进栈的
                    break;
                case '*':
                    x = stack.pop() * stack.pop();
                    break;
                case '/':
                    x = stack.pop();
                    if(x!=0.0) { //被除数不能为0
                        x = stack.pop()/x;
                    }else {
                        return 0.0;
                    }
                    break;
                default:
                    x = 0; //保存浮点数的整数部分
                    while(str.charAt(i)>=48 && str.charAt(i)<=57) {
                        x = x*10 + str.charAt(i) - 48;
                        i++;
                    }
                    if(str.charAt(i) == '.') {
                        i++;
                        y = 0; //利用y保存扫描到到小数部分到值
                        double j = 10.0; //用j作为相应小数位到权值
                        while(str.charAt(i) >= 48 && str.charAt(i) <= 47) {
                            y = y + (str.charAt(i) - 48)/j;
                            i++;
                            j = j*10;
                        }
                        x = x + y; //合并为一个小数
                    }
            }
            stack.push(x); //操作数或中间结果入栈
        }
        x = stack.pop(); //栈中只有一个最终结果则正确,否则是错误的
        if(stack.empty())return x;
    
        return 0.0;
    }
  1. n个布尔值的所有可能组合 2^n

输出n个布尔值的所有可能组合,当n=1时有0和1两种,当n=2时有00、01、10、11四种,当n=n时,有2的n次方种,f(n) = 2*f(n-1) = 2^n. 设n个布尔量用布尔数组表示b[n],要得到b[0]-b[n-1]这n个布尔值的组合,则可看成b[0]在分别为true和false时b[1]-b[n-1]的组合。

private void comp(bool[] b, int k, int n){
    if(k==n){ //递归停止条件,输出排列好的组合
        for(int 1=0; i<n; i++){
            System.out.print(" " + b[i]);
        }
       System.out.println("");
    }else{
        b[k] = false;
        return comp(b, k+1, n);
        b[k] = true;
        return comp(b, k+1, n);
    }
}

调用时k从0开始

  1. 1-n个元素的全排列

输出1-n这n个自然数的全排列 n!
n! = n*(n-1)!

用一个数组a[n]来保存1-n之间的n个自然数,对于i=0n-1,每次使a[0]同a[i]交换(i=0,1,2...n-1)后,进行递归,然后再交换a[0]与a[i]使它恢复为此排列前的状态;同理,对于i=1n-1,每次使a[1]同a[i]交换(i=1,2...n-1)后,进行递归,然后再交换a[1]与a[i]使它恢复为此排列前的状态;依次类推。

private void permute(int[] a, int s, int n){
    int temp;
    if(s==n-1){ //递归到最后一个元素结束,输出结果
        for(int i=0;i< n; i++){
            System.out.print(""+a[i]);
        }
        System.out.println("");
    }else{
        for(int i=s; i< n; i++){ //循环n-s次,每次使a[s]取一个新值
            temp = a[s]; a[s] = a[i]; a[i] = temp; //交换a[s]和a[i]
            permute(a, s+1; n);
            temp = a[s]; a[s] = a[i]; a[i] = temp;//交换回 a[s]和a[i]
        }
    }
}

调用时s从0开始

  1. 迷宫

一个迷宫包含m行n列小方格,每个方格用0表示可通行,1表示墙壁,当然入口和出口都是0才是可通行的。从入口开始,每次可上下左右任意方向移动一格,求从入口到出口到一条路径。

int[][] maze;
    int[][] mark;
    int m,  n;
    int[][] move= {{1,0}, {0,1}, {-1,0}, {0,-1}}; //右下左上的偏移下标
    private void seekPath(int[][] path) {
        m = path.length;
        n = path[0].length;
        maze = new int[m+2][n+2]; //加上围墙的迷宫数组
        mark = new int[m+2][n+2]; //保存访问标记
        for(int i=0; i<m+2; i++) {
            for(int j=0; j<n+2; j++) {
                //给迷宫数组外围加上一层“围墙”,这样迷宫数组每个位置都有四个方向,不用去判断各种边界条件
                if(i==0 || i==m+1 || j==0 || j==n+1) {
                    maze[i][j] = 1;
                }else {
                    maze[i][j] = path[i-1][j-1];
                }
                
                mark[i][j] = 0;
            }
        }
        mark[1][1] = 1; //入口设为1,表示访问过
        
        if(seek(1, 1)) { //从新迷宫数组入口(1,1)处递归
            System.out.print("(" + 0 + "," + 0 + ")"); //按所经过位置的反序输出,最后输出起点坐标
        }else {
            System.out.print("无通路");
        }
    }
    
    private boolean seek(int x, int y) {
        //从迷宫中坐标点(x,y)的位置寻找通向终点的路径,找到返回true,否值返回false
        int g,h; //作为下一个位置的行列坐标
        if(x==m && y==n)return true; //到达出口
        for(int i=0; i<4; i++) { //循环尝试4个方向
            g = x + move[i][0];
            h = y + move[i][1];
            if(maze[g][h]==0 && mark[g][h]==0) { //下一个为0,且没访问过
                mark[g][h] = 1; //标记为访问
                if(seek(g,h)){ //继续递归找下一个
                    int ap = g-1;
                    int bp = h-1;
                    System.out.print("(" + ap + "," + bp + ")"); //找到打出坐标
                    return true;
                }
            }
        }
        return false;
    }
  1. 汉诺塔问题

相传它源于印度神话中的大梵天创造的三个金刚柱a,b,c,一根柱子上叠着上下从小到大n个黄金圆盘。大梵天命令婆罗门将这些圆盘按从小到大的顺序从a柱移动到c柱子上,其中大圆盘不能放在小圆盘上面,依次打出移动过程。

/**
     * @param n n个圆盘
     * @param a 柱子a
     * @param b 柱子b
     * @param c 柱子c
     */
    private void hanTower(int n, char a, char b, char c) {
        if(n==1) {
            System.out.println( a + " -> " + c); //一个,直接从a柱子搬到c柱子
        }else {
            hanTower(n-1, a, c, b); //首先以c柱子为过渡,把a上面到n-1个搬到b上
            System.out.println( a + " -> " + c); //再把a的搬到c
            hanTower(n-1, b, a, c); //后续目标,把上面搬到过渡柱b上的n-1个搬到c上
        }
    }
    
    public static void main(String[] args) {
        hanTower(3, 'a', 'b', 'c');
    }
上一篇下一篇

猜你喜欢

热点阅读