数据结构学习之栈

1. 认识栈结构

  • 栈结构
    • 栈结构是一种常见的线性数据结构
    • 栈是一种只能在一端进行插入或删除操作的线性表
    • 表中允许插入,删除操作的一端称为栈顶,表的另一端称为栈底.
    • 向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;
    • 从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈结构如下图所示:
在这里插入图片描述

  • 栈的特点

    • 栈的主要特点是先进后出
    • 即后进入栈的元素先出栈
    • 每次进栈的数据元素都放在原来栈顶元素之前称为新的栈顶元素,每次出栈的数据元素都是当前栈顶元素
  • 生活中类似于栈的例子

    • 若干人走进一个死胡同,假设该胡同的宽度只允许一个人进出,那么走出胡同的顺序和走出的顺序就正好相反.
    • 比如我们去食堂打饭的时候都需要拿盘子,最新放上去的, 最先被学生拿走使用.最早放上去的,最后会被使用.

栈结构详细图解
在这里插入图片描述

2. 常见的栈笔试题

在这里插入图片描述

题目答案: C

  • A答案: 65进栈, 5出栈, 4进栈出栈, 3进栈出栈, 6出栈, 21进栈,1出栈, 2出栈
  • B答案: 654进栈, 4出栈, 5出栈, 3进栈出栈, 2进栈出栈, 1进栈出栈, 6出栈
  • D答案: 65432进栈, 2出栈, 3出栈, 4出栈, 1进栈出栈, 5出栈, 6出栈

用四个动图来展示这个过程(这个过程这样演示应该还是比较清楚的):
A选项:
在这里插入图片描述
B选项
在这里插入图片描述
C选项
在这里插入图片描述
D选项
在这里插入图片描述

3.栈结构的实现

我们通过js语言来实现一个类,以此来模拟栈的操作

3.1 栈的创建

  • 我们先来创建一个栈的类, 用于封装栈相关的操作
    function Stack() {
        // 栈中的属性
      var items = [];

      //栈中的方法
    }

代码解析:

  • 我们创建了一个Stack构造函数, 用户创建栈的类.
  • 在构造函数中, 定义了一个变量, 这个变量可以用于保存当前栈对象中所有的元素.
  • 这个变量是一个数组类型. 我们之后无论是压栈操作还是出栈操作, 都是从数组中添加和删除元素.

3.2 栈的常见操作列举

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

注意: 我们的实现是将最新的元素放在了数组的末尾, 那么数组末尾的元素就是我们的栈顶元素

      //push
      Stack.prototype.push = function(el) {
        items.push(el);
      }
  • pop方法

注意:

  1. 出栈操作应该是将栈顶的元素删除, 并且返回.

  2. 因此, 我们这里直接从数组中删除最后一个元素, 并且将该元素返回就可以了

      Stack.prototype.pop = function() {
        return items.pop();
      }
  • peek 方法

peek方法是一个比较常见的方法, 主要目的是看一眼栈顶的元素.
注意:

  • 和pop不同, peek仅仅的看一眼栈顶的元素,** 并不需要将这个元素从栈顶弹出**.
      //peek 
      Stack.prototype.peek =  function() {
        return items[item.length - 1]
      }
  • isEmpty
    isEmpty方法
  1. isEmpty方法用户判断栈中是否有元素.
  2. 实现起来非常简单, 直接判断数组中的元素个数是为0, 为0返回true, 否则返回false
      //isEmpty
      Stack.prototype.isEmpty =  function() {
        return items.length === 0
      }
  • size方法
  1. size方法是获取栈中元素的个数.
  2. 因为我们使用的是数组来作为栈的底层实现的, 所以直接获取数组的长度即可
      //size 
      Stack.prototype.size =  function() {
        return items.length 
      }

完整的代码如下:

    function Stack() {
        // 栈中的属性
      this.items = [];

      //栈中的方法
      //push
      Stack.prototype.push = function(el) {
        items.push(el);
      }

      //pop
      Stack.prototype.pop = function() {
        return items.pop();
      }

      //peek 
      Stack.prototype.peek =  function() {
        return items[item.length - 1]
      }

      //isEmpty
      Stack.prototype.isEmpty =  function() {
        return items.length === 0
      }

      //size 
      Stack.prototype.size =  function() {
        return items.length 
      }
      //toString
      Stack.prototype.toString = function() {
        var res = '';
        for(let i = 0 ; i < this.items.length ; i++) {
          res += this.items[i];
        }
        return res;
      }
    }

3.3 封装方法测试

    var stack = new Stack();
    // 压入元素
    stack.push(1)
    stack.push(23);
    stack.push(245);
    stack.push(7);
    console.log(stack);  // 1 23 245 77
    //删除最后一个
    stack.pop();
    stack.pop();
    console.log(stack);  // 1 23
    //查看第一个元素
    var a = stack.peek();
    console.log(a)  //23
    //isEmpty,判断栈是否为为
    var b = stack.isEmpty();
    console.log(b)  //false
    //返回栈的长度
    var c = stack.size();
    console.log(c) //2

4. 栈结构的应用

4.1 简单应用二进制转十进制

如何实现十进制转二进制?

  • 要把十进制转化成二进制,我们可以将该十进制数字和2整除(二进制是满二进一),直到结果是0为止。

  • 举个例子,把十进制的数字10转化成二进制的数字,过程大概是这样:
    在这里插入图片描述
    代码实现:

    //十进制转二进制
    function dec2bin(num) {
      var stack = new Stack();
      var r;
      while(num > 0) {
        rem = num % 2;
        num = Math.floor(num / 2);
        //将余数压入栈中
        stack.push(rem);
      }
      var binNum = ''
      //
      while(!stack.isEmpty()) {
        binNum += stack.pop();
      }
      return binNum;
    }
    console.log(dec2bin(10)) //1010

4.2 求解迷宫问题

问题描述

给定一个M*N的迷宫图,求一条从指定入口到出口的迷宫路径.假设一个迷宫如下图所示(M = 8 , N = 8),其中每个方块用空白表示通道,黄色表示障碍.
在这里插入图片描述
一般情况下,所求迷宫的路径是简单路径,即在求得迷宫的路径上不会出现同一个方块.一个迷宫的路径可能有多条,这些路径又长又短,此时仅仅用栈求一条路径.

算法设计

对于迷宫中的每个方块,有上、下、左、右4个方块相邻第i行第j列的当前方块的位置记为(i,j),规定上方方块为方位0.并按顺时针方向递增编号。在试探过程中假设按从方位0到方位3的方向查找下一个可走的相邻方块。

求迷官问题就是在一个指定的迷宫中求出从人口到出口的一条路径。 在求解时采用“穷举法”,即从人口出发.按方位右下左上次序试探相邻的方块,一旦找到个可走的相邻方块就继续走下去,并记下所走的方位:若某个方块没有相邻的可走方块,则沿原路退回到前一个方块.换下-个方位再继续试探,直到所有可能的通路都试探完为止。

为了保证在任何位置上都能沿原路退回(称为回溯),需要保存从入口到当前位置的路径上走过的方块,由于回溯的过程是从当前位置退回到前一个方块,体现出后进先出的特点,所以采用栈来保存走过的方块。

  • 如果一个非出口方块(x,y)是可走的,则将它进栈
  • 然后开始按照右下左上的顺序测试他的周围是否是可走的
  • 如果是可走的,则将可走的方块压入栈中,.否则继续探测其他方向的方位
  • 如果可走,压入栈中之后更新坐标(x,y)的值,并且更新改点的值为-1,表示已经探测过,不需要再次探测,继续重复上面的步骤,探测上面是否可走
  • 如果四个方向都不能走,内循环结束,判断栈是否为空,然后将栈顶元素出栈
  • 一直重复重复循环,知道探测的坐标(x,y)的值与(M,N)相等,结束程序

在算法中应保证试探的相邻可走方块不是已走路径,上的方块。如方块(x,y)已进栈,在试探方块(x+1,y)的相邻可走方块时又会试探到方块(x,y)。也就是说,从方块(x,y)出发会试探方块(x+1,y),而从方块(x+1,y)出发又会试探方块(x,y),这样可能会引起死循环为此在一个方块进栈后将对应的mg数组元素值改为-1(标记为不可走的方块).
该迷宫的基本探测过程如下:
在这里插入图片描述

数据组织

为了表示迷宫,我们设置一个数组mg,其中每一个元素代表一个方块的状态,1表示对应方块是障碍物,0表示该方块可通行,为了算法方便,一般在迷宫外围加一堵围墙,均设置为1,表示不可通行.如下:

    var mg = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
              [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
              [1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
              [1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
              [1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
              [1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
              [1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
              [1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
              [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
              [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
            ]

另外用到的栈结构,是一个右行,列,方向组成的三元组,栈类的定义如下:

    function Box(x, y, di) {
      this.x = x;
      this.y = y;
      this.di = di;
    }

方向探测表示
在探测方向的时候,每个方块都有四个方向:
在这里插入图片描述
每一次坐标的变化就(x,y)就会发生变化,即可以设置下面的对象,有x,y两个属性:
在这里插入图片描述

    function direction(incX, incY) {
      this.incX = incX;
      this.incY = incY;
    }
    //该数组为到时候探测方向的检测数组
    var Arr = new Array(4);
    var dir = [[0, 1], [1, 0], [0, -1], [-1, 0]]
    for (let i = 0; i < Arr.length; i++) {
      Arr[i] = new direction(dir[i][0], dir[i][1])
    }

基本过程

    function mgPath(mg, Arr, M, N) {
      var temp = new Box();
      var x, y, di;   //用来保存当前处理单元格的坐标
      var row, col;  //保存下一次处理单元格的坐标
      mg[1][1] = -1;  //当前个子无法走
      temp.x = 1;
      temp.y = 1;
      temp.di = -1;
      var s = new Stack();
      s.push(temp);
      while (!s.isEmpty()) {
        temp = s.pop();
        x = temp.x;
        y = temp.y;
        di = temp.di + 1;
        while (di < 4) {
          row = x + Arr[di].incX;
          col = y + Arr[di].incY;
          //等于0说明当前格子可走,否则di+1换个方向尝试
          if (mg[row][col] === 0) {
            temp.x = x;
            temp.y = y;
            temp.di = di;
            var a = JSON.parse(JSON.stringify(temp))
            s.push(a);
            x = row;
            y = col;
            mg[row][col] = -1;
            if (x === M && y === N) {
              temp.x = M;
              temp.y = N;
              temp.di = di;
              var a = JSON.parse(JSON.stringify(temp))
              //将最后的出口也放入栈中
              s.push(a);
              return ouput(s);
            } else {
              di = 0;
            }
          } else {
            di++;
          }
        }
      }
      console.log('该迷宫没有路')
    }

定义处理输出的函数output,大家可以按照自己想要的方式处理输出

    function ouput(data) {
      var result = [];
      //拿出栈中的所有数据
      while (!data.isEmpty()) {
        var a = data.pop();
        var local = [a.x, a.y]
        result.push(local)
      }
      //逆序输出数组中的数据即为结果
      return result.reverse()
    }

调用函数

    var a = mgPath(mg, Arr, M, N)
    console.log(a)

输出的结果如下:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值