一、栈
先进后出,后进先出
1. 封装一个栈类
//封装栈类
class Stack {
constructor() {
// 存放栈中的元素
this.items = [];
}
//栈的相关操作
//1.push():压栈操作,添加新元素到栈顶
//在类中,方法写出来就是直接添加到该类的原型对象上
push(element) {
this.items.push(element);
}
//2.从栈中取出元素,pop在数组中是删除最后一个元素,返回该元素
pop() {
return this.items.pop();
}
//3.查看栈顶元素
peek() {
return this.items[this.items.length - 1];
}
//4.判断栈是否为空
isEmpty() {
return this.items.length === 0;
}
//5.获取栈中元素个数
size() {
return this.items.length;
}
//6.toString方法转字符串
toString() {
return this.items.join('-');
}
}
测试代码:
let stack = new Stack();
stack.push('zzy');
stack.push(18);
stack.push('ht');
console.log(stack);
stack.pop();
console.log(stack.peek()); //18
console.log(stack.isEmpty()); //false
console.log(stack.size());//2
console.log(stack.toString()); //zzy-18
测试结果:
2. 栈的简单应用
用栈实现一个十进制转二进制的函数
//用栈封装一个十进制转二进制的算法
//思路:除以2取余,余数依次入栈,然后依次取出栈顶元素
function dec2bin(decNumber) {
//1.生成一个栈的实例
let stack = new Stack();
//2.循环取余入栈,不确定循环次数,用while
while (decNumber > 0) {
// 2.1.获取余数并放入栈中
stack.push(decNumber % 2);
// 2.2.获取整除后的结果作为下一次运算的数字(floor:向下取整)
decNumber = Math.floor(decNumber / 2);
}
//3.去除栈顶元素,拼接字符串(任何类型和字符串相加都会变成字符串)
let result = '';
while(!stack.isEmpty()) {
result += stack.pop();
}
return result;
}
console.log(dec2bin(100));
console.log(dec2bin(10));
console.log(dec2bin(1000));
测试结果:
二、队列
先进先出,后进后出
1. 封装一个队列类
//队列结构的封装
class Queue {
constructor() {
//属性
this.items = [];
}
//方法
//1.将元素添加到队列中
enqueue(element) {
this.items.push(element);
}
//2.从队列中删除前端元素,并返回该元素
dequeue() {
return this.items.shift();
}
//3.查看前端的元素
front() {
return this.items[0];
}
//4.查看队列是否为空
isEmpty() {
return this.items.length === 0;
}
//5.查看队列中元素的个数
size() {
return this.items.length;
}
//6.toString方法
toString() {
return this.items.join('');
}
}
测试代码:
let queue = new Queue();
queue.enqueue('abc');
queue.enqueue(6);
queue.enqueue(8);
console.log(queue);
queue.dequeue(); //删除abc
console.log(queue);
console.log(queue.front()); //6
console.log(queue.isEmpty()); //false
console.log(queue.size()); //2
console.log(queue.toString()); //'68'
测试结果:
2. 队列的简单应用
利用队列实现击鼓传花
击鼓传花规则:围成一圈数数,比如规定数到5的人淘汰,那么淘汰的人后面的人从1开始数,然后数到5的人再淘汰,以此类推,直到剩下最后一个人,求该人的位置。
思路:围成一圈的话,可以看成一个队列,从第一个人开始数,那么在数到num
之前的人可以依次从队头删除,加入队尾(保持环形结构)。直到被数到的这个人到队头,然后把它删除就行了。
以上循环依次进行,直到队列只剩一个元素,返回该元素和它的位置
//击鼓传花,规定数到几的人淘汰,然后下一个人从1开始数,数到该数字淘汰
//求最后剩下的那个人的位置,传入参数(名字列表,数字)
function passGame(nameList, num) {
//1.定义一个队列,并依次把元素加入队列
let queue = new Queue();
for(let i = 0; i < nameList.length; i++) {
queue.enqueue(nameList[i]);
}
console.log('初始队列元素:',queue.items);
//2.从第一个人开始数数
while(queue.size() > 1) {
//3.1依次把num之前的数字从队头取出,放到队尾
for(let i = 0; i < num - 1; i++) {
queue.enqueue(queue.dequeue());
}
//3.2删除第一个元素(数到num的人)
queue.dequeue();
console.log(`人数变化:`,queue.items);
}
//4.把剩下那个人的名字和位置找出来
let leftBoy = queue.front();
let index = nameList.indexOf(leftBoy);
console.log(`剩下的哥们儿是${leftBoy},他的初始位置是:${index}`);
return index;
}
passGame(['张三','李四','王五','赵六','田七','郭八'], 6);
3.封装优先级队列
(1)这是什么东西?
所谓优先级队列,其实就是有人可以插队。那么封装的时候队列中元素应该是带着一个优先级标识,标识nb的元素就可以往前靠。也就是说需要有两个参数,分别是element(元素)
和priority(优先级标识)
。
(2)封装的逻辑
大概的思路就是
1、先声明一个内部元素类,用来存储(元素,优先级)
两个属性
2、封装优先级队列,继承前面的Queue中的属性和方法(通过super
关键字)
3、重写插入方法。
重写的逻辑:
(1)首先判断是否为空,空就直接队尾插入;
(2)不为空,就要遍历当前队列元素,插入的新值依次比较,如果新值优先级较低,那么就使用splice
插入到当前位置的前面(把其他的顶到后面),同时跳出循环。
(3)如果比较完之后新插入元素优先级最高,那么直接插入到队尾。
(3)封装代码
封装代码如下:
//优先队列内部的元素类
class QueueElement {
constructor(element, priority) {
this.element = element;
this.priority = priority;
}
}
//封装优先级队列
class PriorityQueue extends Queue {
constructor() {
super(); //super关键字调用父类的构造函数
}
//重写enqueue方法
enqueue(element, priority) {
//1.根据传入的元素,创建一个元素对象实例
let queueElement = new QueueElement(element, priority);
//2.1判断队列是否为空,如果为空那么直接push进去
if (this.isEmpty()) {
this.items.push(queueElement);
} else {
//2.2如果不为空,那么比较优先级(这里默认小的优先)
let added = false; //定义一个标识,判断是否已经插入
for (let i = 0; i < this.items.length; i++) {
//3.如果插入的元素优先级更小,那么就在当前位置插入(其他的被挤到后面)
if (queueElement.priority < this.items[i].priority) {
this.items.splice(i, 0, queueElement);
added = true; //插入完成,修改标识
break; //如果插入完成,就跳出循环
}
}
//4.如果遍历结束后,新元素始终最大,那么就添加到队尾
if(!added) {
this.items.push(queueElement);
}
}
}
//其他方法全部继承(转字符串需要小改)
dequeue() {
return super.dequeue(); //super关键字调用父类的函数
}
front() {
return super.front();
}
isEmpty() {
return super.isEmpty();
}
size() {
return super.size();
}
toString() {
let result = "";
for(let item of this.items) {
result += `${item.element}-${item.priority} `;
}
return result;
}
}
测试:
//测试代码
const queue = new PriorityQueue();
queue.enqueue('zzy',10); //参数1为元素,参数2为优先级
queue.enqueue('nba',50);
queue.enqueue('cba',20);
queue.enqueue('dj',40);
queue.enqueue('ht',30);
console.log(queue.items);
console.log(queue.toString()); //zzy-10 cba-20 ht-30 dj-40 nba-50
(4)几个注意点
1、类的继承,super()
直接调用相当于调用的是父类中的constructor
函数,也就是说子类调用super
相当于直接继承父类的属性(不调用super会报错)。
2、类的继承,如果要在子类中调用父类的方法,要使用super.方法名()
3、关于splice
的用法,splice(位置,删除几个,替换的元素)
,splice(1,0,‘Tom’):表示在索引为1的元素前面
插入元素’Tom‘(也可以理解为从索引为1的元素开始删除,删除0个元素,再在索引为1的元素前面添加元素’Tom’);
4、关于splice
的用法,splice(1,1,‘Tom’):表示从索引为1的元素开始删除(包括索引为1的元素),共删除1个元素,并添加元素’Tom’。即把索引为1的元素替换
为元素’Tom’。