线性结构-栈

简介: 线性结构-栈
栈是 Stack一个后进先出 Last In First Out,LIFO的线性表,他要求只在表尾对数据执行删除和插入等操作。
栈就是一个线性表, 可以是数组、也可以是链表。但它的操作有别于一般的线性表。栈的元素必须先进后出,也就是 先进入栈的元素必须后出栈。而不能像一般的链表或数组那样从任意位置读取元素。
栈的操作只能在线性表的表尾进行,这个标为被称为栈的栈顶 top,相应的表头被称为栈的栈底 bottom


栈的数据必须从栈顶进入,也必须从栈顶取出,先入栈的数据在后入栈的数据下面。
栈中不含有任何数据时的状态叫作空栈,此时栈顶top等于栈底bottom。

栈的定义

前面说过,作为一个线性结构,栈既可以用数组实现,也可以用链表实现。
在大多数情况下,我们用数组来实现栈。

public class MyStack {
    int[] stack; // 用数组实现一个栈
    int top; // 栈顶索引,实际上就是栈顶位置的数组下标
    int capacity; // 栈的容量
    public MyStack(int capacity) {
        stack = new int[capacity]; // 动态初始化栈,长度为capacity
        top = 0; // 栈顶索引为0,说明此时是空栈
        this.capacity = capacity; // 初始化栈的容量
    }
    //更多操作
}
MyStack类中并没有定义栈底位置bottom的值。这是因为我们是使用数组来实现栈的,所以bottom的值恒为0。
如果采用链表的形式实现栈,则需要定义bottom的值,它应当是指向栈底节点的指针变量(引用变量)。

MyStack是我们定义的栈类,包含3个成员变量:

  • stackint[]类型的数组,说明栈将存储整型。
  • top:栈顶在数组stack中的下标,一般规定top始终指向栈顶元素的上一个空间。真正的栈顶元素是stack[top-1]
  • capacity:记录栈当前的容量,随着栈中元素不断增加,可对栈进行扩容,变量capacity的值也应随之调整。

在调用构造函数public MyStack(int capacity)初始化一个栈后。top等于bottom等于0,即栈顶等于栈底,此时栈中没有数据(空栈)

栈的基本操作

入栈

入栈就是向栈中存入数据。入栈要在栈顶进行,每当向栈顶压入一个数据,栈顶指针top都要+1,直到栈满。

public void push(int elem) {
    // 入栈操作
    if (top == capacity) {
        // 达到栈容量上限,需要扩容
        increaseCapacity();
    }
    stack[top] = elem; // 将元素elem存放在stack[top]
    top++; // top指向栈顶元素的上一个空间,此时栈顶元素为stack[top-1]
}

在数据入栈前判断栈是否已满if (top == capacity)
因为规定了top始终指向栈顶元素的上一个空间,下标top是从0开始计算的,所以**stack[top-1]**就是栈顶元素,并且**top**值等于该栈中元素的个数。当top等于capacity时,栈中元素的个数等于capacity,top已经指向stack数组的外面,所以不能再向栈中压入元素,需要调用increaseCapacity()函数对栈进行扩容。

public void increaseCapacity() {
    // 增加栈的容量
    // 初始化一个新栈,容量是原stack容量的2倍
    int[] stackTmp = new int[stack.length * 2];
    System.arraycopy(stack, 0, stackTmp, 0, stack.length);
    stack = stackTmp;
}

将参数存入栈顶,即执行stack[top] = elem;操作。
然后移动top,指向栈顶元素的上一个位置top++;

出栈

函数pop()的作用是将栈顶元素取出并返回,同时调整栈顶指针top的值-1

public int pop() {
    // 出栈操作
    if (top == 0) {
        // 栈顶等于栈底,说明栈中没有数据
        System.out.println("There are no elements in stack");
        return -111;
    }
    return stack[--top];
}

与所有数据结构一样,在执行增删操作之前,要先判断是否符合条件。
从栈顶取出元素前,需要判断栈内是否有元素。当top==0时,栈内没有元素,pop的操作将是非法的,所以需要返回一个无效值ERROR_ELEM_VALUE,在介绍“线性结构-数组”中,有一道“删除重复元素”的题目,当时将重复元素赋值为-111,也是同样的道理。一旦将某个整数赋值给常量**ERROR_ELEM_VALUE**,则该整数就不能作为栈中的有效元素了

来两道题

二/十进制转换


利用栈结构将二进制数转换为十进制数


利用栈的FILO特点,方便位权运算

首先将二进制数从高位到低位顺序入栈。然后从栈顶依次取出每一个元素。当取出第i个元素时,结果增加$2^{i-1}$。最终的和即为该二进制数对应的十进制数。

public class BiToDecTest {
    public static String BiToDec(String binary) {
        MyStack stack = new MyStack(10);
        int i;
        for (i = 0; i < binary.length(); i++) {
            if (binary.charAt(i) == '0') {
                stack.push(0); // 将二进制0入栈
            } else {
                stack.push(1);// 将二进制1入栈
            }
        }
        i = 0;
        int e;
        long sum = 0;
        while ((e = stack.pop()) != stack.ERROR_ELEM_VALUE) {
            sum = sum + (int) (e * Math.pow(2, i));
            i++;
        }
        return String.valueOf(sum);
    }

    public static void main(String args[]) {
        System.out.println("The result of converting 11001 to decimal number is");
        System.out.println(BiToDecTest.BiToDec("11001"));
    }
}

public static String BiToDec(String binary)String.valueOf(sum)的作用是将binary指定的二进制串转换成对应的十进制串并返回。参数**binary****String**类型,为了与之对应,函数的返回值也是**String**类型,并通过**String.valueOf()**函数将十进制转换成对应的字符串

括号匹配问题


已知表达式中只允许有两种括号:圆括号()和方括号[]。它可以任意地嵌套使用,例如[()][()()][()([])]都是合法的表达式。括号必须成对出现,[(])([)][())等不成对出现的情况是非法的。编写一个程序,判断一个括号表达式是否合法。


一道栈的经典问题
  1. 合法的情况下,第一个入栈的字符应该是左括号。
  2. 我们可以在检测到下一个字符为右括号时,弹出栈内的左括号,查看是否匹配。
  3. 如果不匹配或已经空栈,则表明括号表达式不合法。
我们介绍一段没上面那么好理解的代码:

循环遍历字符串上的字符,每个字符进行如下判断:
首先是判断是否栈空,如果栈不为空,则将栈顶c与临时字符expression.charAt(i)匹配,成功则继续遍历;不成功则再将刚刚出栈的元素塞回,并将临时字符入栈。
如果栈为空,则将临时字符expression.charAt(i)直接入栈。
如果表达式合法,所有元素都被弹出,最后结果是空栈。
因此最后一步即为判断是否为空栈,栈空则表示合法。不为空则非法。

public static boolean MatchBracket(String expression) {
    // 判断括号表达式expression是否合法
    MyStack stack = new MyStack(10); // 初始化栈
    char c;
    for (int i = 0; i < expression.length(); i++) {
        if ((c = stack.pop()) != stack.ERROR_ELEM_VALUE) {
            // 从栈中弹出了有效元素,说明栈不为空
            if (!match(c, expression.charAt(i))) {
                // 如果栈顶元素c跟expression的第i个括号
                // 不相匹配,则将c重新入栈,同时将
                // expression的第i个括号入栈
                stack.push(c);
                stack.push(expression.charAt(i));
            }
        } else {
            // 如果是空栈,说明输入的是第1个括号,因此保存在栈中
            stack.push(expression.charAt(i));
        }
    }
    if (stack.pop() != stack.ERROR_ELEM_VALUE) {
        // 栈不为空,说明表达式expression不合法,返回false
        return false;
    } else {
        return true; // 栈为空,说明表达式expression合法,返回true
    }
}

在检索完括号表达式之后,如果栈已空,说明左右括号完全匹配,即括号表达式合法。
如果栈未空,则表明输入的括号不完全匹配,也就是括号表达式非法。
对于匹配括号的函数,我们不能简单地作==的判断,因为'('注定不等于')'
如果是C++,我们可以使用map容器,右括号为索引,所括号为实值。因为我们是检测到右括号之后才去匹配左括号,所以要将右括号作为索引。
如果是Java,也有类似的容器。但在这里,我们使用更粗暴的方法。

private static boolean match(char a, char b) {
    if ((a == '(' && b == ')') ||
            (a == '[' && b == ']')) {
        return true;
    }
    return false;
}
目录
相关文章
|
11天前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
16 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
11天前
初步认识栈和队列
初步认识栈和队列
36 10
|
5天前
数据结构(栈与列队)
数据结构(栈与列队)
11 1
|
11天前
|
算法
数据结构与算法二:栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式
这篇文章讲解了栈的基本概念及其应用,并详细介绍了中缀表达式转换为后缀表达式的算法和实现步骤。
28 3
|
10天前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
37 1
|
13天前
|
存储 安全 Java
【用Java学习数据结构系列】探索栈和队列的无尽秘密
【用Java学习数据结构系列】探索栈和队列的无尽秘密
25 2
|
6天前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
9 0
|
11天前
探索顺序结构:栈的实现方式
探索顺序结构:栈的实现方式
|
11天前
|
存储 C语言
栈和队列题目练习
栈和队列题目练习
12 0
|
18天前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
84 64