目录
一、栈结构(Stack)
1.简介
编辑
程序中的栈结构:
2.栈常见的操作:
1.封装栈类
2.栈结构的简单应用:
二、队列结构(Queue)
1.队列简介
队列的应用:
队列类的实现:
队列的常见操作:
2.封装队列类
代码实现
测试代码
3.队列的应用
代码实现
三、优先队列
1.优先级队列的实现
代码实现:
测试代码:
2.数组splice用法
3.数组的push
数组是一个线性结构,并且可以在数组的任意位置插入和删除元素。而栈和队列就是比较常见的受限的线性结构。如下图所示:
栈的特点为先进后出,后进先出(LIFO:last in first out)。
函数调用栈:A(B(C(D()))):即A函数中调用B,B调用C,C调用D;在A执行的过程中会将A压入栈,随后B执行时B也被压入栈,函数C和D执行时也会被压入栈。所以当前栈的顺序为:A->B->C->D(栈顶);函数D执行完之后,会弹出栈被释放,弹出栈的顺序为D->C->B->A;
递归:为什么没有停止条件的递归会造成栈溢出?比如函数A为递归函数,不断地调用自己(因为函数还没有执行完,不会把函数弹出栈),不停地把相同的函数A压入栈,最后造成栈溢出(Stack Overfloat)
3.练习:题目:有6个元素6,5,4,3,2,1按顺序进栈,问下列哪一个不是合法的出栈顺序?
题目所说的按顺序进栈指的不是一次性全部进栈,而是有进有出,进栈顺序为6 -> 5 -> 4 -> 3 -> 2 -> 1。
解析:
- A答案:65进栈,5出栈,4进栈出栈,3进栈出栈,6出栈,21进栈,1出栈,2出栈(整体入栈顺序符合654321);
- B答案:654进栈,4出栈,5出栈,3进栈出栈,2进栈出栈,1进栈出栈,6出栈(整体的入栈顺序符合654321);
- C答案:6543进栈,3出栈,4出栈,之后应该5出栈而不是6,所以错误;
- D答案:65432进栈,2出栈,3出栈,4出栈,1进栈出栈,5出栈,6出栈。符合入栈顺序;
- push(element):添加一个新元素到栈顶位置;
- pop():移除栈顶的元素,同时返回被移除的元素;
- peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它);
- isEmpty():如果栈里没有任何元素就返回true,否则返回false;
- size():返回栈里的元素个数。这个方法和数组的length属性类似;
- toString():将栈结构的内容以字符串的形式返回。
代码实现
// 封装栈类function Stack(){// 栈中的属性this.items =[]// 栈的相关操作// 1.push():将元素压入栈//方式一(不推荐):给对象添加方法,其他对象不能复用// this.push = () => {// }//方式二(推荐):给Stack类添加方法,能够多对象复用Stack.prototype.push = function(element) {// 利用数组item的push方法实现Stack类的pop方法this.items.push(element)}// 2.pop():从栈中取出元素Stack.prototype.pop = () => {// 利用数组item的pop方法实现Stack类的pop方法return this.items.pop()}// 3.peek():查看一下栈顶元素Stack.prototype.peek = () => {return this.items[this.items.length - 1]}// 4.isEmpty():判断栈是否为空Stack.prototype.isEmpty = () => {// 两个小时的教训啊不是this.length(不是Stack对象的length,Stack类没有length属性啊),而是 Stack类中定义的数组items才有length属性呀return this.items.length == 0 }// 5.size():获取栈中元素的个数Stack.prototype.size = () => {return this.items.length}// 6.toString():以字符串形式输出栈内数据Stack.prototype.toString = () => {//希望输出的形式:20 10 12 8 7let resultString = ''for (let i of this.items){resultString += i + ' '}return resultString}}
测试代码:
// 栈的使用let s = new Stack()s.push(20)s.push(10)s.push(100)s.push(77)console.log(s) //65console.log(s.pop()); //68console.log(s.pop()); //69console.log(s.peek()); //71console.log(s.isEmpty()); //72console.log(s.size()); //74console.log(s.toString()); //75
利用栈结构的特点封装十进至转换为二进至的函数:
//简单应用://封装函数:将十进制转成二进制(十转二的运算最后倒叙取余的特点符合栈'先进后出')let dec2bin = decNumber => {//1.定义一个栈对象,保存余数var stack = new Stack()// 2.循环操作while(decNumber > 0){// 2.1.获取余数并放入栈中stack.push(decNumber % 2)// 2.2.获取整除后的结果作为下一次运算的数字(floor:向下取整)decNumber = Math.floor(decNumber / 2)}// 3.从栈中取出0和1let binaryString = '';let a = stack.items.lengthwhile(stack.items.length != 0){binaryString += stack.pop();}return binaryString;}//测试代码console.log(dec2bin(10)); //103console.log(dec2bin(100)); //104console.log(dec2bin(1000)); //105
队列是是一种受限的线性表,特点为先进先出(FIFO:first in first out)。
- 受限之处在于它只允许在表的前端(front)进行删除操作;
- 在表的后端(rear)进行插入操作;
相当于排队买票,先来的先买票,后来的后买票。
队列的实现和栈一样,有两种方案:
- enqueue(element):向队列尾部添加一个(或多个)新的项;
- dequeue():移除队列的第一(即排在队列最前面的)项,并返回被移除的元素;
- front():返回队列中的第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息与Stack类的peek方法非常类似);
- isEmpty():如果队列中不包含任何元素,返回true,否则返回false;
- size():返回队列包含的元素个数,与数组的length属性类似;
- toString():将队列中的内容,转成字符串形式;
// 基于数组封装队列类function Queue() {// 属性this.items = []// 方法// 1.enqueue():将元素加入到队列中Queue.prototype.enqueue = element => {this.items.push(element)}// 2.dequeue():从队列中删除前端元素Queue.prototype.dequeue = () => {return this.items.shift()}// 3.front():查看前端的元素Queue.prototype.front = () => {return this.items[0]}// 4.isEmpty:查看队列是否为空Queue.prototype.isEmpty = () => {return this.items.length == 0;}// 5.size():查看队列中元素的个数Queue.prototype.size = () => {return this.items.length}// 6.toString():将队列中元素以字符串形式输出Queue.prototype.toString = () => {let resultString = ''for (let i of this.items){resultString += i + ' '}return resultString}}
// 创建队列let queue = new Queue()// 将元素加入到队列中queue.enqueue('a')queue.enqueue('b')queue.enqueue('c')queue.enqueue('d')console.log(queue); //58// 从队列中删除元素queue.dequeue()console.log(queue); //62queue.dequeue()console.log(queue); //64//frontconsole.log(queue.front()); //67// 验证其他方法console.log(queue.isEmpty()); //70console.log(queue.size()); //71console.log(queue.toString()); //72
使用队列实现小游戏:击鼓传花,传入一组数据和设定的数字num,循环遍历数组内元素,遍历到的元素为指定数字num时将该元素删除,直至数组剩下一个元素。
// 队列应用:面试题:击鼓传花let passGame = (nameList, num) => {//1.创建队列结构let queue = new Queue()//2.将所有人依次加入队列// 这是ES6的for循环写法,i相当于nameList[i]for (let i of nameList) {queue.enqueue(i)}// 3.开始数数while (queue.size() > 1) {//队列中只剩1个人就停止数数// 不是num的时候,重新加入队列末尾// 是num的时候,将其从队列中删除// 3.1.num数字之前的人重新放入队列的末尾(把队列前面删除的加到队列最后)for (let i = 0; i < num - 1; i++) {// queue.dequeue()将从队列前面取出来的数据放入队列尾部queue.enqueue(queue.dequeue())}// 3.2.num对应这个人,直接从队列中删除/*思路是这样的,由于队列没有像数组一样的下标值不能直接取到某一元素,所以采用,把num前面的num-1个元素先删除后添加到队列末尾,这样第num个元素就排到了队列的最前面,可以直接使用dequeue方法进行删除*/queue.dequeue()}//4.获取剩下的那个人console.log(queue.size()); //104let endName = queue.front()console.log('最终剩下的人:' + endName); //106 return nameList.indexOf(endName);}//5.测试击鼓传花let names = ['lily', 'lucy', 'Tom', 'Lilei', 'Tony']console.log(passGame(names, 3));
优先级队列主要考虑的问题为:
- 每个元素不再只是一个数据,还包含数据的优先级;
- 在添加数据过程中,根据优先级放入到正确位置;
// 封装优先级队列function PriorityQueue() {//内部类:在类里面再封装一个类;表示带优先级的数据function QueueElement(element, priority) {this.element = element;this.priority = priority;} // 封装属性this.items = []// 1.实现按照优先级插入方法PriorityQueue.prototype.enqueue = (element, priority) => {// 1.1.创建QueueElement对象let queueElement = new QueueElement(element, priority)// 1.2.判断队列是否为空if(this.items.length == 0){this.items.push(queueElement)}else{// 定义一个变量记录是否成功添加了新元素let added = falsefor(let i of this.items){// 让新插入的元素与原有元素进行优先级比较(priority越小,优先级越大)if(queueElement.priority < i.priority){this.items.splice(i, 0, queueElement)added = true// 新元素已经找到插入位置了可以使用break停止循环break}}// 新元素没有成功插入,就把它放在队列的最前面if(!added){this.items.push(queueElement)}}}// 2.dequeue():从队列中删除前端元素PriorityQueue.prototype.dequeue = () => {return this.items.shift()}// 3.front():查看前端的元素PriorityQueue.prototype.front = () => {return this.items[0]}// 4.isEmpty():查看队列是否为空PriorityQueue.prototype.isEmpty = () => {return this.items.length == 0;}// 5.size():查看队列中元素的个数PriorityQueue.prototype.size = () => {return this.items.length}// 6.toString():以字符串形式输出队列中的元素PriorityQueue.prototype.toString = () => {let resultString = ''for (let i of this.items){resultString += i.element + '-' + i.priority + ' '}return resultString}}
// 测试代码let pq = new PriorityQueue();pq.enqueue('Tom',111);pq.enqueue('Hellen',200);pq.enqueue('Mary',30);pq.enqueue('Gogo',27);// 打印修改过后的优先队列对象console.log(pq);
splice(1,0,'Tom'):表示在索引为1的元素前面插入元素’Tom‘(也可以理解为从索引为1的元素开始删除,删除0个元素,再在索引为1的元素前面添加元素'Tom');
splice(1,1,'Tom'):表示从索引为1的元素开始删除(包括索引为1的元素),共删除1个元素,并添加元素'Tom'。即把索引为1的元素替换为元素'Tom'。
- 数组:在数组[0,1,2]中,pop(3),结果为[0,1,2,3];
- 栈:执行pop(0),pop(1),pop(2),pop(3),从栈底到栈顶的元素分别为:0,1,2,3;如果看成数组,可写为[0,1,2,3],但是索引为3的元素3其实是栈顶元素;所以说栈的push方法是向栈顶添加元素(但在数组的视角下为向数组尾部添加元素);
- 队列:enqueue方法可以由数组的push方法实现,与数组相同,相当于在数组尾部添加元素。
可以这样想:栈结构是头朝下(索引值由下往上增大)的数组结构。
上一篇:经典排序算法
下一篇:如何构造 HTTP 请求?