小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
扫描线算法顾名思义,就是用一根假想的扫描线从下向上对图形进行扫描,然后在每次扫描到边的时候,进行答案的计算。
数飞机
我们先从一道题引出今天算法,题目是这样的,有 4 驾飞机,这些飞机起飞和降落时间如下表
序号 | start | end |
---|---|---|
1 | 1 | 10 |
2 | 2 | 3 |
3 | 5 | 8 |
4 | 4 | 7 |
问某一个时刻空中飞机的数量最多是多少?
可以先画一下图,这样一目了然,那么如何解决这样问题,而且这样问题可以演变其他问题。这就要用到今天的算法扫描线算法,就是在时间轴进行扫描,然后记录在变化点情况下进行更新状态(空中飞机的数量)。然后有起飞(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)表示会议结束的时间。为了让所有会议都可以正常举行,需要准备多个会议室,这个也就是树飞机问题,也就是计算同时进行会议数量最多是多少,也就是空中飞机数量最多是多少。不过我们一种方法,比较简单但是容易理解。如下图
我们先为会议按开始时间从小到大排个序,然后为第一个会议准备一间会议室,然后从列表中选择下一个会议,看其开始时间是否大于占用会议室的第一个会议(占用会议列表时按结束时间进行排序,最早结束排在最前面)的结束时间,如果大于则说明该会议已经结束,会议室可以利用,这将该会议在这个会议室进行,否则就为这个会议室开一个新的会议室。
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))