栈
栈是一种遵从后进先出(LIFO)原则的有序集合,新添加或待删除的元素都保存在栈的同一端,称为栈顶,另一端就叫栈底
栈也被用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录(浏览器的返回按钮)
1.创建一个基于数组的栈
class Stack {
constructor() {
this.items = []
}
}
·push(element(s)) 添加一个(或几个)元素到栈顶
·pop() 移除栈顶的元素,同时返回被移除的元素
·peek() 返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)
·isEmpty() 如果栈里没有任何元素就返回true,否则返回false
·clear() 移除栈里所有元素
·size() 返回栈里元素的个数。
1.向栈添加元素
push(element) {
this.items.push(element)
}
2.从栈移除元素
pop() {
return this.items.pop()
}
3.查看栈顶元素
peek() {
return this.items[this.items.length - 1]
}
4.检查栈是否为空
isEmpty() {
return this.items.length === 0
}
5.清空栈元素
clear() {
this.items = []
}
6.使用Stack类
const stack = new Stack()
console.log(stack.isEmpty(), 'isEmpty')
stack.push(1)
stack.push(2)
stack.push(3)
console.log(stack.peek(), 'peek')
console.log(stack.size(), 'size')
console.log(stack.pop(), 'pop')
2.创建一个基于JavaScript对象的Stack类
在使用数组时,大部分的方法的时间复杂度是O(n)。O(n)的意思是,需要迭代震哥哥数组直到找到要找到的那个元素,在最坏的情况下需要迭代数组的所有位置。另外,数组是元素的一个有序集合,为了保证元素排列有序,它会占用更多的内存空间。
class Stack {
constructor() {
this.count = 0;
this.items = {};
}
}
在这个版本的Stack类中,将使用一个count属性来帮助记录栈的大小(也能帮助从数据结构中添加和删除元素)
1.向栈中插入元素
push(element) {
this.items[this.count] = element
this.count ++
}
2.验证一个栈是否为空和它的大小
size() {
return this.count
}
isEmpty() {
return this.count === 0
}
3.从栈中弹出元素
pop() {
if(this.isEmpty()) {
return undefined;
}
this.count --;
const result = this.items[this.count];
delete this.items[this.count];
return result;
}
4.查看栈顶的值并将栈清空
peek() {
if(this.isEmpty()) {
return undefined;
}
return this.items[this.count - 1]
}
clear() {
this.count = 0;
this.items = {}
}
5.创建toString方法
toString() {
if(this.isEmpty()) {
return ''
}
let objString = `${this.items[0]}`
for(let i = 1; i < this.count; i++) {
objString = `${objString}${this.items[i]}`
}
return objString
}
除了toString方法,其他方法的时间复杂度均为O(1),代表可以直接找到目标元素并对其进行操作
3.保护数据结构内部元素
ES2015类是基于原型的,尽管基于原型的类能节省内存空间并在扩展方面优于函数的类,但这种方式不能声明私有属性(变量)或方法。下面看看其他使用JavaScript来实现私有属性的方法
1.下划线命名约定
class Stack {
constructor() {
this._count = 0;
this._items = {};
}
}
下划线命名约定就是在树形名称之前加上一个下划线,不过这种方式只是一种约定,并不能保护数据。
2.用ES2015的限定作用域Symbol实现类
ES2015新增了一种叫做Symbol的基本类型,它是不可变的,可以用作对象的属性
const _items = Symbol('stackItems');
class Stack {
constructor() {
this[_items] = []
}
push(element) {
this[_items].push(element)
}
pop() {
return this[_items].pop()
}
peek() {
return this[_items][this[_items].length - 1]
}
isEmpty() {
return this[_items].length === 0
}
size() {
return this[_items].length
}
cleart() {
this[_items] = []
}
}
这种方法创建了一个加的私有属性,因为ES2015新增的Object.getOwnPropertySymbols方法能够获取到类里卖弄生命的所有Symbols属性
const stack = new Stack();
stack.push(2);
stack.push(3);
stack.push(4);
let objectSymbols = Object.getOwnPropertySymbols(stack);
console.log(objectSymbols.length); // 1
console.log(objectSymbols); // [ Symbol(stackItems) ]
console.log(objectSymbols[0]); // Symbol(stackItems)
stack[objectSymbols[0]].push(5);
stack.print(); // 2,3,4,5
从以上代码可以看到,访问stack[objectSymbols[0]]是可以得到_items的,并且,_items是一个数组,可以进行任意的数组操作。
3.用ES2015的WeakMap实现类
有一种数据类型可以确保属性是私有的,就是WeakMap。WeapMap可以存储键值对,其中键是对象,值可以是任意数据类型。
const items = new WeakMap();
class Stack {
constructor() {
items.set(this, [])
}
push(element) {
const s = items.get(this);
s.push(element);
}
pop() {
const s = items.get(this);
const r = s.pop();
return r;
}
peek() {
const s = items.get(this);
return s[s.length - 1]
}
isEmpty() {
const s = items.get(this);
return s.length === 0
}
size() {
const s = items.get(this);
return s.length;
}
clear() {
const s = items.get(this);
while(s.length) {
s.pop()
}
}
}
const stack = new Stack()
console.log(stack.peek(), 'peek')
stack.push(2);
stack.push(4);
console.log(stack.isEmpty(), 'isEmpty');
console.log(stack.size(), 'size');
stack.clear();
console.log(stack.size())
items在Stack类里是真正的私有属性,采用这种方法,代码的可读性不强,而且在扩展该类时无法继承私有属性。
4.用栈解决问题
栈的实际应用非常广泛。在回溯问题中,它可以存储访问过的任务或路径、撤销的操作。Java和C#用栈来存储变量和方法调用,特别是处理递归算法时,有可能抛出一个栈溢出异常
从十进制到二进制
function decimalToBinary(decNumber) {
const remStack = new Stack();
let str = '';
let rem;
let number = decNumber;
while(number > 0) {
rem = number % 2;
remStack.push(rem)
number = Math.floor(number / 2);
}
while(!remStack.isEmpty()) {
str = `${str}${remStack.pop()}`
}
return str
}
console.log(decimalToBinary(8));
console.log(decimalToBinary(7));
console.log(decimalToBinary(1023));
进制转换算法
function baseConverter(decNumber, base) {
const remStack = new Stack();
const diggits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let number = decNumber;
let rem;
let str = '';
if(base < 2 || base > 36) {
return ''
}
while(number > 0) {
rem = number % base;
remStack.push(diggits[rem]);
number = Math.floor(number / base)
}
while(!remStack.isEmpty()) {
str = `${str}${remStack.pop()}`
}
return str;
}
console.log(baseConverter(100, 32));
console.log(baseConverter(10231, 32));