数组是一种灵活自由的线性结构,栈是一种受限的线性结构。
限制:只允许在一端进行操作,这端为栈顶,另一端为栈底。
实现栈结构一般有基于数组和基于链表两种方式。
函数调用栈
A调用B,B调用C,C调用D。
A最先被压入栈,D最后被压入栈,D执行完后弹出栈,以此类推,A最后出栈。
函数调用栈的称呼由函数调用的执行机制而来。
创建一个基于数组的栈
使用数组来保存栈的元素,但由于栈的LIFO,需要对元素的插入和删除进行限制。
push(element(s)):添加一个(或几个)新元素到栈顶。 pop():移除栈顶的元素,同时返回被移除的元素。 peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)。 isEmpty():如果栈里没有任何元素就返回 true,否则返回 false。 clear():移除栈里的所有元素。 size():返回栈里的元素个数。该方法和数组的 length 属性很类似。class Stack {constructor() {this.item = [];}push(ele) {this.item.push(ele);}pop() {return this.item.pop();}peek() {return this.item[item.length - 1];}isEmpty() {// return !this.item.length;return this.item.length === 0;}clear() {this.item = []}size() {return this.item.length;}}
使用Stack类
const stack = new Stack();console.log(stack.isEmpty()); // truestack.push(5);stack.push(8);console.log(stack.peek()); // 8console.log(stack.size()); // 2stack.pop();console.log(stack.size()); // 1
创建一个基于JavaScript对象的栈
使用数组在查找元素时大部分方法的时间复杂度是O(n),O(n)的意思是需要迭代整个数组直到找到那个元素。
使用对象来存储元素,可以占用较少的内存空间。
下列方法中除了toString() 方法,其他方法复杂度为O(1)。
class Stack {constructor() {this.item = {};this.count = 0;}push(ele) {this.count += 1;this.item[this.count] = ele;}pop() {if(this.isEmpty()) {return undefined;}const res = this.item[this.count];delete this.item[this.count];this.count -= 1;return res;}peek() {if(this.isEmpty()) {return undefined;}return this.item[this.count];}isEmpty() {return !this.count;}clear() {this.item = {};this.count = 0;// clear的另一种写法while(!this.isEmpty()) {this.pop();}}size() {return this.count;}// 基于数组的栈自带toString()方法,此处应手动添加。toString() {let str = "";if (this.isEmpty()) return str;for (const k in this.item) {str = str ? `${str},${this.item[k]}` : this.item[k];}return str;}}
保护数据结构内部元素
在上述创建栈的方法中,我们发现,item和count能被直接访问到。
console.log(Object.getOwnPropertyNames(stack)); // [ 'item', 'count' ]console.log(Object.keys(stack)); // [ 'item', 'count' ]console.log(stack.item); // { '1': 5, '2': 8, '3': 5, '4': 8 }
下划线命名的约定
class Stack {constructor() {// 约定下划线来标记私有属性this._count = 0;this._items = {};} }
ES6: WeakMap实现类
const items = new WeakMap(); // {1}class Stack {constructor () {items.set(this, []); // {2}}push(element){const s = items.get(this); // {3}s.push(element); }pop(){const s =const r =return r;}// 其他方法}items.get(this); s.pop();上面的代码片段解释如下。 行{1},声明一个 WeakMap 类型的变量 items。 行{2},在 constructor 中,以 this(Stack 类自己的引用)为键,把代表栈的数组存入 items。 行{3},从 WeakMap 中取出值,即以 this 为键(行{2}设置的)从 items 中取值。
TS: private修饰符
TypeScript 提供了一个给类属性和方法使用的 private 修饰符。然而,该修饰符只在编译时有用(包括我们在前几章讨论的 TypeScript 类型和错误检测)。在代码被转移完成后,属性同样是公开的。
用栈解决问题
十进制转二进制
function decimalToBinary(decNumber) {const remStack = new Stack();let number = decNumber;let rem;let binaryString = "";while (number > 0) {// {1}rem = Math.floor(number % 2); // {2}remStack.push(rem); // {3}number = Math.floor(number / 2); // {4}}while (!remStack.isEmpty()) {// {5}binaryString += remStack.pop().toString();}return binaryString;}console.log(decimalToBinary(233)); // 11101001console.log(decimalToBinary(10)); // 1010console.log(decimalToBinary(1000)); // 1111101000
进制转换算法
function baseConverter(decNumber, base) {const remStack = new Stack();const digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // {6}let number = decNumber;let rem;let baseString = "";if (!(base >= 2 && base <= 36)) {return "";}while (number > 0) {rem = Math.floor(number % base);remStack.push(rem);number = Math.floor(number / base);}while (!remStack.isEmpty()) {baseString += digits[remStack.pop()]; // {7}}return baseString;}console.log(baseConverter(100345, 2)); // 11000011111111001console.log(baseConverter(100345, 8)); // 303771 6console.log(baseConverter(100345, 16)); // 187F9console.log(baseConverter(100345, 35)); // 2BW0
