zoukankan      html  css  js  c++  java
  • javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例

    栈(Stack)是限定仅在表尾进行插入或删除操作的线性表。表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈。

    栈又称为后进先出(last in first out)的线性表。

    堆栈可以用链表数组两种方式实现,一般为一个堆栈预先分配一个大小固定且较合适的空间并非难事,所以较流行的做法是 Stack 结构下含一个数组。如果空间实在紧张,也可用链表实现,且去掉表头

    栈的链式表示结构图:

    用js数组可以非常简单地实现栈的顺序表示,故这里不赘述。这里主要讲解一下栈的链式表示。

     1 // 找的链式表示
     2 function Stack() {
     3   this.top = null;
     4   this.size = 0;
     5 }
     6 module.exports = Stack;
     7 Stack.prototype = {
     8   constructor: Stack,
     9   push: function (data) {
    10     var node = {
    11       data: data,
    12       next: null
    13     };
    14 
    15     node.next = this.top;
    16     this.top = node;
    17     this.size++;
    18   },
    19   peek: function () {
    20     return this.top === null ?
    21       null :
    22       this.top.data;
    23   },
    24   pop: function () {
    25     if (this.top === null) return null;
    26 
    27     var out = this.top;
    28     this.top = this.top.next;
    29 
    30     if (this.size > 0) this.size--;
    31 
    32     return out.data;
    33   },
    34   clear: function () {
    35     this.top = null;
    36     this.size = 0;
    37   },
    38   displayAll: function () {
    39     if (this.top === null) return null;
    40 
    41     var arr = [];
    42     var current = this.top;
    43 
    44     for (var i = 0, len = this.size; i < len; i++) {
    45       arr[i] = current.data;
    46       current = current.next;
    47     }
    48 
    49     return arr;
    50   }
    51 };
    52 
    53 var stack = new Stack();
    54 
    55 stack.push(1);
    56 stack.push('asd');
    57 
    58 stack.pop();
    59 stack.push({a: 1});
    60 console.log(stack);

    相关单元测试:

     1 describe('stack tests', function(){
     2   var stack = new Stack();
     3 
     4   it('should push into stack', function(){
     5     stack.push(1);
     6     expect(stack.peek()).toBe(1);
     7     stack.push('asd');
     8     expect(stack.peek()).toBe('asd');
     9     expect(stack.size).toBe(2);
    10   });
    11 
    12   it('should pop from stack', function(){
    13     stack.pop();
    14     expect(stack.peek()).toBe(1);
    15     expect(stack.size).toBe(1);
    16     stack.push({a: 1});
    17     expect(stack.peek()).toEqual({a: 1});
    18     expect(stack.size).toBe(2);
    19   });
    20 
    21   it('should be an empty stack', function(){
    22     stack.pop();
    23     expect(stack.peek()).toBe(1);
    24     stack.pop();
    25     expect(stack.peek()).toBe(null);
    26     expect(stack.size).toBe(0);
    27   });
    28 });
    View Code

    堆栈的应用

    示例1:数值进制转换

    公式: N = (N / d) * d + N % d
    N:十进制数值, d:需要转换的进制数

     1 function numTransform(number, rad) {
     2   var s = new Stack();
     3 
     4   while (number) {
     5     s.push(number % rad);
     6     number = parseInt(number / 8, 10);
     7   }
     8 
     9   var arr = [];
    10   while (s.top) {
    11     arr.push(s.pop());
    12   }
    13   console.log(arr.join(''));
    14 }
    15 
    16 numTransform(1348, 8);
    17 numTransform(1348, 2);

    示例2:括号匹配检查

    在算法中设置一个栈,每读入一个括号,若是右括号,则或者使置于栈顶的最急迫的期待得以消解,或者是不合法的情况;若是左括号,则作为一个新的更急迫的期待压入栈中,自然使得原有的在栈中的所有未消解的期待的急迫性都降一级。另外,在算法开始和结束时,栈都应该是空的。

     1 function bracketsMatch(str) {
     2   var stack = new Stack();
     3   var text = '';
     4 
     5   for (var i = 0, len = str.length; i < len; i++) {
     6     var c = str[i];
     7     if (c === '[') {
     8       stack.push(c);
     9     } else if (c === ']') {
    10       if (!stack.top || stack.pop() !== '[') throw new Error('unexpected brackets:' + c);
    11     } else {
    12       text += c;
    13     }
    14   }
    15   console.log(text);
    16 }
    17 
    18 console.log(bracketsMatch('[asd]'));
    19 
    20 function Matcher(left, right) {
    21   this.left = left;
    22   this.right = right;
    23   this.stack = new Stack();
    24 }
    25 Matcher.prototype = {
    26   match: function (str) {
    27     var text = '';
    28 
    29     for (var i = 0, len = str.length; i < len; i++) {
    30       var c = str[i];
    31       if (c === this.left) {
    32         this.stack.push(c);
    33       } else if (c === this.right) {
    34         if (!this.stack.top || this.stack.pop() !== this.left) {
    35           throw new Error('unexpected brackets:' + c);
    36         } else {
    37           text += ',';
    38         }
    39       } else {
    40         text += c;
    41       }
    42     }
    43     console.log(text);
    44     return text;
    45   }
    46 };
    47 var m = new Matcher('{', '}');
    48 m.match('[{123}123');

    示例3:行编辑

    当用户发现刚刚键入的一个字符是错的时,可补进一个退格符“#”,以表示前一个字符无效;如果发现当前键入的行内差错较多或难以补进,则可以键入一个退行符“@”

    ,以表示当前行中的字符均无效。

    为此,可设这个输入缓冲区为一个栈结构,每当从终端接收了一个字符之后先做如下判断:

    如果它既不是"#"也不是"@",则将字符压入栈;

    如果是"#",则从栈顶删去一个字符;

    如果是"@",则清空栈。

     1 function LineEditor(str) {
     2   this.stack = new Stack();
     3   this.str = str || ''
     4 }
     5 LineEditor.prototype = {
     6   getResult: function () {
     7     var stack = this.stack;
     8     var str = this.str;
     9     for (var i = 0, len = str.length; i < len; i++) {
    10       var c = str[i];
    11       switch (c) {
    12         case '#':
    13           stack.pop();
    14           break;
    15         case '@':
    16           stack.clear();
    17           break;
    18         default:
    19           stack.push(c);
    20           break;
    21       }
    22     }
    23 
    24     var result = '';
    25     var current = stack.top;
    26     while (current) {
    27       result = current.data + result;
    28       current = current.next;
    29     }
    30 
    31     return result;
    32   }
    33 };
    34 
    35 var le = new LineEditor('whli##ilr#e(s#*s)
    36     
    outcha@putchar(*s=#++)');
    37 console.log(le.getResult());

    示例4:表达式求值

    表达式求值是程序设计语言编译中的一个最基本问题、它的实现是栈应用的又一个典型例子。这里介绍一种简单直观,广为使用的算法,通常称为“运算符优先法”。

     1 // from: http://wuzhiwei.net/ds_app_stack/
     2 
     3 var prioty = {
     4   "+": 1,
     5   "-": 1,
     6   "%": 2,
     7   "*": 2,
     8   "/": 2,
     9   "^": 3,
    10   "(": 0,
    11   ")": 0,
    12   "`": -1
    13 };
    14 
    15 function doop(op, opn1, opn2) {
    16   switch (op) {
    17     case "+":
    18       return opn1 + opn2;
    19     case "-":
    20       return opn1 - opn2;
    21     case "*":
    22       return opn1 * opn2;
    23     case "/":
    24       return opn1 / opn2;
    25     case "%":
    26       return opn1 % opn2;
    27     case "^":
    28       return Math.pow(opn1, opn2);
    29     default:
    30       return 0;
    31   }
    32 }
    33 
    34 function opcomp(a, b) {
    35   return prioty[a] - prioty[b];
    36 }
    37 
    38 function calInfixExpression(exp) {
    39   var cs = [];
    40   var ns = [];
    41   exp = exp.replace(/s/g, "");
    42   exp += '`';
    43   if (exp[0] === '-') {
    44     exp = "0" + exp;
    45   }
    46   var c;
    47   var op;
    48   var opn1;
    49   var opn2;
    50   for (var i = 0; i < exp.length; ++i) {
    51     c = exp[i];
    52     // 如果是操作符
    53     if (c in prioty) {
    54       // 如果右边不是左括号且操作符栈的栈顶元素优先权比右边大
    55       // 循环遍历进行连续运算
    56       while (c != '(' && cs.length && opcomp(cs[cs.length - 1], c) >= 0) {
    57         // 出栈的操作符
    58         op = cs.pop();
    59         // 如果不是左括号或者右括号,说明是运算符
    60         if (op != '(' && op != ')') {
    61           // 出栈保存数字的栈的两个元素
    62           opn2 = ns.pop();
    63           opn1 = ns.pop();
    64           // 将与操作符运算后的结果保存到栈顶
    65           ns.push(doop(op, opn1, opn2));
    66         }
    67       }
    68       // 如果右边不是右括号,保存到操作符栈中
    69       if (c != ')') cs.push(c);
    70     } else {
    71       // 多位数的数字的情况
    72       while (!(exp[i] in prioty)) {
    73         i++;
    74         c += exp[i];
    75       }
    76       ns.push(parseFloat(c));
    77       i--;
    78     }
    79   }
    80   return ns.length ? ns[0] : NaN;
    81 }
    82 
    83 var exp1 = calInfixExpression('5+3*4/2-2^3+5%2');
    84 console.log(exp1);

    栈与递归调用的实现:

    栈的另一个重要应用是在程序设计语言中实现递归调用。

    递归调用:一个函数(或过程)直接或间接地调用自己本身,简称递归(Recursive)。

    递归是程序设计中的一个强有力的工具。因为递归函数结构清晰,程序易读,正确性很容易得到证明。

    为了使递归调用不至于无终止地进行下去,实际上有效的递归调用函数(或过程)应包括两部分:递推规则(方法),终止条件。

    为保证递归调用正确执行,系统设立一个“递归工作栈”,作为整个递归调用过程期间使用的数据存储区。

    每一层递归包含的信息如:参数、局部变量、上一层的返回地址构成一个“工作记录” 。每进入一层递归,就产生一个新的工作记录压入栈顶;每退出一层递归,就从栈顶弹出一个工作记录。

    从被调函数返回调用函数的一般步骤:

    (1) 若栈为空,则执行正常返回。

    ⑵ 从栈顶弹出一个工作记录。

    ⑶ 将“工作记录”中的参数值、局部变量值赋给相应的变量;读取返回地址。

    ⑷ 将函数值赋给相应的变量。

    (5) 转移到返回地址。

    相关:

    javascript实现数据结构与算法系列

  • 相关阅读:
    75. InputStreamReader和OutputStreamWriter(转换流--字节流转换成字符流)
    74. 编码与解码
    73. PrintStream(打印流)
    72.Properties(配置文件)
    71 Serializable(序列化和反序列化)
    70. SequenceInputStream(文件合并)
    Rabin-Karp指纹字符串查找算法
    优先队列
    版本管理工具svn简介
    php 2038年问题
  • 原文地址:https://www.cnblogs.com/webFrontDev/p/3683325.html
Copyright © 2011-2022 走看看