JavaScript 数据结构学习笔记 (二) 栈与队列
栈
什么是栈
- 我们在学习严老的《数据结构》时,知道栈是一种遵循 后进先出(LIFO) 原则的有序集合;新加入的或者等待被删除的元素都保存在栈的同一端,称之为栈顶,另一端就叫做栈底。
- 生活中有些例子就很符合栈这种数据结构,例如叠盘子。
- 栈更是编译器实现的重要的数据结构
创建栈
- 我们知道,js中的数组非常灵活,它也拥有非常多的方法,用来实现栈和队列这样的结构非常简易。
- 代码如下:
function Stack() {
let items = [];
// 向栈顶添加元素
this.Push = function (element) {
items.push(element);
}
//从栈顶移除元素并返回该元素
this.Pop = function () {
return items.pop();
}
//返回栈顶元素,但是并不删除它。
this.Peek = function () {
return items[items.length-1];
}
//判断栈是否为空
this.IsEmpty = function () {
return items.length == 0;
}
//返回栈的长度
this.Size = function () {
return items.length;
}
//栈置空
this.Clear = function () {
items = [];
}
//打印栈
this.Print = function () {
console.log(items.toString());
}
}
ES6改进function Stack
- 我们知道,原生js只能仿照类的行为。所以在上文的代码中,我们声明了一个私有的items变量,它只能被Stack函数访问,这个方法会为每一个实例都创建一个item变量的副本。所以,当我们创建多个实例的时候,就会造成内存损耗冗长,那么就不太合适了。
ES6的限定作用域Symbol()实现类
先来看一下代码:
let _items = Symbol();
class Stack {
constructor(){
this[_items] = [];
}
push(ele){
this[_items].push(ele);
}
pop(){
return this[_items].pop();
}
peek(){
return this[_items][this[_items].length-1];
}
IsEmpty() {
return this[_items].length == 0;
}
Size() {
return this[_items].length;
}
Clear() {
this[_items] = [];
}
Print() {
console.log(this[_items].toString());
}
toString(){
return this[_items].toString();
};
}
- 可以看到,通过ES6语法,我们把Stack函数转化成了Stack类。但js于C++,Java不同,他不能直接在类里声明变量。需要在类的构造函数
constructor()里声明,在类的其他函数里通过this.变量名来引用该变量。 - 尽管如此,items仍是公共的,无法声明私有变量或者方法,于是我们采用Symbol。
- Symbol作为ES6新加入的基本类型,他是不可变的。
- 在上面的代码段中,我们用Symbol类型声明了变量_items,在
constructor里初始化它的值,如果要访问它,只需要把所有的this.变量名换成[this.变量名]就行了
队列
什么是队列
- 与栈很像,队列是一种遵循 先进先出(FIFO) 原则的一组有序集合,队列在队尾添加元素,在队头移除元素
- 生活中最典型的就是排队了,所以才叫queue嘛哈哈
创建队列
上面在栈的创建时已经介绍了ES6的Symbol方法,现在我们来使用WeakMap来保存私有属性items,并用闭包函数来封装Queue类
- 代码如下:
let Queue2 = (function(){
const items = new WeakMap();
class Queue2 {
constructor(){
items.set(this,[]);
}
enqueue(ele){
let q = items.get(this);
q.push(ele);
}
dequeue(){
let q = items.get(this);
let r = q.shift();
return r;
}
front(){
let q = items.get(this);
let r = q[0];
return r;
}
isEmpty = function(){
let q = items.get(this);
let r = q.length;
return r == 0;
}
size = function(){
let q = items.get(this);
return q.length;
}
print = function(){
let q = items.get(this);
console.log(q.toString());
}
}
return Queue2;
})();
let queue2 = new Queue2();
console.log(queue2.isEmpty());
queue2.enqueue("John");
queue2.enqueue("Jack");
queue2.enqueue("camlls");
queue2.print();//John,Jack,camlls
console.log(queue2.size());//3
console.log(queue2.isEmpty());//false
- 之所以用这样的方式来实现,是希望尽可能的向面向对象的原则上靠,即只给用户我们想让他们用到的。
优先队列
优先队列的原则:
- [1] 如果队列为空,直接将元素入列。
- [2] 比较该元素与其他元素的优先级,当找到一个比该元素的priority值大(优先级更低)的项时,把它插到新元素之前
- [3] 如果优先级相同,则先进先出。
优先队列的创建:
-
创建优先队列有两种方法:
1.设置优先级,在正确的位置添加元素 2.用入列操作添加元素,然后按照优先级移除它们 -
我选择使用第一种办法:
function PriorityQueue(){
let items = [];
function QueueElement (ele,priority){
this.ele = ele;
this.priority = priority;
}
this.enqueue = function(ele,priority){
let queueElement = new QueueElement(ele,priority);
let added = false;
for(let i=0;i<items.length;i++){
if(queueElement.priority<items[i].priority){
items.splice(i,0,queueElement);
added=false;
break;
}
}
if(!added){
items.push(queueElement);
}
}
this.print = function(){
for(let i=0;i<items.length;i++){
console.log(`${items[i].ele} - ${items[i].priority}`);
}
}
this.dequeue = function(){
return items.shift();
}
this.front = function(){
return items[0];
}
this.isEmpty = function(){
return items.length == 0;
}
this.size = function(){
return items.length;
}
}
let pQ = new PriorityQueue();
pQ.enqueue("John",2);
pQ.enqueue("Jack",1);
pQ.enqueue("Camila",1);
pQ.print();
结果:
循环队列-击鼓传花
- 循环队列的原理其实很简单:就是队头出来了又回到队尾去排队
function hotPotato (nameList, num){
let queue = new Queue();
for (let i=0;i<nameList.length;i++){
queue.enqueue(nameList[i]);
}
let eliminated = '';
while (queue.size()>1) {
for (let i=0;i<num;i++){
//队头出来对尾插
queue.enqueue(queue.dequeue());
}
eliminated = queue.dequeue();
console.log(eliminated + '在击鼓传花中被淘汰。');
}
return queue.dequeue();
}
let names = ['John','Jack','Camila','Ingrid','Carl'];
let winner = hotPotato(names,7);
console.log('the winner is'+winner);
- 结果如下:
小结
可以看到,基于js数组如此灵活的方法以及ES6语法的类的实现,像栈和队列这样稍微基础的数据结构类型可以相对简易的实现,也容易我们理解