栈
1 什么是栈?
栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。栈具有后进先出(LIFO-Last In First Out)的特点。
2 栈的属性/方法
为了记录栈顶元素的位置,同时也为了标记哪里可以加入新元素,使用变量top。
对栈的两种主要操作是将一个元素压入栈和将一个元素弹出栈。入栈:push(),出栈:pop()。
另一个常用的操作是预览栈顶的元素。pop()虽然可以访问栈顶的元素,但是调用该方法后栈顶的元素也从栈中删除了。所以使用peek()方法,仅仅访问而不删除。
length()方法返回栈内元素个数。
empty()方法返回栈是否为空。
clear()方法清空栈内元素。
3 栈的实现
function Stack(){
this.dataStore=[];
this.top=0;
this.push=push;
this.pop=pop;
this.peek=peek;
this.length=length;
this.empty=empty;
this.clear=clear;
}
function push(item){
this.dataStore[this.top++]=item;
}//不要忘了this.top++
function pop(){
return this.dataStore[--this.top];
}
function peek(){
return this.dataStore[this.top-1];
}//体会pop和peek的区别
function clear(){
delete this.dataStore;
this.dataStore=[];
this.top=0;
}
function length(){
return this.top;
}
function empty(){
return this.top===0?true:false;
}
4 栈的应用
4.1 数制转换
算法如下:
- 最低位为n%b,将此位压入栈。
- 使用n/b代替n
- 重复步骤1和2,直到n=0且没有余数。
- 将栈内元素依次弹出,排列得到转换后的数字字符串形式。
此算法之针对基数2-9的情况。
function convertNum(num,base){
var s=new Stack();
do{
s.push(num%base);
num=Math.floor(num/base);
}while(num>0)//全部压栈
var convertedStr='';
while(!s.empty()){
convertedStr+=s.pop();
}
return convertedStr;
}
console.log(convertNum(10,2));//1010
4.2 回文
回文是指这样一种现象:一个单词/短语/数字。从后往前和从前往后写都是一样的。
使用栈可以轻松判断回文,每个字符从左到右压栈,弹出后排列成新的字符串,两者内容相同则是回文。
function IsPalindrome(word){
var s=new Stack();
var word=word.toString();
for(var i=0;i<word.length;i++){
console.log(word[i]);
s.push(word[i]);
}
var newWord='';
while(!s.empty()){
newWord+=s.pop();
}
console.log(word,newWord);
return word==newWord?true:false;
}
console.log(IsPalindrome(1221));//true
console.log(IsPalindrome('hello'));//false
4.3 递归模拟
以阶乘为例
function factorial(n){
var s=new Stack();
do{
s.push(n--);
}while(n>0)
var total=1;
while(!s.empty()){
total*=s.pop();
}
return total;
}
console.log(factorial(5));//120
4.4 练习
4.4.1 括号匹配
栈可以用来判断一个算术表达式中的括号是否匹配。 编写一个函数, 该函数接受一个算术表达式作为参数, 返回括号缺失的位置。 下面是一个括号不匹配的算术表达式的例子: 2.3 + 23 / 12 + (3.14159× 0.24。
function getPos(str){
var s=new Stack();
for(var i=0;i<str.length;i++){
if(str[i]=='('){
s.push(i);
}else if(str[i]==')'){
s.pop();
}
}
console.log('不匹配位置为字符'+s.peek());
}
getPos('2.3 + 23 / 12 + (3.14159× 0.24');//不匹配位置为字符16
- 关键点:检测到(后push的是i,即括号本身的位置。当且仅当有)出现时才会pop,所以剩下的是没有被匹配的。
4.4.2 佩兹糖果盒
现实生活中栈的一个例子是佩兹糖果盒。 想象一下你有一盒佩兹糖果, 里面塞满了红色、 黄色和白色的糖果, 但是你不喜欢黄色的糖果。 使用栈(有可能用到多个栈) 写一段程序, 在不改变盒内其他糖果叠放顺序的基础上, 将黄色糖果移出。
function removeYellow(s){
var otherStack=new Stack();
var yellowStack=new Stack();
var newStack=new Stack();
while(!s.empty()){
if(s.peek()!='yellow'){
otherStack.push(s.pop());
}else{
yellowStack.push(s.pop());
}
}
while(!otherStack.empty()){
newStack.push(otherStack.pop());
}
return newStack;
}
var s=new Stack();
s.push('red');
s.push('orange');
s.push('yellow');
s.push('green');
s.push('blue');
s.push('pink');
s=removeYellow(s);
console.log(s);
- 关键点:
- 原思路:颜色不为黄的时候就将pop的元素加入到新的栈中,最后将该栈中的元素再pop出来给原栈就可以了。问题在于:对颜色为黄的元素也要进行判断处理,否则原栈就永远不会为空,while(!s.isEmpty())也就变成了无限循环,这也是运行程序的时候出现问题的地方。
- 值得注意的是,将原栈的所有元素pop出去后,数组本身并不为空,只是top指针为0,如果此时将筛选出来的元素直接添加到原栈中就会出现问题。所以最后还是开辟了一个新栈,所以函数中用了三个栈。