栈与队列

一、栈
栈是一种“操作受限的线性表”
 
1、栈主要包含两个操作,入栈和出栈,也就是在栈顶插入一个数据,在栈顶删除一个数据,也就是说所有的操作都是在栈顶进行的。
 
2、用代码实现一个栈
栈的实现有两种,如果是用数组实现的叫做顺序栈,如果是用链表实现的叫链式栈。
下面来用js实现一个顺序栈
class Stack{
    constructor(){
        this.items = []//存储栈元素的数组
        this.length = 0//栈中元素


    }


    push(item){
        //入栈操作
        this.items.push(item)
        this.length++;
    }
    pop(){
        //出栈操作
       let temp =  this.items.pop()
       this.length--;
       return temp
    }
}
let mystack = new Stack()
mystack.push('hush')
mystack.push('krys')
mystack.push('liang')
mystack.push('stefanie')
console.log(mystack.items)
let temp = mystack.pop()
console.log(temp,'..',mystack.items)

 

 
用js来实现一个链表栈
//初始化结点
class Node{
    constructor(key){
        this.next = null
        this.key = key
    }
}
//初始化单链表
class List{
    constructor(){
        this.head = null
        this.length = 0
    }
    //创建结点
    static createNode(key){
        return new  Node(key)
    }




    //往头部插入数据
    push(node){
        if (this.head){
            //此时head有指向的结点
            node.next = this.head
        } else {
            node.next = null
        }
        this.head = node
        this.length++
    }
    //删除结点
    pop(){
      //删除头结点
        this.head = this.head.next
        this.length --;
        return;
    }
}


let node1 = new Node('krysliang'),node2 = new Node('wanlingliang')
let list = new List()
list.push(node1)
list.push((node2))
console.log(list.head)
list.pop()
console.log(list.head)

 

栈与队列_第1张图片
 
这里可以看到不管是出栈或者入栈,都只需要常量级的变量,而且没有循环,所以时间复杂度和空间复杂度都是O(1)
 
在考虑空间复杂度的时候,指的是除了原本的数据存储空间外,算法运行还需要额外的存储空间。
 
3、我们在讲栈的应用时,讲到用函数调用栈来保存临时变量,为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗
 
其实,我们不一定非要用栈来保存临时变量,只不过如果这个函数调用符合后进先出的特性,用栈这种数据结构来实现,是最顺理成章的选择。
从调用函数进入被调用函数,对于数据来说,变化的是什么呢?是作用域。所以根本上,只要能保证每进入一个新的函数,都是一个新的作用域就可以。而要实现这个,用栈就非常方便。在进入被调用函数的时候,分配一段栈空间给这个函数的变量,在函数结束的时候,将栈顶复位,正好回到调用函数的作用域内。
 
 
4、有关于栈的算法训练
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
 
 
有效字符串需满足:
 
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
 
示例 1:
输入: "()"
输出: true
示例 2:
 
输入: "()[]{}"
输出: true
示例 3:
 
输入: "(]"
输出: false
示例 4:
 
输入: "([)]"
输出: false
示例 5:
 
输入: "{[]}"
输出: true
 
 
var isValid = function(s) {
    let right = [')','}',']']
    if(s.length==0) return true;
    if(s.length==1) return false;


    let stack = [],flag = true
    for (let i = 0;i0){//如果此时栈中还有数据也就是还有未匹配的括号
        flag = false
    }
    if (flag){
        return true
    } else {
        return false
    }
};

 

二、队列
CPU资源有限,任务的处理速度与线程个数并不是线性正相关,比如当线程过多的时候,CPU频繁的切换就会导致性能的下降。
 
队列就是先进先出,入队只能在队尾进行,出队只能在对头进行。
栈与队列_第2张图片
 
队列跟栈一样,如果用数组实现那么它就是顺序队列,如果用链表实现那么它就是链表队列。
1、用代码实现一个队列
class Quene{
    constructor() {
      this.items = []//用来装队列的数组
        this.length = 0//队列长度
    }


    enquene(data){
        //入队
       this.items.push(data)
       this.length++
    }
    dequene(){
        let item = this.items.shift()
        this.length--
        return item
    }
}


let quene = new Quene()
quene.enquene(1)
quene.enquene(2)
quene.enquene(3)
quene.enquene(4)
quene.enquene(5)
console.log(quene.items)
let item = quene.dequene()
console.log(item,quene.items)

 

 
用链表实现一个队列
class List{
    constructor(){
        this.head = null
        this.length = 0
    }


    insert(node){
        //入队
        if (this.head){
            let p = this.head.next
            let prev = this.head
            while (p){
                prev = p
                p = p.next


            }
            prev.next = node


        } else {
            //队列中暂时没有结点
            this.head = node
        }
        this.length ++
    }
    delete(){
        //出队
        if (this.head){
            let item = this.head.next
            this.head = this.head.next
            this.length++
            return item
        }
    }
}


let node1 = new Node(1),node2 = new Node(2),node3 = new Node(3)
let list = new List()
list.insert(node1)
list.insert(node2)
list.insert(node3)
console.log(list.head)
let item = list.delete()
console.log(list.head)

 

栈与队列_第3张图片
 
2、循环队列
什么是循环队列呢?循环队列如何用代码实现?实现的关键是什么?
循环队列其实就是,本来队列是一条线的,我现在把它掰成一个环,比如下面这张图,数组有0~7一共8个位置,现在队尾在7,队头在4,因为tail已经在最后一个位置了,如果是普通队列,当插入最后一个数据后,如果再有新的数据到来,就要将数据移动到前面来,这就要有额外的时间去处理,但是如果我们用循环队列来实现,当到了7这里我监测到前面0的空间还是空的,那么我就跳到0这里把最新的数据插入。这样一来,循环队列是不是省事多了呢?
 
 
写循环队列的代码有两个需要注意的点:
  • 队空和队满的判断
  • 从7跳到0这里的判断
 
先来看看队空和队满的判断条件:
如果队列为空,也就是队列一个数据都没有,此时tail==head。
如果队列满了,此时tail的位置在head的前面一格,那么这个怎么用代码描述呢?假设数组的长度为8,此时head为4,如果队满了tail在3这里,这里有一个关系就(3+1)%8=4,也就是(tail+1)%n==head
class CircularQueue{
    constructor(n){
        this.items = []
        this.n = n
        this.head = 0
        this.tail = 0
    }
    enqueue(item){
        //入队
        //如果队满了就不予以入队,否则入队,tail++
        if ((this.tail+1)%this.n == this.head) {
            console.log('队满不予以入队',this.items)
            return false
        }


        this.items[this.tail] = item
        this.tail = (this.tail+1)%this.n //当前队尾所在的位置
        console.log('正常入队',item,this.items)
        return  true
    }
    dequene(){
        //出队
        //如果队空就返回false
        if (this.head == this.tail) {
            console.log('队空不予以出队')
            return false
        }
        let item = this.items[this.head]
        this.items[this.head] = null
        this.head = (this.head+1)%this.n
        console.log('出队元素',item,'当前队列',this.items)
        return  item
    }
}


let myquene = new CircularQueue(3)
let item1 = myquene.dequene()
myquene.enqueue(1)
myquene.enqueue(2)
myquene.enqueue(3)
let item2 = myquene.dequene()
let item3 = myquene.dequene()
let item4 = myquene.dequene()
myquene.enqueue(4)
myquene.enqueue(5)
myquene.enqueue(6)

 

 
栈与队列_第4张图片
 
可以看到,tail所在的位置一定是空的,所以就会浪费掉一个正常的存储空间。
 
3、阻塞队列和并发队列
以前上操作系统的课的时候,讲到CPU进程调度的时候就有说过阻塞队列和并发队列了。就是当前如果队列为空也就是没有数据可以拿的时候,队列是被阻塞的,直到队列中有数据了,才可以从对头拿数据。而此外,当队列满的时候,就不可以再往队列插入数据,直到队列中有空闲位置再插入数据。这就是一个生产者-消费者的模型。
 
并发队列呢,就是同一时刻只允许一个存或者一个取操作。
 
4、思考
当线程池没有空闲线程的时候,新的任务请求线程资源的时候,线程池要如果处理呢?
 
一般有两种策略。第一种是非阻塞的处理方式,直接拒绝任务请求;
另一种是阻塞的处理方式,将请求进行排队,等到有空闲线程的时候,取出排队的请求继续处理。
 
那么这里的队列中,如果时基于链表的实现方式,可以实现一个支持无线排队的无界队列,但是可能会导致过多的请求排队等待,后面来的请求等待的时候就很长了。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池时不合适的。
 
而基于数组实现的有界队列,队列的大小有限,所以线程池中排队的请求超过队列大小的时候,接下俩的请求就会被拒绝。这个时候,设置一个合理的队列大小就很重要了。
 
对于大部分资源有限的场景,当没有空闲资源的时候,基本上都可以通过队列这种数据结构i请求排队。
 

你可能感兴趣的:(数据结构与算法)