JavaScript【栈和队列】

目录

一、栈结构(Stack)

1.简介

​编辑

程序中的栈结构:

2.栈常见的操作:

1.封装栈类

2.栈结构的简单应用:

二、队列结构(Queue)

1.队列简介

队列的应用:

队列类的实现:

队列的常见操作:

2.封装队列类

代码实现

测试代码

3.队列的应用

代码实现

三、优先队列

1.优先级队列的实现

代码实现:

测试代码:

2.数组splice用法

3.数组的push


一、栈结构(Stack)

1.简介

数组是一个线性结构,并且可以在数组的任意位置插入和删除元素。而栈和队列就是比较常见的受限的线性结构。如下图所示:

JavaScript【栈和队列】_第1张图片

栈的特点为先进后出,后进先出(LIFO:last in first out)。

JavaScript【栈和队列】_第2张图片

程序中的栈结构:

  • 函数调用栈: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)

JavaScript【栈和队列】_第3张图片

3.练习:题目:有6个元素6,5,4,3,2,1按顺序进栈,问下列哪一个不是合法的出栈顺序?

  • A:5 4 3 6 1 2 (√)
  • B:4 5 3 2 1 6 (√)
  • C:3 4 6 5 2 1 (×)
  • D:2 3 4 1 5 6 (√)

题目所说的按顺序进栈指的不是一次性全部进栈,而是有进有出,进栈顺序为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出栈。符合入栈顺序; 

2.栈常见的操作:

  • push(element):添加一个新元素到栈顶位置;
  • pop():移除栈顶的元素,同时返回被移除的元素;
  • peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它);
  • isEmpty():如果栈里没有任何元素就返回true,否则返回false;
  • size():返回栈里的元素个数。这个方法和数组的length属性类似;
  • toString():将栈结构的内容以字符串的形式返回。

1.封装栈类

代码实现

    // 封装栈类
    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 7
        let 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)													//65

    console.log(s.pop());											//68
    console.log(s.pop());											//69
    
    console.log(s.peek());											//71
	console.log(s.isEmpty());										//72
   
    console.log(s.size());											//74
    console.log(s.toString());										//75

2.栈结构的简单应用:

利用栈结构的特点封装十进至转换为二进至的函数:

JavaScript【栈和队列】_第4张图片

 

JavaScript【栈和队列】_第5张图片

 

    //简单应用:
    //封装函数:将十进制转成二进制(十转二的运算最后倒叙取余的特点符合栈'先进后出')
    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和1
      let  binaryString = '';
      let a = stack.items.length
     while(stack.items.length != 0){
        binaryString += stack.pop();
      }
      return binaryString;
    }
    
    //测试代码
    console.log(dec2bin(10));										//103
    console.log(dec2bin(100));										//104
    console.log(dec2bin(1000));										//105

二、队列结构(Queue)

1.队列简介

队列是是一种受限的线性表,特点为先进先出(FIFO:first in first out)

  • 受限之处在于它只允许在表的前端(front)进行删除操作;
  • 在表的后端(rear)进行插入操作;

JavaScript【栈和队列】_第6张图片

相当于排队买票,先来的先买票,后来的后买票。

JavaScript【栈和队列】_第7张图片

队列的应用:

  • 打印队列:计算机打印多个文件的时候,需要排队打印;
  • 线程队列:当开启多线程时,当新开启的线程所需的资源不足时就先放入线程队列,等待CPU处理;

队列类的实现:

队列的实现和栈一样,有两种方案:

  • 基于数组实现;
  • 基于链表实现;

队列的常见操作:

  • enqueue(element):向队列尾部添加一个(或多个)新的项;
  • dequeue():移除队列的第一(即排在队列最前面的)项,并返回被移除的元素;
  • front():返回队列中的第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息与Stack类的peek方法非常类似);
  • isEmpty():如果队列中不包含任何元素,返回true,否则返回false;
  • size():返回队列包含的元素个数,与数组的length属性类似;
  • toString():将队列中的内容,转成字符串形式;

2.封装队列类

代码实现

    // 基于数组封装队列类
    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);												//62
    queue.dequeue()
    console.log(queue);												//64

    //front
    console.log(queue.front());								 		//67
    
    // 验证其他方法
    console.log(queue.isEmpty());								 	//70
    console.log(queue.size());								 		//71
    console.log(queue.toString());								 	//72

3.队列的应用

使用队列实现小游戏:击鼓传花,传入一组数据和设定的数字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());									//104
      let endName = queue.front()
      console.log('最终剩下的人:' + endName);						   //106	

      return nameList.indexOf(endName);
    }


    //5.测试击鼓传花
    let names = ['lily', 'lucy', 'Tom', 'Lilei', 'Tony']
    console.log(passGame(names, 3));			

三、优先队列

优先级队列主要考虑的问题为:

  • 每个元素不再只是一个数据,还包含数据的优先级;
  • 在添加数据过程中,根据优先级放入到正确位置;

1.优先级队列的实现

代码实现:

    // 封装优先级队列
    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 = false
          for(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);

2.数组splice用法

  • splice(1,0,'Tom'):表示在索引为1的元素前面插入元素’Tom‘(也可以理解为从索引为1的元素开始删除,删除0个元素,再在索引为1的元素前面添加元素'Tom');

  • splice(1,1,'Tom'):表示从索引为1的元素开始删除(包括索引为1的元素),共删除1个元素,并添加元素'Tom'。即把索引为1的元素替换为元素'Tom'。

3.数组的push

  • 数组:在数组[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方法实现,与数组相同,相当于在数组尾部添加元素。

可以这样想:栈结构是头朝下(索引值由下往上增大)的数组结构。

JavaScript【栈和队列】_第8张图片

 

你可能感兴趣的:(开发语言,算法,javascript,队列,栈)