java数据结构-栈.md

栈的定义和数据类型

栈定义

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

栈的基本操作

  • 入栈
public E push(E item) {
        addElement(item);
        return item;
    }
  • 出栈 pop() (要先判断非空)
public synchronized E pop() {
        E  obj;
        int len = size();

        obj = peek();  //调用peek
        removeElementAt(len - 1); //下标是从底往上算的
        return obj;
    }
  • 获取栈顶元素 peek() (要先判断非空)
public synchronized E peek() {
        int     len = size();

        if (len == 0) //空会抛异常
            throw new EmptyStackException();
        return elementAt(len - 1);
    }
  • 判断栈是否为空 empty()
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. 中缀表达式和后缀表达式
  • 中缀表达式:把双目运算符出现在两个操作数中间的表达式称为中缀表达式。如12/6+2
  • 后缀表达式:又称逆波兰式,把运算符放在两个运算对象的后面。如上面中缀表达式对应的后缀表达式为126/2+
  • 中缀表达式既要考虑括号还要考虑运算符的优先级,对计算机来说太复杂,后缀表达式能很好解决这种问题,计算过程完全按照运算符出现对先后顺序,整个计算过程只需扫描一遍即可。
  • 中缀表达式转换为后缀表达式对规则:把每个运算符都移到他的两个元算对象的后面,然后删除所有的括号即可。
    中缀表达式转换为后缀表达式的算法如下
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');
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容