我所知道的数据结构之栈

做者前言

你们好,我是阿濠,今篇内容跟你们分享的是数据结构之栈,很高兴分享到segmentfault与你们一块儿学习交流,初次见面请你们多多关照,一块儿学习进步.

1、什么是栈

官方定义:栈(stack)是一个后进先出的线性表,它要求只在表尾作进行删除和插入操做。

栈:后进者先出,先进者后出,这就是典型的栈结构。java

举个例子:就像叠盘子同样,后来放的盘子老是在上面,拿的时候也是从上面拿,也就是先拿后来放上面的盘子,最后拿最先放的盘子。segmentfault

栈是一种重要的线性结构,栈是线性表的一种形式,在生活中,咱们的浏览器,每点击一次“后退”都是返回最近的一次浏览页面,至关于就是后进先出数组

栈有一些特殊限制:
1.栈元素必须后进先出
2.栈的操做只能在这个线性表的表尾进行
3.对于栈来讲,表尾则是栈顶(Top),表头则是栈底(bottom)浏览器

栈的插入和删除操做

由于栈的本质是线性表,线性表有两种存储方式,因此栈也分两种存储方式分别为栈的顺序存储结构、栈的链式存储结构数据结构

栈的插入操做(Push)叫作进栈,也称为入栈,对于顺序栈的进栈操做只需将新的数据元素存入栈内,而后让记录栈内元素个数的变量加1,程序便可再次经过arr[size-1]从新访问新的栈顶元素。进栈操做示意图以下:学习

因为顺序栈底层一般会采用数组来保存数据元素,所以可能出现的状况是:当程序试图让一个数据元素进栈时,底层数据已满,那么就必须扩充底层数组的长度来容纳新进栈的数据元素。spa

栈的删除操做(Pop)叫作出栈,也称为弹栈,对于顺序栈的出栈操做而言,须要将栈顶元素弹出栈,程序要作两件事。3d

  • 让记录栈内元素个数的变量减1.
  • 释放数组对栈顶元素的引用。

对于删除操做来讲,只要让记录栈内元素个数的size减1,程序便可经过arr[size-1]访问到新的栈顶元素。但不要忘记释放原来栈顶的数组引用,不然会引发内存泄漏。code

栈比普通线性表的功能更弱,栈是一种被限制过的线性表,只能从栈顶插入,删除数据元素。blog

栈的链式存储结构及实现

能够采用单链表来保存栈中全部元素,这种链式结构的栈也被称为栈链。对于栈链而言,栈顶元素不断地改变,程序只要使用一个top引用来记录当前的栈顶元素便可。

链式的进栈操做,只须要作几件件事:

  • 让top引用指向新元素添加的元素
  • 新元素的next引用指向原来的栈顶元素。
  • 让记录栈内元素个数的size变量加1.

链式的出栈操做,只须要作几件件事:

  • 释放原来的栈顶元素
  • 让top引用指向原栈顶元素的下一个元素
  • 让记录栈内元素个数的size变量减1.

从空间利用率的角度说,链栈的空间利用率比顺序栈的空间利用率要高一些。

2、认识栈

java中有封装好的类,能够直接调用

  • push(element): 添加一个新元素到栈顶位置.
  • pop():移除栈顶的元素,同时返回被移除的元素。
  • peek():返回栈顶的元素,不对栈作任何修改(仅仅返回栈顶的元素)。
  • isEmpty():若是栈里没有任何元素就返回true,不然返回false
  • clear():移除栈里的全部元素。
  • size():返回栈里的元素个数。这个方法和数组的length属性很相似。

咱们能够作一个二进制转换十进制的方式去看看栈的后进先出,从数学的角度来讲二进制转换为十进制是最低位起去乘以N位的2^(n-1),而后所有加起来

简单的来讲示例二进制:1000 转换成十进制为8 why?

代入公式:0*2^(1-1)+0*2^(2-1)+0*2^(3-1)+1*2^(4-1)=0+0+0+8

假设咱们输入的数字是11001001这样的二进制数,那么放入栈就是
图片.png

若是就会发现top栈顶就是最低位,依次算完发现栈底就是最高位

经典使用场景 - 表达式求值

经过两个栈实现的这个功能的
一个栈保存操做数,另外一个栈保存运算符,咱们从左到右遍历表达式
1.当遇到数字时,将其压入操做数栈;
2.当遇到运算符时,就与运算符栈的栈顶元素进行比较。

若是比运算符栈顶元素的优先级高,就将当前运算符压入栈;
若是比运算符栈的栈顶元素的优先级低或相同,那么就从操做数栈的栈顶取2个操做数,而后进行计算,再把计算完成的结果压入操做数栈,继续比较。

下面咱们举个例子:3+5*8-6这个表达式

经典使用场景 - 现浏览器的前进、后退功能

  • 使用两个栈X和Y,首次浏览的界面入X栈。
  • 后退时,依次从X中出栈,并将出栈的数据依次放入Y栈
  • 前进时,从Y栈中出栈,在依次入X栈。
  • 当X栈中没有数据时,说明不能后退了;
  • 当Y栈中没有数据时,说明不能前进了。

好比你顺序查看了 a,b,c 三个页面,咱们就依次把 a,b,c 压入栈,这个时候,两个栈的数据就是这个样子:

当你经过浏览器的后退按钮,从页面 c 后退到页面 a 以后,咱们就依次把 c 和 b 从栈 X 中弹出,而且依次放入到栈 Y。这个时候,两个栈的数据就是这个样子:

这个时候你又想看页面 b,因而你又点击前进按钮回到 b 页面,咱们就把 b 再从栈 Y 中出栈,放入栈 X 中。此时两个栈的数据是这个样子:

这个时候,你经过页面 b 又跳转到新的页面 d 了,页面 c 就没法再经过前进、后退按钮重复查看了,因此须要清空栈 Y。此时两个栈的数据这个样子:

3、代码实战

最长有效括号
题目描述: 给定一个只包含 '('')' 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"

示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

//    解法一:栈+暴力            
public boolean isValid(String s) { // 判断任何一个子字符串s是否有效
    Stack<Character> stack = new Stack<Character>();
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            stack.push('(');
        } else if (!stack.empty() && stack.peek() == '(') {
            stack.pop();
        } else {
            return false;
        }
    }
    return stack.empty();
}
//* 时间复杂度  O(n^2) 空间复杂度  O(n) *
public int longestValidBracketLengthOne(String s) {
    int maxLength = 0;
    for (int i = 0; i < s.length(); i++) {
        for (int j = i + 2; j <= s.length(); j+=2) {
            if (isValid(s.substring(i, j))) {
                maxLength = Math.max(maxLength, j - i);
            }
        }
    }
    return maxLength;
}

假设咱们按照思路输入")())" 初始的时候maxLength=0
1.i=0时,j=2,进入isValid方法的s=")(",这时的stack=0,方法里的for循环i=0时,无符合要求,直接return false结束j+=2
2.j=4,进入isValid方法的s=")())"这时的stack=0,方法里的for循环i=0时,无符合要求,直接return false结束i++
图片.png

3.i=1,j=3,进入isValid方法的s="()",方法里的for循环i=0时,符合第一个if则push加入栈,stack=1,i++判断i=1时,不符合第一个if,符合第二个if,由于刚刚加入栈的栈顶='(',这时作了出栈的操做,stack=0,结束方法此时maxLength=3-1=2 结束i++
图片.png

4.i=2,j=2,进入isValid方法的s="))",不符合for循环,j=4,j=4,进入isValid方法的s=")())",也不符合..

图片.png

........

此方法一外面for循环作一次,里面for也要作一次,即作n*n次,因此时间复杂度 O(n^2)

//    解法二:栈                    
public int longestValidBracketLengthTwo(String s) {
    if (s == null || s.length() == 0) {
        return 0;
    }
    int maxLength = 0; // 设定返回值 即最长有效括号的长度
    int n = s.length(); // 保存须要循环的次数 也就是字符串的最初长度
    char[] sArr = s.toCharArray(); // 字符串生成相应的字符数组

    Stack<Integer> stack = new Stack<Integer>(); // 建立系统的栈类 存放字符数组中的下标 
    stack.push(-1); // -1 入栈用于处理边界条件

    for (int i = 0; i < n; i++) {
        // 循环第i项对应的字符是 ')'
        // 且 stack.size() > 1 表示栈不为空
        // 且 必须保证栈顶元素(下标 )对应的字符是 '('
        if (sArr[i] == ')' && stack.size() > 1 && sArr[stack.peek()] == '(') {
            stack.pop(); // 对应字符'(' 的栈顶元素(下标 ) 出栈
            // 记录最长长度 由于i是线性往前增长  而栈顶元素(下标 )是线性增减
            maxLength = Math.max(maxLength, i - stack.peek()); 
        } else { // 其余状况,直接将当前位置入栈
            stack.push(i);
        }
     }
    return maxLength;
}

假设咱们按照思路输入")())" 初始的时候maxLength=0

我定义n=输入的字符串长度也是循环的次数,并生成相对应的char[]字符数组,与系统栈,
给栈定义边界为-1,避免对应不上char字符数组

图片.png

并根据char数组肯定循环次数,从而先找到')'的下标再判断栈的栈顶是否'(',若是对应的下标不符合要求则将下标加入栈

图片.png

咱们找到下标0是')',可是目前的栈顶的值对应数组不是'(',因此将下标0放入栈里,对应的')'称为栈顶。

图片.png

下标1对应的数组是'('不是咱们要找的值

图片.png

下标2对应的数组是')',符合咱们的要求,目前的栈顶是1,对应的数组是'('符合要求,咱们将目前的栈顶弹出,此时站的个数由3-1=2,且栈顶对应的值就是0,maxLength=2-0

图片.png

......

由于只须要将字符串变成char数组,而且将数组里的char判断符合逻辑问题,因此是 时间复杂度 O(n) 空间复杂度 O(n)

相关文章
相关标签/搜索