JavaScript数据结构与算法-栈和队列

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014465934/article/details/88864276

1.栈

栈是一种遵从后进先出(LIFO)原则的有序集合。新添加的或待删除的元素都保存在栈的末尾。称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都靠近栈底。

现在通过数组的方法来实现栈,代码如下:

//我们将创建一个类来表示栈
function Stack() {
  //我们需要一种数据结构来保存栈里的元素,可以选择数组
  var 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(){//如果栈内没有任何元素就返回true,否则返回false
    return items.length == 0;
  };
  this.size = function(){//返回栈里的元素个数
    return items.length;
  };
  this.clear = function(){//移除栈里的所有元素
    items = [];
  };
  this.print = function(){//打印
    console.log(items.toString());
  };
  this.toString = function(){
    return items.toString();
  };
}

使用Stack类(栈):

//首先,我们需要初始化Stack类。
let stack = new Stack(); 

//验证一下栈是否为空(输出是true,因为还没有往栈里添加元素)
console.log(stack.isEmpty()); //输出为true

//接下来,往栈里添加一些元素
stack.push(5);
stack.push(8); 

console.log(stack.peek()); //输出8 

//再添加一个元素
stack.push(11);
console.log(stack.size()); //输出3
console.log(stack.isEmpty()); //输出false 

//然后,调用两次pop方法从栈里移除2个元素:
stack.pop();
stack.pop();
console.log(stack.size()); //输出2
stack.print(); //输出[5, 8] 

2.ECMAScript 6 和 Stack 类

用 ES6 语法声明 Stack 类:

class Stack {
 constructor () {
 this.items = []; //{1}
 }
 push(element){
 this.items.push(element);
 }
 //其他方法
} 

我们只是用ES6的简化语法把Stack函数转换成Stack类。这种方法不能像其他语言(Java、C++、C#)一样直接在类里面声明变量,只能在类的构造函数constructor里声明(行{1}),在类的其他函数里用this.nameofVariable就可以引用这个变量。

尽管代码看起来更简洁、更漂亮,变量items却是公共的。
ES6的类是基于原型的。虽然基于原型的类比基于函数的类更节省内存,也更适合创建多个实例,却不能够声明私有属性(变量)或方法。
而且,在这种情况下,我们希望Stack类的用户只能访问暴露给类的方法。否则,就有可能从栈的中间移除元素(因为我们用数组来存储其值),这不是我们希望看到的。

看看ES6语法有没有其他的方法可以创建私有属性。

这个问题打算专门写个笔记。

3.用栈解决问题

从十进制到二进制:

floor() 方法执行的是向下取整计算,它返回的是小于或等于函数参数,并且与之最接近的整数。可以理解为让除法的操作仅返回整数部分。

function divideBy2(decNumber){
	var remStack = new Stack(),
 	rem,
 	binaryString = '';
 
 	while (decNumber > 0){ //{1}
 		rem = Math.floor(decNumber % 2); //{2}
 		remStack.push(rem); //{3}
 		decNumber = Math.floor(decNumber / 2); //{4}
 	}
 	
 	while (!remStack.isEmpty()){ //{5}
 		binaryString += remStack.pop().toString();
 	}
 
 	return binaryString;
} 

在这段代码里,当结果满足和2做整除的条件时(行{1}),我们会获得当前结果和2的余数,放到栈里(行{2}、{3})。然后让结果和2做整除(行{4})。另外请注意:JavaScript有数字类型,但是它不会区分究竟是整数还是浮点数。因此,要使用Math.floor函数让除法的操作仅返回整数部分。最后,用pop方法把栈中的元素都移除,把出栈的元素变成连接成字符串(行{5})。

升级版, 如何将10进制数字转成任意进制数字,代码如下:

function baseConverter(decNumber,base){
  var remStack = new Stack(),
  rem,
  baseString = "",
  digits = "0123456789ABCDEF";	//{6} 

  while(decNumber > 0){
    rem = Math.floor(decNumber % base);
    remStack.push(rem);
    decNumber = Math.floor(decNumber / base);
  }
  while(!remStack.isEmpty()){
    baseString += digits[remStack.pop()];   //{7}
  }
  return baseString;
} 
baseConverter(100345,2) // "11000011111111001"
baseConverter(100345,8) //"303771"
baseConverter(100345,16) // "187F9"   

我们只需要改变一个地方。在将十进制转成二进制时,余数是0或1;在将十进制转成八进制时,余数是0到7之间的数;但是将十进制转成16进制时,余数是0到9之间的数字加上A、B、C、D、E和F(对应10、11、12、13、14和15)。因此,我们需要对栈中的数字做个转化才可以(行{6}和行{7})。

就是digits是一个字符串,我们得到的remStack是一串数字,通过数字下标获得digits中的字母ABCDEF,这就是对栈中的数字做转化。

4.队列

创建类来表示一个队列:

function Queue() {
	var items = [];
  	this.enqueue = function(element){//向队列尾部添加一个(或是多个)元素
  		items.push(element);
  	};
  	this.dequeue = function(){//移除队列的第一个元素,并返回被移除的元素
    	return items.shift();
 	};
  	this.front = function(){//返回队列的第一个元素——最先被添加的,也将是最先被移除的元素。队列不做任			何变动。(不移除元素,只返回元素信息。与stack的peek方法类似)
    	return items[0];
  	};
  	this.isEmpty = function(){//如果队列内没有任何元素就返回true,否则返回false
    	return items.length == 0;
  	};
  	this.clear = function(){//移除队列里的所有元素
    	items = [];
  	};
  	this.size = function(){//返回队列里的元素个数
    	return items.length;
  	};
  	this.print = function(){//打印                                                                                                                                                                                                                             
    	console.log(items.toString());
  	};
 }

使用Queue类:

let queue = new Queue();
console.log(queue.isEmpty()); //输出true 

queue.enqueue("John");
queue.enqueue("Jack"); 

queue.enqueue("Camila");

queue.print();
console.log(queue.size()); //输出3
console.log(queue.isEmpty()); //输出false
queue.dequeue();
queue.dequeue();
queue.print(); 

5.用 ECMAScript 6 语法实现的 Queue 类

和第3章一样,我们也可以用ECMAScript 6语法编写Queue类。在这种方法中,我们要用一个WeakMap来保存私有属性items,并用外层函数(闭包)来封装Queue类。

let Queue2 = (function () {
 	const items = new WeakMap();
 	class Queue2 {
		 constructor () {
			 items.set(this, []);
 		}
 		enqueue(element) {
 		let q = items.get(this);
 			q.push(element);
 		}
 		dequeue() {
 			let q = items.get(this);
 			let r = q.shift();
			return r;
 		}
 		//其他方法
 	}
 	return Queue2;
})();

6.循环队列-击鼓传花

还有另一个修改版的队列实现,就是循环队列。循环队列的一个例子就是击鼓传花游戏(Hot Potato)。在这个游戏中,孩子们围成一个圆圈,把花尽快地传递给旁边的人。某一时刻传花停止,这个时候花在谁手里,谁就退出圆圈结束游戏。重复这个过程,直到只剩一个孩子(胜者)。

function hotPotato(namelist, num){
  var queue = new Queue();
  for(var i = 0; i < namelist.length; i++){
    queue.enqueue(namelist[i]);   //把名字都添加到队列
  }
  var eliminated = '';
  while(queue.size() > 1){
    for(var i = 0; i < num; i++){
      queue.enqueue(queue.dequeue());   //队列开头移除一项,再将其添加到队列末尾,模拟在击鼓传花
    }
    eliminated = queue.dequeue();   //循环固定次数后,把队列首部移除,并返回,这就是淘汰的
    console.log(eliminated+"在游戏中淘汰了。");
  }
  return queue.dequeue();   //循环到queue.size()只有一个时候,这个时候返回的就是胜利者
}
var names = ["a","b","c","d","e"];
var winner = hotPotato(names,7);
console.log("胜利者"+winner);
//c在游戏中淘汰了。
//b在游戏中淘汰了。
//e在游戏中淘汰了。
//d在游戏中淘汰了。
//胜利者a

7.JavaScript任务队列

当我们在浏览器中打开新标签时,就会创建一个任务队列。这是因为每个标签都是单线程处理所有的任务,它被称为事件循环。

猜你喜欢

转载自blog.csdn.net/u014465934/article/details/88864276
今日推荐