数据结构(1)栈

156 阅读9分钟

数据结构与算法的认识

什么是数据结构

数据结构是计算机中存储、组织数据的方式。通常情况下,选择合适的数据结构可以带来最优效率的算法。

常见的数据结构

常见的数据结构有:栈、队列、链表、集合、字典、树、图

什么是算法

一个有限指令集,每条指令的描述不依赖于语言。接受一些输入(有些情况下不需要输入)并产生输出,一定在有限步骤之后终止。算法就是解决问题的步骤逻辑,数据结构的实现离不开算法。

栈的结构

1.栈(stack),是一种运算受限的线性结构,遵循后进先出的原则(LIFO,Last In First Out),表示后入栈的元素先出栈。

2.栈的限制是仅允许在表的一端进行插入和删除运算,这一端被称为栈顶,而另一端称为栈底。

3.向一个栈插入新元素称作入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素。从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

image.png

栈结构相关的面试题

有六个元素6,5,4,3,2,1顺序入栈,问下列哪一个不是合法的出栈序列?()
A.5 4 3 6 2 1
B.4 5 3 1 2 6
C.3 4 6 5 2 1
D.2 3 4 1 5 6

分析:入栈顺序是 6,5,4,3,2,1,6先入栈1最后入栈

A.5需要出栈,则表示6已经先入栈,然后5再入栈,此时5是栈顶元素可以直接出栈,再将4入栈,4是栈顶元素可以出栈,再将3入栈,3是栈顶元素可以出栈,然后栈顶元素就是最开始入栈的6将6出栈,再将2入栈然后出栈,最后将1入栈再出栈。此时入栈的顺序就是6,5,4,3,2,1而出栈的顺序也是合法的。

B.4需要出栈,则表示6,5已经先入栈,然后4入栈此时4是栈顶元素可以直接出栈,然后5是栈顶元素5可以直接出栈,然后将3入栈则栈顶元素就是3可以出栈,然后再将2和1依次入栈,此时栈顶元素是1可以出栈,再然后栈顶元素是2可以出栈,最后将最开始入栈的6出栈。此时入栈的顺序就是6,5,4,3,2,1而出栈的顺序也是合法的。

C.压入6->压入5->压入4->压入3,再弹出3,再弹出4,此时需要先弹出5才能弹出6,出栈顺序就不合法。

D.压入6->压入5->压入4->压入3->压入2,再弹出2,再弹出3,再弹出4,再压入1后弹出1,再弹出5最后弹出6。此时入栈的顺序就是6,5,4,3,2,1而出栈的顺序也是合法的。

栈结构实现

栈常见的操作

  • push(element): 添加一个新元素到栈顶位置.
  • pop():移除栈顶的元素,同时返回被移除的元素。
  • peek():返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)。
  • isEmpty():如果栈里没有任何元素就返回true,否则返回false
  • clear():移除栈里的所有元素。
  • size():返回栈里的元素个数。这个方法和数组的length属性很类似。

ES5语法封装

 function Stack() {
            // 栈中的属性,开辟空间保存栈元素
            let items = []

            // 栈相关的方法
            // 压栈操作,向栈顶添加元素,数组的最后添加元素
            this.push = function (element) {
                items.push(element)
            }

            // 出栈操作,移除栈顶元素,返回栈顶元素(数组最后一个元素)
            this.pop = function () {
                return items.pop()
            }

            // peek操作,不改变栈的结构,只返回栈顶元素
            this.peek = function () {
                return items[items.length - 1]
            }

            // 判断栈中的元素是否为空,栈为空为true,用数组的length长度来判断栈是否为空
            this.isEmpty = function () {
                return items.length == 0
            }

            // 获取栈中元素的个数等于数组中元素的个数
            this.size = function () {
                return items.length
            }
        }

ES5语法将方法封装在实例对象的原型对象上

 function Stack() {
            // 栈中的属性,开辟空间保存栈元素
            let items = []

            // 栈相关的方法
            // 压栈操作,向栈顶添加元素,数组的最后添加元素
            Stack.prototype.push = function (element) {
                items.push(element)
            }

            // 出栈操作,移除栈顶元素,返回栈顶元素(数组最后一个元素)
            Stack.prototype.pop = function () {
                return items.pop()
            }

            // peek操作,不改变栈的结构,只返回栈顶元素
            Stack.prototype.peek = function () {
                return items[items.length - 1]
            }

            // 判断栈中的元素是否为空,栈为空为true,用数组的length长度来判断栈是否为空
            Stack.prototype.isEmpty = function () {
                return items.length == 0
            }

            // 获取栈中元素的个数等于数组中元素的个数
            Stack.prototype.size = function () {
                return items.length
            }
        }

ES6语法封装一个类实现栈的操作

class Stack {
    constructor() {
        // 栈中的属性,开辟空间保存栈元素
        this.item = [];
    }
    //原型方法
    // 1.向栈顶添加元素   (数组的最后添加元素)
    push(element) {
        this.item.push(element);
    }

    // 2.出栈操作,移除栈顶元素,返回栈顶元素(数组最后一个元素)
    pop() {
        return this.item.pop();
    }

    // 3.peek操作,不改变栈的结构,只返回栈顶元素
    peek() {
        return this.item[this.item.length - 1];
    }

    // 4.判断栈中的元素是否为空,栈为空为true,用数组的length长度来判断栈是否为空
    isEmpty() {
        return this.item.length == 0;
    }
    // 5.清空栈
    clear() {
        this.item = [];
    }
    // 6.获取栈中元素的个数等于数组中元素的个数
    size() {
        return this.item.length;
    }
};

栈的应用

十进制转任意进制

 //十进制转换为任意进制
        function tenTransformAny(num, scale) {
            //num除数,scale进制数
            //创建空栈
            let stack = new Stack();
            let yushu;
            let arr = ["A", "B", "C", "D", "E", "F"];
            while (num > 0) {
                //余数入栈的终止条件是除数大于0,除数小于等于0时余数不用入栈
                //余数
                yushu = num % scale;
                //16进制:余数大于9,余数为10是A,11是B,12是C...15是F
                if (yushu > 9) {
                    //比如余数是11,就为B,11-10数组下标取1得到B
                    yushu = arr[yushu - 10];
                    //余数入栈
                    stack.push(yushu);
                } else {
                    //余数入栈
                    stack.push(yushu);
                }
                //除数改变
                num = Math.floor(num / scale);
            }
            //栈不为空则依次余数出栈
            let str = "";
            while (!stack.isEmpty()) {
                str+=stack.pop();
            }
            return str;
        };

括号匹配问题

代码思路:声明一个可以建立左右括号对应关系的对象(key为左括号,value为右括号),然后使用for...of遍历字符串,如果遍历的元素是左括号,就直接入栈,如果是右括号,将栈顶的元素出栈与当前遍历到的元素进行比对,如果不匹配则return false;如果匹配则出栈;如果左右括号都不是,那就终止本次循环(continue);最后判断传入的字符串是否合法等价于栈的长度是否为0

function symbolMatch(str) {
            //创建栈
            let stack = new Stack();
            //创建对象key是左符号,value是右符号
            let obj = {
                "{": "}",
                "(": ")",
                "[": "]",
            }
            //遍历str字符串
            for (let key of str) {
                //是左符号obj[key]可以取出值,布尔判定是true
                if (obj[key]) {
                    //遍历str字符串遇到左符号,就依次将左符号入栈
                    stack.push(key);
                } else if (Object.values(obj).includes(key)) {
                    //是右符号则遍历obj对象的所有value时会包含右符号,(Object.values(obj).includes(key)返回true
                    //遍历obj对象的所有value,如果是右符号则Object.values(obj).includes(key)布尔判定为true
                    //栈顶元素出栈
                    let element = stack.pop();
                    //右符号和栈顶不匹配
                    if (key != obj[element]) {
                        //匹配不成功就直接false
                        return false;
                        //匹配成功不做任何操作就又开始遍历下一个字符进行下一次比较
                    }
                } else {
                    // 如果左右符号都不是,那就终止本次循环continue,继续下一次循环
                    continue;
                }
            }
            //遍历完成之后要保证栈内要为空,如果最后都成功匹配那么栈一定为空,返回true,若栈不为空表示还有未匹配的左符号就返回false表示不匹配
            return stack.isEmpty();
        }

使用栈判断当前字符串是不是回文数

代码思路:创建一个栈,遍历当前字符串将字符依次入栈,再声明一个新的空字符串,让栈中的元素依次出栈并将元素拼接到新的字符串,最后比较新字符串与当前原字符串是否相等即可

function isPalindromicNumber(arg) {
            //创建栈
            let stack = new Stack();
            let str = "";
            //先遍历字符串将字符依次入栈
            for (let j = 0; j < arg.length; j++) {
                stack.push(arg[j]);
            }
            //将元素依次出栈,并拼接到新的字符串str上,再比较str与原字符串是否相等
            //栈为空stack.isEmpty()是true就停止出栈结束while循环,条件语句为false时while结束
            while (!stack.isEmpty()) {
                str += stack.pop();
            }
            //出栈拼接的str与原字符串相等就是回文数返回true
            return str == arg;
        };

佩兹糖果盒

案例:现实生活中栈的一个例子是佩兹糖果盒。想象一下你有一盒佩兹糖果,里面塞满了红色、黄色和白色的糖果,但是你不喜欢黄色的糖果。使用栈(有可能用到多个栈)写一段程序,在不改变盒内其他糖果叠放顺序的基础上,将黄色糖果移出。

代码思路:需要两个栈,一个栈保存原来的糖果,原来的糖果依次出栈,颜色是黄色就直接出栈,颜色是红色或者白色的糖果就放到另外一个栈,直到保存原来糖果的栈为空时全部糖果已取出,再将另外一个栈保存的只有红色以及白色糖果的栈依次出栈再依次入栈到原来的糖果盒

function peizibox(sweetBox) {
            let newbox=new Stack();
            //原来的糖果依次出栈,直到保存原来糖果的栈为空时全部糖果已取出
            while(!sweetBox.isEmpty()){
                //颜色是黄色就直接出栈不做任何操作,不是黄色就放到另外一个栈
                let re=sweetBox.pop()
                if(re!="yellow"){
                    newbox.push(re);
                }
            }
            //再将另外一个栈保存的只有红色以及白色糖果的栈依次出栈再依次入栈到原来的糖果盒,另外一个栈不为空就一直出栈,为空就结束出栈
            while(!newbox.isEmpty()){
                let candy=newbox.pop();
                sweetBox.push(candy);
            }
            //返回原来的盒子就只装有红色以及白色的糖果且顺序未改变
            return sweetBox;
        }