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

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

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

栈的链式表示结构图:

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

 // 找的链式表示
function Stack() {
this.top = null;
this.size = 0;
}
module.exports = Stack;
Stack.prototype = {
constructor: Stack,
push: function (data) {
var node = {
data: data,
next: null
}; node.next = this.top;
this.top = node;
this.size++;
},
peek: function () {
return this.top === null ?
null :
this.top.data;
},
pop: function () {
if (this.top === null) return null; var out = this.top;
this.top = this.top.next; if (this.size > 0) this.size--; return out.data;
},
clear: function () {
this.top = null;
this.size = 0;
},
displayAll: function () {
if (this.top === null) return null; var arr = [];
var current = this.top; for (var i = 0, len = this.size; i < len; i++) {
arr[i] = current.data;
current = current.next;
} return arr;
}
}; var stack = new Stack(); stack.push(1);
stack.push('asd'); stack.pop();
stack.push({a: 1});
console.log(stack);

相关单元测试:

 describe('stack tests', function(){
var stack = new Stack(); it('should push into stack', function(){
stack.push(1);
expect(stack.peek()).toBe(1);
stack.push('asd');
expect(stack.peek()).toBe('asd');
expect(stack.size).toBe(2);
}); it('should pop from stack', function(){
stack.pop();
expect(stack.peek()).toBe(1);
expect(stack.size).toBe(1);
stack.push({a: 1});
expect(stack.peek()).toEqual({a: 1});
expect(stack.size).toBe(2);
}); it('should be an empty stack', function(){
stack.pop();
expect(stack.peek()).toBe(1);
stack.pop();
expect(stack.peek()).toBe(null);
expect(stack.size).toBe(0);
});
});

堆栈的应用

示例1:数值进制转换

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

 function numTransform(number, rad) {
var s = new Stack(); while (number) {
s.push(number % rad);
number = parseInt(number / 8, 10);
} var arr = [];
while (s.top) {
arr.push(s.pop());
}
console.log(arr.join(''));
} numTransform(1348, 8);
numTransform(1348, 2);

示例2:括号匹配检查

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

 function bracketsMatch(str) {
var stack = new Stack();
var text = ''; for (var i = 0, len = str.length; i < len; i++) {
var c = str[i];
if (c === '[') {
stack.push(c);
} else if (c === ']') {
if (!stack.top || stack.pop() !== '[') throw new Error('unexpected brackets:' + c);
} else {
text += c;
}
}
console.log(text);
} console.log(bracketsMatch('[asd]')); function Matcher(left, right) {
this.left = left;
this.right = right;
this.stack = new Stack();
}
Matcher.prototype = {
match: function (str) {
var text = ''; for (var i = 0, len = str.length; i < len; i++) {
var c = str[i];
if (c === this.left) {
this.stack.push(c);
} else if (c === this.right) {
if (!this.stack.top || this.stack.pop() !== this.left) {
throw new Error('unexpected brackets:' + c);
} else {
text += ',';
}
} else {
text += c;
}
}
console.log(text);
return text;
}
};
var m = new Matcher('{', '}');
m.match('[{123}123');

示例3:行编辑

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

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

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

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

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

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

 function LineEditor(str) {
this.stack = new Stack();
this.str = str || ''
}
LineEditor.prototype = {
getResult: function () {
var stack = this.stack;
var str = this.str;
for (var i = 0, len = str.length; i < len; i++) {
var c = str[i];
switch (c) {
case '#':
stack.pop();
break;
case '@':
stack.clear();
break;
default:
stack.push(c);
break;
}
} var result = '';
var current = stack.top;
while (current) {
result = current.data + result;
current = current.next;
} return result;
}
}; var le = new LineEditor('whli##ilr#e(s#*s)\
\noutcha@putchar(*s=#++)');
console.log(le.getResult());

示例4:表达式求值

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

 // from: http://wuzhiwei.net/ds_app_stack/

 var prioty = {
"+": 1,
"-": 1,
"%": 2,
"*": 2,
"/": 2,
"^": 3,
"(": 0,
")": 0,
"`": -1
}; function doop(op, opn1, opn2) {
switch (op) {
case "+":
return opn1 + opn2;
case "-":
return opn1 - opn2;
case "*":
return opn1 * opn2;
case "/":
return opn1 / opn2;
case "%":
return opn1 % opn2;
case "^":
return Math.pow(opn1, opn2);
default:
return 0;
}
} function opcomp(a, b) {
return prioty[a] - prioty[b];
} function calInfixExpression(exp) {
var cs = [];
var ns = [];
exp = exp.replace(/\s/g, "");
exp += '`';
if (exp[0] === '-') {
exp = "0" + exp;
}
var c;
var op;
var opn1;
var opn2;
for (var i = 0; i < exp.length; ++i) {
c = exp[i];
// 如果是操作符
if (c in prioty) {
// 如果右边不是左括号且操作符栈的栈顶元素优先权比右边大
// 循环遍历进行连续运算
while (c != '(' && cs.length && opcomp(cs[cs.length - 1], c) >= 0) {
// 出栈的操作符
op = cs.pop();
// 如果不是左括号或者右括号,说明是运算符
if (op != '(' && op != ')') {
// 出栈保存数字的栈的两个元素
opn2 = ns.pop();
opn1 = ns.pop();
// 将与操作符运算后的结果保存到栈顶
ns.push(doop(op, opn1, opn2));
}
}
// 如果右边不是右括号,保存到操作符栈中
if (c != ')') cs.push(c);
} else {
// 多位数的数字的情况
while (!(exp[i] in prioty)) {
i++;
c += exp[i];
}
ns.push(parseFloat(c));
i--;
}
}
return ns.length ? ns[0] : NaN;
} var exp1 = calInfixExpression('5+3*4/2-2^3+5%2');
console.log(exp1);

栈与递归调用的实现:

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

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

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

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

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

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

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

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

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

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

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

(5) 转移到返回地址。

相关:

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

javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例的更多相关文章

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

    1.线性表(Linear list) 线性表--简单示例及线性表的顺序表示和实现 线性表--线性链表(链式存储结构) 线性表的静态单链表存储结构 循环链表与双向链表 功能完整的线性链表 线性链表的例子 ...

  2. javascript实现数据结构与算法系列:循环链表与双向链表

    循环链表(circular linked list) 是另一种形式的链式存储结构.它的特点是表中最后一个结点的指针域指向头结点,整个表形成一个环. 循环链表的操作和线性链表基本一致,仅有细微差别. w ...

  3. javascript实现数据结构与算法系列:功能完整的线性链表

    由于链表在空间的合理利用上和插入,删除时不需要移动等的有点,因此在很多场合下,它是线性表的首选存储结构.然而,它也存在着实现某些基本操作,如求线性表长度时不如顺序存储结构的缺点:另一方面,由于在链表中 ...

  4. javascript实现数据结构与算法系列:线性表的静态单链表存储结构

    有时可借用一维数组来描述线性链表,这就是线性表的静态单链表存储结构. 在静态链表中,数组的一个分量表示一个结点,同时用游标(cur)代替指针指示结点在数组中的相对位置.数组的第0分量可看成头结点,其指 ...

  5. javascript实现数据结构与算法系列:队列 -- 链队列和循环队列实现及示例

    1 队列的基本概念 队列(Queue):也是运算受限的线性表.是一种先进先出(First In First Out ,简称FIFO)的线性表.只允许在表的一端进行插入,而在另一端进行删除. 队首(fr ...

  6. JavaScript 版数据结构与算法(二)队列

    今天,我们要讲的是数据结构与算法中的队列. 队列简介 队列是什么?队列是一种先进先出(FIFO)的数据结构.队列有什么用呢?队列通常用来描述算法或生活中的一些先进先出的场景,比如: 在图的广度优先遍历 ...

  7. 数据结构与算法系列2 线性表 链表的分类+使用java实现链表+链表源码详解

    数据结构与算法系列2.2 线性表 什么是链表? 链表是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表的链接次序实现的一系列节点组成,节点可以在运行时动态生成,每个节点包括两个 ...

  8. C#数据结构与算法系列(一):介绍

    1.介绍 数据结构:是指相互之间存在一种或多种特定关系的数据元素的集合用计算机存储.组织数据的方式.数据结构分别为逻辑结构.(存储)物理结构和数据的运算三个部分. 数据结构包括:线性结构和非线性结构. ...

  9. 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解

    数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...

随机推荐

  1. .Net异步编程之Async与Await的使用

    参考教程:http://www.cnblogs.com/x-xk/archive/2013/06/05/3118005.html http://www.cnblogs.com/tdws/p/56790 ...

  2. 【代码】ini 文件读取工具类

    using System; using System.Runtime.InteropServices; using System.Text; namespace hrattendance.Common ...

  3. 重定向语句Response.Redirect()方法与Response.RedirectPermanent()对搜索引擎页面排名的影响

    在ASP.NET中,开发人员经常使用Response.Redirect()方法,用编程的手法,将对老的URL的请求转到新的URL上.但许多开发人员没有意识到的是,Response.Redirect() ...

  4. Python脚本控制的WebDriver 常用操作 <十> 层级定位

    下面将使用WebDriver来模拟操作一个层级定位元素的操作 测试用例场景 在实际的项目测试中,经常会有这样的需求:页面上有很多个属性基本相同的元素,现在需要具体定位到其中的一个.由于属性基本相当,所 ...

  5. 第一个android应用程序

    首先打开Eclipse和一个AVD.在Eclipse中选择File→New→Project→Android→Android Application Project 点击Next,按照下图所示填写 注: ...

  6. eth0: error fetching interface information: Device not found

    转载,原文出处:http://zh888.blog.51cto.com/1684752/775447 亲测有效,感谢作者!!! ----------------------------分割线----- ...

  7. Oracle数据迁移至MySQL

    ORACLE DB: 11.2.0.3.0 MYSQL DB: 5.5.14 因项目需求,需要将ORACLE生产中数据迁移至MYSQL数据库中作为初始数据,方法有如下几种: 1.ORACLE OGG ...

  8. C# ArrayList的用法总结

    C# ArrayList的用法总结 System.Collections.ArrayList类是一个特殊的数组.通过添加和删除元素,就可以动态改变数组的长度. 一.优点 1. 支持自动改变大小的功能 ...

  9. 使用angular封装echarts

    Echarts是一个开源的图表组件,图表比较丰富,工作中需要用到它来搭建一个数据展示系统.但是系统原有的框架是基于angular的,而echarts是基于原生js的,如果直接使用的话就丢失了angul ...

  10. 点击TableView中某行进入下一级界面(Swift)

    TableView这个控件在iOS的开发中非常的常见,他可以较好的展示一个层级结构.这里主要介绍,在点击某个条目的时候,如何进行跳转的下一个界面.以下是官方的关于这个跳转如何去实现,和如何去传递数据的 ...