在JavaScript中实现栈

236 阅读5分钟

简介

栈是一种高效的数据结构,因为数据只能在栈顶添加或删除,所以这样的操作很快,而且我们比较容易实现。

一、对栈的操作

1.后入先出: 栈被称为一种“后入先出”的数据结构。类似于汉诺塔(游戏),区别在于这里我们不用考虑栈内元素的大小。在咖啡厅内的一摞盘子也是现实世界中常见的栈的例子,通常从最上面取盘子,盘子洗净后,也会将盘子摞在最上面。

2.栈顶: 栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。由于栈具有后入先出的特点,所以任何不在栈顶的元素都无法访问。为了得到栈底的元素,我们必须先拿掉上面的元素。

3.入栈出栈: 入栈使用 push方法,出栈使用 pop方法。

4.预览栈顶的元素: 使用peek方法。pop方法虽然可以访问栈顶的元素,但是调用该方法后,栈顶元素也从栈中被永久性地删除了。显然这是不符合我们预期的,而peek方法则只返回栈顶元素,而不会删除它。

5.栈的主要方法: push、pop 和 peek。

6.其他方法和属性:

(1)clear方法将清除栈内所有元素。

(2)length方法将记录我们栈内元素的个数,也可以用来表示栈内是否含有元素。

二、栈的实现

栈的实现,可以采用数组来存储数据。我们首先定义Stack类的构造函数。

(1)dataStore:保存栈内元素,构造函数将其初始化为一个空数组。

(2)top变量:记录栈顶的位置,被构造函数初始化的值表示栈顶对应数组的起始位置。当有元素被压入栈,该变量的值会随之变化。

(3)top变量被构造函数初始化的值可为0或-1,总之就是标识我们的栈里面没有元素了。

初始化值的不同代码的写法会有所不同:特别特别注意++(或--)操作符的位置,它放在this.push(或this.pop或this.peek)的前面,表示先将变量top的值递增(递减)运算再进行当行操作。它放在this.push(或this.pop或this.peek)的前面,表示先进行当行操作再将变量 top 的值递增(递减)运算。两种方式可通过对比来理解记忆,具体如下:

1、被构造函数初始化的值为0时:

1.1.定义 Stack 类的构造函数

function Stack(){
    // 栈的两个属性
    this.dataStore = [];
    this.top = 0; 
    // 栈的五个方法
    this.push = push; 
    this.pop = pop;
    this.peek = peek; 
    this.length = length;
    this.clear = clear;
}

1.2.入栈操作:push方法

function push(element){
    this.dateStore[this.top++] = element;
}

1.3.出栈操作:pop方法

function pop(){
    return this.dateStore[--this.top];
}

1.4.获取栈顶元素操作:peek方法

function peek(){
    return this.dateStore[this.top-1];
}

如果对一个空栈调用 peek方法,结果为 undefined。这是因为栈是空的,这时栈顶没有任何元素

1.5. 获取栈中元素个数:length方法

function length() {
    return this.top;
}

1.6清除栈中所有元素:clear方法

function clear() {
    this.top = 0; 
}

2、当top变量被构造函数初始化的值为-1时:

一些实现细节上不同于top变量为0时,但不影响外部使用。

2.1.定义 Stack 类的构造函数

function Stack(){
    // 栈的两个属性
    this.dataStore = [];
    this.top = -1;
    // 栈的五个方法
    this.push = push; 
    this.pop = pop;
    this.peek = peek; 
    this.length = length;
    this.clear = clear;
}

2.2.入栈操作:push方法

function push(element){
    this.dateStore[++this.top] = element;
}

2.3.出栈操作:pop方法

function pop(){
    return this.dateStore[this.top--];
}

2.4.获取栈顶元素操作:peek方法

function peek(){
    return this.dateStore[this.top];
}

2.5. 获取栈中元素个数:length方法

function length() {
    return this.top + 1;
}

2.6.清除栈中所有元素:clear方法

function clear() {
    this.top = -1;
}

三、测试 Stack类

1、测试 Stack 类的实现

var s = new Stack(); 
s.push("David"); 
s.push("Raymond"); 
s.push("Bryan");
console.log("length: " + s.length()); 
console.log(s.peek());
var popped = s.pop(); 
console.log("The popped element is: " + popped); 
console.log(s.peek());
s.push("Cynthia");
console.log(s.peek());
s.clear();
console.log("length: " + s.length()); 
console.log(s.peek());
s.push("Clayton"); 
console.log(s.peek());

2、测试代码输出结果为:

length: 3 
Bryan 
The popped element is: Bryan 
Raymond 
Cynthia 
length: 0 
undefined 
Clayton 

倒数第二行返回 undefined,这是因为栈被清空后,栈顶就没值了,这时使用 peek() 方法 预览栈顶元素,自然得到 undefined。

四、使用stack 类

1、数制间的相互转换

可以利用栈将数字转化为二至九进制的数字。假设想将数字 n 转换为以 b 为基数 的数字,实现转换的算法如下。

(1) 最高位为 n % b,将此位压入栈。

(2) 使用 n/b 代替 n。

(3) 重复步骤 1 和 2,直到 n 等于 0,且没有余数。

(4) 持续将栈内元素弹出,直到栈为空,依次将这些元素排列,就得到转换后数字的字符 串形式。

注: 此算法只针对基数为 2~9 的情况。

1.1.转化为二至九进制:

function mulBase(num, base) {
    var s = new Stack(); 
    do {
        s.push(num % base); 
        num = Math.floor(num /= base); 
    }while (num > 0);
    var converted = ""; 
    while (s.length() > 0) { 
        converted += s.pop();
    }
    return converted; 
}

1.1.1将数字转换为二进制和八进制

 function mulBase(num, base) {
     var s = new Stack(); 
     do {
         s.push(num % base); 
         num = Math.floor(num /= base); 
     } while (num > 0); 
     var converted = ""; 
     while (s.length() > 0) { 
        converted += s.pop(); 
     }
     return converted; 
 }
 
 var num = 32; 
 var base = 2; 
 var newNum = mulBase(num, base); 
 console.log(num + " converted to base " + base + " is " + newNum); 
 num = 125; 
 base = 8;
 var newNum = mulBase(num, base); 
 console.log(num + " converted to base " + base + " is " + newNum); 

输出为:

32 converted to base 2 is 100000 
125 converted to base 8 is 175

2、回文

回文指的是:一个单词、短语或数字,正序和倒叙都是一样的。比如,单词“dad”、“racecar”就是回文;如果忽略空格和标点符号,下面这个句子也是文,“A man, a plan, a canal: Panama”;数字 1001 也是。

使用栈,可以轻松判断一个字符串是否是回文。当使用push()方法和pop()方法得到的两个字符串相等时,就是一个回文。

2.1.判断给定字符串是否是回文

function isPalindrome(word) {
    var s = new Stack(); 
    for (var i = 0;i < word.length; ++i) {
        s.push(word[i]); 
    }
    var rword = ""; 
    while (s.length() > 0) {
        rword += s.pop(); 
    }
    if (word == rword) { 
        return true; 
    }
    else {
        return false; 
    } 
}
var word = "hello"; 
if (isPalindrome(word)) { 
    console.log(word + " is a palindrome."); 
} else {
    console.log(word + " is not a palindrome."); 
}
word = "racecar" 
if (isPalindrome(word)) {
    console.log(word + " is a palindrome."); 
} else {
    console.log(word + " is not a palindrome."); 
}

程序的输出为:

hello is not a palindrome.
racecar is a palindrome.

3、递归演示

为了演示如何用栈实现递归,考虑一下求阶乘函数的递归定义。 首先看看 5 的阶乘是怎么 定义的:

5! = 5 × 4 × 3 × 2 × 1 = 120

下面是一个递归函数,可以计算任何数字的阶乘:

function factorial(n) { 
    if (n === 0) { 
        return 1; 
    } else {
        return n * factorial(n-1); 
    } 
} 

使用该函数计算 5 的阶乘,返回 120。

3.1.使用栈模拟递归过程

function fact(n) { 
    var s = new Stack();
    while (n > 1) {
        s.push(n--); 
    }
    var product = 1; 
    while (s.length() > 0) { 
        product *= s.pop(); 
    }
    return product; 
}

console.log(factorial(5)); // 显示 120 
console.log(fact(5)); // 显示 120