算法系列—scan line 算法(1)

256 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

扫描线算法顾名思义,就是用一根假想的扫描线从下向上对图形进行扫描,然后在每次扫描到边的时候,进行答案的计算。

数飞机

我们先从一道题引出今天算法,题目是这样的,有 4 驾飞机,这些飞机起飞和降落时间如下表

序号startend
1110
223
358
447

问某一个时刻空中飞机的数量最多是多少?

scan_line_001.png

可以先画一下图,这样一目了然,那么如何解决这样问题,而且这样问题可以演变其他问题。这就要用到今天的算法扫描线算法,就是在时间轴进行扫描,然后记录在变化点情况下进行更新状态(空中飞机的数量)。然后有起飞(start)我们就给计数器(空中飞机数量的计数器加 1)有降落(end)就更新计数器(减去 1)。

这就是扫描线算法的精髓了,接下来上代码。


// 创建二维数组,每一个元素[start,end]
const airplines = [[1,10],[2,3],[5,8],[4,7]]
// const airplines = [[1,4],[2,6],[3,7],[4,5]]

function countAirPlanes(airplines){
    // 创建数组,第一个元素start或者end 时间,对应如果 start 给 1,如果是 end 就给 -1[start/end,1/-1]
    let list = []
    // 遍历所有飞机
    for(let i = 0; i< airplines.length;i++){
        // start 时间 -> 1
        list.push([airplines[i][0],1])
        // end 时间 -> -1
        list.push([airplines[i][1],-1])
    }
    //对数组按时间排序
    list.sort(function(num1,num2){
        return num1[0]-num2[0];
    })
    // console.log(list)

    
    let cnt = 0;//更新计数
    let ans = 0;
    for(let i = 0; i < list.length;i++ ){
        cnt += list[i][1];
        // console.log(cnt)
        //记录下 cnt 随时间变化中的数值中的最大值
        ans = Math.max(ans,cnt)
    }

    return ans
}

console.log(countAirPlanes(airplines))

重叠问题

这是也是 Meeting Rooms 也是一个 LeetCode 的题,问题是这样的有如下会议列表

const meetings = [[1,10],[2,7],[3,19],[8,12],[10,20],[11,30]]

列表中每一个元素(数组),例如 [1,10] 其中第一个位置数值(1)表示会议开始的时间,第二个位置的数字(2)表示会议结束的时间。为了让所有会议都可以正常举行,需要准备多个会议室,这个也就是树飞机问题,也就是计算同时进行会议数量最多是多少,也就是空中飞机数量最多是多少。不过我们一种方法,比较简单但是容易理解。如下图

scan_line_002.png

我们先为会议按开始时间从小到大排个序,然后为第一个会议准备一间会议室,然后从列表中选择下一个会议,看其开始时间是否大于占用会议室的第一个会议(占用会议列表时按结束时间进行排序,最早结束排在最前面)的结束时间,如果大于则说明该会议已经结束,会议室可以利用,这将该会议在这个会议室进行,否则就为这个会议室开一个新的会议室。

const meetings = [[1,10],[2,7],[3,19],[8,12],[10,20],[11,30]]
const top = 0;
const parent = i => ((i + 1) >>> 1) - 1;
const left = i => (i << 1) + 1;
const right = i => (i + 1) << 1;

class PriorityQueue {
  constructor(comparator = (a, b) => a > b) {
    this._heap = [];
    this._comparator = comparator;
  }
  size() {
    return this._heap.length;
  }
  isEmpty() {
    return this.size() == 0;
  }
  peek() {
    return this._heap[top];
  }
  push(...values) {
    values.forEach(value => {
      this._heap.push(value);
      this._siftUp();
    });
    return this.size();
  }
  pop() {
    const poppedValue = this.peek();
    const bottom = this.size() - 1;
    if (bottom > top) {
      this._swap(top, bottom);
    }
    this._heap.pop();
    this._siftDown();
    return poppedValue;
  }
  replace(value) {
    const replacedValue = this.peek();
    this._heap[top] = value;
    this._siftDown();
    return replacedValue;
  }
  _greater(i, j) {
    return this._comparator(this._heap[i], this._heap[j]);
  }
  _swap(i, j) {
    [this._heap[i], this._heap[j]] = [this._heap[j], this._heap[i]];
  }
  _siftUp() {
    let node = this.size() - 1;
    while (node > top && this._greater(node, parent(node))) {
      this._swap(node, parent(node));
      node = parent(node);
    }
  }
  _siftDown() {
    let node = top;
    while (
      (left(node) < this.size() && this._greater(left(node), node)) ||
      (right(node) < this.size() && this._greater(right(node), node))
    ) {
      let maxChild = (right(node) < this.size() && this._greater(right(node), left(node))) ? right(node) : left(node);
      this._swap(node, maxChild);
      node = maxChild;
    }
  }
}


function minMeetingsRoom(){
    // rooms 数组,我们增加一个 room 就放到这个数组里
    const rooms = []
    // 按开始时间进行排序
    meetings.sort(function(num1,num2){
        return num1[0] - num2[0]
    });

    // pq 我们队列按结束时间进行排序
    let heap = new PriorityQueue((a, b) => a[1] < b[1])
    //默认将第一个 meeting 放入 pq 队列
    if(meetings.length != 0 ) heap.push(meetings[0]);
    // 从第 2 个会议开始遍历
    for (let i = 1; i < meetings.length; i++) {
        //获取最早结束会议所占用的会议室,
        let cur = heap.pop();
        // console.log(cur[1])
        //最早结束会议结束时间如果小于当前会议的开始时间,就把这个会议室让出给当前会议
        if(cur[1] <= meetings[i][0]){
            cur[1] = meetings[i][1];
        } else {
            heap.push(meetings[i])
        }
        heap.push(cur)
        
    }
    // console.log(heap)
    return heap.size()
    
}

console.log(minMeetingsRoom(meetings))