理解算法(1): 最大值,最小值,和堆。

最近总想,算法好像没有数学那样直观,例如方程可以解决一大类问题,我们遇到许多数学问题,只要将其转成方程问题,剩下的就是解方程。算法好像不是那么直观,顺着这个思路开始重新看算法问题。今天有一个收获,也可能其他人早就知道。

int max=INT_MIN;
for(size_t i=0;i<v.size();i++){
   if(v[i]>max){
       max=v[i];
   }
}

int min=INT_MAX;
for(size_t i=0;i<v.size();i++){
   if(v[i]<min){
       min=v[i];
   }
}

我今天才意识到,这里的
a)int max=INT_MIN; 和 if(v[i]>max)
b)int min=INT_MAX; 和 if(v[i]<min)

居然,分别就是隐含的堆。其中,
a) int max=INT_MIN; 是元素只有一个的最大堆。
b) int min=INT_MAX; 是元素只有一个的最小堆。

从这个角度来说,很多利用堆来解决的算法,本质上就和上面这两个for循环一样。

例如,求k个最小值:

// 步骤1,等价于 int min = INT_MAX;
MinHeap heap;
for(size_t i=0;i<k;i++){
  heap.push(v[i]);
}

// 步骤2,等价于 if(v[i]<min) 然后交换
for(size_t i=k;i<v.size();i++){
  if(v[i]<heap.top()){
      heap.pop();
      heap.push(v[i]);
  }
}

例如,求k个最大值

// 步骤1,等价于 int max = INT_MIN;
MaxHeap heap;
for(size_t i=0;i<k;i++){
  heap.push(v[i]);
}

// 步骤2,等价于 if(v[i]>max) 然后交换
for(size_t i=k;i<v.size();i++){
  if(v[i]>heap.top()){
      heap.pop();
      heap.push(v[i]);
  }
}

从这个角度来说,似乎:求最大值和最小值,就是堆问题。只是堆的元素可能是1个,也可能是k个,进一步堆每个元素可能是一个向量,就像方程和方程组的关系。

下一个例子是,求多个数组里,覆盖范围最小的区间,使得区间包含了每个数组至少一个元素。例如

输入数据:

[ 3, 6, 8, 10, 15 ]
[ 1, 5, 12 ]
[ 4, 8, 15, 16 ]
[ 2, 6 ]

输出:

4–6

这仍然是一个求「最小值」问题,最小值变成了一个最小区间,基本思路还是

  • 使用一个最小堆存区间
  • 循环往前迭代,比较新区间是否比当前区间更小,如果是就替换。

如果把lists看成矩阵,

  • 初始化:首先取每个数组里最小的元素,一共4个数构成了一个数组
  • 更新:删除4个元素里最小的数,同时从这个数所在的原list里取下一个数填充

那么,就会得到一种新的组合数构成的数组

[ {3,1,4,2} {3,5,4,2} {3,5,4,6}, ... ]

对于这个数组,用一个for循环就可以遍历出最小区间,这个区间包含每个list至少一个元素:

MinHeap heap;
int high=0;
pair<int,int> min_range={0,INT_MAX};
for(size_t i=0;i<lists.size(); i++){
  auto e = {value=lists[i][0], row=i, column=0};
  heap.push(e);
  high = max(high, e.value);
}

while(true){
  auto e = heap.top();
  heap.pop();

  low = e.value;
  if(high-low<min_range.second-min_range.first){
     min_range.first = low;
     min_range.second = high; 
  }
  
  if(e.column+1<lists[e.row].size()){
    next_value = lists[e.row][e.column+1];
    next_row = e.row;
    next_column = e.column+1;
    heap.push({value:next_value, row: next_row, column: next_column});
  }else{
     break;
  }
}

从这个角度来说,如何看待「数组」,就是需要重新审视的,正确的角度看“数组”,就可以获得更简洁的算法。

参考资料:

[1] 大部分时候不必自己写个堆数据结构,直接用c++的 make_heap 即可:
en.cppreference.com/w/cpp/algorithm/make_heap
[2] 这里有更多的堆数据结构本身的介绍的习题:
www.geeksforgeeks.org/heap-data-structure/
[3] 至于堆内部是怎么维护数据结构的,倒是其次,可以看这几个链接,随机从网上搜的:
[3.1] opencoursehub.cs.sfu.ca/jackt/grav-cms/cmpt225-1/notes/files/unit16_heap.pdf
[3.2] courses.engr.illinois.edu/cs225/fa2023/assets/assignments/labs/heaps/cs225sp22-lab_heaps-handout-solution.pdf
[3.3] web.stanford.edu/class/archive/cs/cs161/cs161.1168/lecture4.pdf
[3.4] staff.ustc.edu.cn/~csli/graduate/algorithms/book6/chap07.htm

--end--

posted @ 2023-10-20 00:35  ffl  阅读(35)  评论(0编辑  收藏  举报