catalog

  • 二路归并
    • 归并操作--并集
      • 暴力
      • 二叉树
    • 归并操作--元素复合
      • 暴力
      • 二叉树
        • 堆维护
        • 行分组
  • 多路归并
    • 归并操作--并集
    • 归并操作--元素复合
    • 例题

二路归并

多路归并 是 二路归并 的拓展, 因此先从二路归并开始;

二路归并是一种(算法思想), 并不是一个具体的算法, 他的实现不是唯一的


给定两个序列: A = {a1, a2, a3, ...}B = {b1, b2, b3, ...}, 长度分别为: LA 和 LB
将这两个序列, 进行(复合, 即归并操作), 形成一个新的序列: C
然后, 求序列C里的 第K大数

(归并操作), 大致分为2类: (1, 并集) (2, 元素复合)

归并操作–并集

对于(并集)操作, 是将A,B序列的元素, 都原封不动的, 放到新序列C
即, 新序列的长度是LA + LB, C = {A} + {B};
此时求C的(第K小数)

限制
La, Lb可以很大, 甚至是你无法存储下来的; (比如, 他是个等差/等比数列)
但是, K不大, 比如是1e6

也就是, C序列的大小 可以非常大, 我们无法获得C序列的每个元素; 但是, 答案只关注前K小/大 的元素, 而K不大

这里以求(第K小数)为例子, 求(第K大数)是一样的;

对于LA, LB不大, 我们首先将A, B序列 进行sort排序; (如果LA, LB很大, 此时他是等差/等比数列, 意味着它已经是排序了的)

即, 此时有:
A = {a1, a2, a3, ..., aLA} 递增
B = {b1, b2, b3, ..., bLB} 递增


暴力

算法1–暴力 O(2K*logK)
定理1
可以证明, C的 (第K小数), 一定是 (A的前K小数) 和 (B的前K小数) 中的某个数, 即在这K + K个元素中;
… 一定不会是a(K + i), 因为该元素 在C序列中, 至少是第K + i小的数

因此, 即使LA, LB很大, 没有关系, 我们只关注: {a1, ..., aK} 和 {b1, ..., bK}K + K个元素; 而K很小
即此时C = {a1, ..., ak, b1, ..., bk}

直接将这K + K个元素, 进行sort排序, 返回第K小数

二叉树

算法2–二叉树 O(K) (常用)
该算法思想, 是二路归并的核心思路, 涉及二路归并的众多应用

此时需要预备一个前提知识:
对于排序了的A, B序列, 其对应的C序列的 最小数, 一定是: min( a1, b1)
其实也是上面的定理1的特例;

第一小数, 面临的AB序列是: {a1,...} 和 {b1, ...} (总共K+K个数), 因此, 它的值为min( a1, b1)

第二小数, 它面临的AB序列是: {a2, ...} 和 {b1, ...}{a1, ...} 和 {a2, ...} 它有两种可能, 因为第一小数有两种可能, (但总共都是K+K-1个数)
假如(第一小数的结果为a1), 则此时, 第二小数面临的AB序列是: {a2, ...} 和 {b1, ...},
则此时第二小数的结果为: min( a2, b1) (等价于在{a2, ...} 和 {b1, ...}最小数, 即min( 两个队头元素: a2, b1)

… 因为, 对于长度为N的序列, 它的(第K小数), 就等价于:
… 将序列的(前K - 1小数)都从序列中删除掉 后的 长度为N - K + 1的序列的 最小数

因此, 从最初的集合{a1, ...} 和 {b1, ...}, 我们每次 根据队头两个元素, 获取集合的 最小数 (即min( ai, bj))
然后, 将该最小数, 从其所在集合中, 删除, 得到新的集合{ai, ...} 和 {bj, ...}
然后, 重复执行该过程;

即, 最初AB集合有: LA + LB个元素,
获取第一小数后, 还有LA + LB - 1个元素
获取第二小数后, 还有LA + LB - 2个元素
获取第三小数后, 还有LA + LB - 3个元素
… 当我们在获取(第K小数)时, 其实就是在{ai, ...} 和 {bj, ...}的(这LA + LB - K + 1个数)中, 找到它的 (最小数)

为什么这个算法叫做 (二叉树)呢?
对于每一个(第K小数), 它有两种可能min( ai, bj) (即两个队头), 如果你将它的(所有可能)都枚举出来, 画出来
会发现, 他会形成一个 (二叉树) 形态;

具体算法:
队头的删除, 不需要真的删除, 用双指针即可, 维护a_indb_ind, 都是从0开始, 进行K循环,
每一次循环, 得到C序列的第i小数, 自然到了最后一次循环, 就得到了第K小数

a_ind = 0, b_ind = 0;
for( int th = 0; th < K; ++th){ //< th是英文的后缀, 是`第`排名的意思if( a_ind >= LA){ans[ th] = B[ b_ind ++];}else if( b_ind >= LB){ans[ th] = A[ a_ind ++];}else{if( A[ a_ind] <= B[ b_ind]){ans[ th] = A[ a_ind ++];}else{ans[ th] = B[ b_ind ++];}}

归并操作–元素复合

对于(元素复合)操作, 是从每个序列里 都各取一个元素, 即形成一个pair (Ai, Bj)
此时, C序列的大小 是(指数级别的), LA * LB个元素

一个pair( Ai, Bj), 他的值, 可以是(加法) 可以是(乘法) …
我们以加法为例, C = {a1+b1, a1+b2, ..., a1+bLB, a2+b1, a2+b2, ..., a2+bLB, ..., aLA+bLB}LA * LB个元素

此时, 求他的(第K小/大数)

我们以求(第K小数)为例:

其实大致和(上面的 归并操作–并集) 差不多;
首先, 将A, B序列排序


暴力

算法1–暴力 O(K^2 * log(K*K))
对于第K小数Ai + Bj, 则一定有: i <= K, j <= K
即, 他只会涉及到: A序列的前K个数 和 B序列的前K个数 的复合
Ai + Bj (i,j <= K)排序, 得到第K个


二叉树

算法2–二叉树 O(K * logK)
对于A, B序列, 其C序列中 最小数, 一定是 (AB序列开头所组成的), 即: A1 + B1这个数;
第二小数, 即C序列中 将(第一小数)从C序列中删除掉的, 最小数;

但是, 这里的 和 上面的(二叉树算法) 的不同点: 上面(并集操作)中, C序列的元素 和 A/B序列里的元素, 是一样的
即, C的最小数 就是 A的最小数 或 B的最小数;
但是在这里, C的最小数是A1 + B1, 他和 A里面的元素, 是不同的!!! 他是一种(复合)
即, (删除C序列的最小数) 并不等价于 (删除A/B序列的最小元素)

但也有相似点, 都是通过: 从C序列中, 删除当前(最小数)后, 然后再求(最小数), 就得到了 (第二小数)

C序列, 从下面矩形的角度来看

(1,1)  (1,2)  (1,3) ... (1, K)
(2,1)  (2,2)  (2,3) ...
(3,1)  (3,2)  (3,3) ...
...
(K, 1) ...

C序列, 一共K * K个元素 (不必要是LA * LB, 最多用到 每个序列的前K个元素);
每个(i, j)表示, 他的值是: Ai + Bj

这个矩阵的规律是:
由于A, B序列是递增的, 因此: (i, j) 大于等于 上(i - 1, j) 和 左(i, j - 1);
最小的数, 一定是在 (左上角)
更重要的性质是: 对于第K小数(i, j), 因为所有的(<=i, <=j) 都是小于等于 (i, j)
… 因此, 所有的(<= i, <= j)元素 (即位于该第K小数(i, j)的 左或上方的元素), 一定是第< K小数
即, 所有<= K小的数, 即这些点, 他们在该矩形里 的分布, 有以下规律:
… 含有(1, 1)
… 任何点 的 (所有左上角元素), 一定也在 该图形里;
举例:

a b c d
e f g h
i j k l

所有 前K小的数, 在矩形里的图形 不可以是: a b f (因为, f的左侧e不在该图形里)
a e f是不可以的 (因为f的上侧b不在该图形里)
a b c d是可以的; a b c e i是可以的; a b c e f i是可以的;

假设, 前K小数 是: a b c e f, 我们如何求 第K + 1小数呢?
刨去a b c e f后的矩形是:

_ _ _ d
_ _ g h
i j k l

此时, 这个(残缺矩形)的 最小数, 一定是: min( d, g, i) (所有行的队头元素)
如果两个行的长度相同, 则选择靠上行的队头元素
比如, 最初这个完整的矩形, 不需要维护a, e, i; 只需维护a即可;


此时做法有2种:

堆维护

做法一: 堆维护
我们维护一个heap, 存{sum, i, j}, 表示: Ai + Bj = sum, heap以sum从小到大排序
最初放入{A1 + B1, 1, 1};
进行K次循环, 第a次循环, 把heap.top(): {sum, i, j}拿出, 就意味着, sum是第a小数
拿出之后, 再将{A(i+1) + Bj, i+1, j}{Ai + B(j+1), i, j+1}, 两个元素放入
但是, 要注意:
… 新放入这两个元素, 并不一定是 (所有行的队头元素), 我们想的是(维护所有行的队头元素);
… … 比如上面矩形为例, 我们会把j放入队列, 但他不是队头元素; 但也不要紧, 总之heap里维护的, 一定是有 (所有行的队头元素)
… 我们会重复放入同一个(元素)多次!!! 比如, g会让h入heap, d也会让h入heap
… … 因此, 必须防止重复, 如果放入过heap, 就不要再放;

set< pair< int, int> > sett;
using Item_ = tuple< int, int, int>;
priority_queue< Item_, vector< Item_>, greater< Item_> > heap;
heap.push( {A[ 0] + B[ 0], 0, 0});
sett.insert( { 0, 0});
FOR_( th, 0, N - 1, 1){auto sum = GET_( heap.top(), 0);auto a_ind = GET_( heap.top(), 1);auto b_ind = GET_( heap.top(), 2);heap.pop();//==Ans[ th] = sum;//--if( a_ind + 1 < N){if( sett.find( { a_ind + 1, b_ind}) == sett.end()){sett.insert( { a_ind + 1, b_ind});heap.push( { A[ a_ind + 1] + B[ b_ind], a_ind + 1, b_ind});}}if( b_ind + 1 < N){if( sett.find( { a_ind, b_ind + 1}) == sett.end()){sett.insert( { a_ind, b_ind + 1});heap.push( { A[ a_ind] + B[ b_ind + 1], a_ind, b_ind + 1});}}
}

行分组

做法二: 行分组 (常用)
还是从上面的那个矩阵出发, 既然我们没有办法只维护 (残缺矩阵)的 左上角边缘元素
而, 边缘元素, 一定是 (每一行的 队头元素), 因此, 我们维护 (每一行的队头元素)
即, 将这矩形, 分成K个行, 每个行是递增的, 每个行单独考虑;

a(1,1) b(1,2) c(1,3) d(1,4)
e(2,1) f(2,2) g(2,3) h(2,4)
i(3,1) j(3,2) k(3,3) l(3,4)
m(4,1) n(4,2) o(4,3) q(4,4)

比如: f(2, 2)表示: f = A[2] + B[2]

最开始, 堆里维护: { {a, 1}, {e, 1}, {i, 1}, {m, 1}}, 即每一行的队头元素 与 其在该行里的(列坐标) … 下面会介绍, 不需要记录他是哪一行的
进行K次循环, 对于第a次循环, 取出heap头: {sum, col} (sum就是: 第a小数)
将该元素后面一个元素, 放入heap后
虽然我们不知道, 这个heap头元素, 他是哪个行的, 但不要紧; 比如他是row行的;
此时: sum = A[row] + B[col], 他的后一个元素是:sum_nex = A[row] + B[col + 1]
因此: sum_nex = sum - B[ col] + B[ col + 1], 因此, 不需要得到A[row]
即, 放入: {sum - B[col] + B[col + 1], col + 1}


注意点

  • 这个矩形, 不一定是K * K大小; 也可能是: LA * LB大小, 即LA < K, LB < K; 但是LA * LB >= K
    此时, 就不是划分K组, 而是LA组; 注意边界情况;

多路归并

二路归并是: 两个序列A和B, 所复合得到的C序列 的第K排序问题;

多路归并是: M个序列A, B, C, …, 所归并得到的Z序列 的第K排序问题

此时的(归并操作), 依然大致分为2类: (1, 并集) (2, 元素复合)


归并操作–并集

并集
A: a1, a2, a3, ...
B: b1, b2, b3, ...
C: c1, c2, c3, ...
D: d1, d2, d3, ...

共M个序列

此时的复合序列Z序列为: {a1, a2, ..., b1, b2, ..., c1, c2, ... ...}LA + LB + LC + LD + ...个元素;

与二路归并的做法一样, 二路归并是维护两个指针, 而这里需要维护M个指针
最开始指向 每个序列的开头;
每次: 比较这M个指针所指向的数的大小, 找到最小的;
比如tar指针指向的数, 是最小的; 将tar ++后移

归并操作–元素复合

此时, 如果还按照二路归并的做法
你得到的矩阵, 就不是二维的了, 因为Ai + Bj + Cz + ..., 而是一个M维度的矩形;
这就复杂多了, 没法将他 分成 每个行的划分;


对于一个第K小数: Ai + Bj + Cz + ..., 则其中的Ai + Bj一定在: 序列A和B的复合序列中, 是第<= K小的数

第一步: 根据A,B序列 得到一个X序列 (X序列长度为K, 存Ai + Bj的前K小数)
第二步: 根据X,C序列, 得到X序列 (X序列长度为K, 存Xi + Cj的前K小数)
第三步: 根据X,D序列, 得到X序列 (X序列长度为K, 存Xi + Dj的前K小数)

最后, X序列里, 存的就是: Ai + Bj + Cz + ...的 前K小数

例题

  • 链接
    元素复合
  • 链接
    并集

算法 {多路归并,二路归并,第K大数}相关推荐

  1. 《算法进阶50讲》K大数

    文章目录 前言 一.概念 二.排序 1.题目描述 2.算法思路 3.时间复杂度 4.源码分析 三.哈希表 1.题目描述 2.算法思路 3.时间复杂度 4.源码分析 四.堆 1.题目描述 2.算法思路 ...

  2. 【外排序】外排序算法(磁盘排序、磁带排序) 外存设备结构分析 败者树多路归并 最佳归并树白话讲解

    外排序 外排序概述 外排序的基本方法是归并排序法 例子 总结 存储设备(可忽略) 磁带 磁带结构 磁盘 硬盘结构 块 硬盘上的数据定位 磁盘排序 磁盘排序过程 1.生成初始顺串 方法1(常规方法): ...

  3. POJ 2104 K-th Number(区间第k大数)(平方切割,归并树,划分树)

    题目链接: http://poj.org/problem? id=2104 解题思路: 由于查询的个数m非常大.朴素的求法无法在规定时间内求解. 因此应该选用合理的方式维护数据来做到高效地查询. 假设 ...

  4. uva 11997 K Smallest Sums 优先队列处理多路归并问题

    题意:K个数组每组K个值,每次从一组中选一个,共K^k种,问前K个小的. 思路:优先队列处理多路归并,每个状态含有K个元素.详见刘汝佳算法指南. 1 #include<iostream> ...

  5. 77. Leetcode 1439. 有序矩阵中的第 k 个最小数组和 (堆-技巧二-多路归并)

    技巧二 - 多路归并其实这个技巧,叫做多指针优化可能会更合适,只不过这个名字实在太过朴素且容易和双指 针什么的混淆,因此我给 ta 起了个别致的名字 - 多路归并.多路体现在:有多条候选路线.代码上, ...

  6. java 蓝桥杯 算法训练 区间k大数查询(题解)

    试题 算法训练 区间k大数查询 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一行包含一个数n,表示 ...

  7. 蓝桥杯 算法训练 区间k大数查询(水题)

    算法训练 区间k大数查询 时间限制:1.0s   内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一行包含一个数n,表示序列长度. ...

  8. 蓝桥杯 算法训练 区间k大数查询 --c++

    试题 算法训练 区间k大数查询 提交此题 评测记录 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一 ...

  9. [HIT-DB-Lab3] 数据库的多路归并算法及其实现

    HIT战德臣老师的数据库lab3, 目的是用代码实现一个两阶段多路归并算法, 处理数据, 具体要求如下 目的 思路 先看看ppt上有关内容: 两趟多路归并算法其实不难理解, 如果你的内存足够大, 你可 ...

最新文章

  1. mac redies install
  2. mysql删除数据后不释放空间问题
  3. noip2006总结
  4. eclipse集成tomcat运行web时提示引入jar包的类找不到的解决办法
  5. php测试框架,PHPUnit使用
  6. 漫步凸分析七——凸函数闭包
  7. python单元测试unittest
  8. ajaxFileUpload.js插件支持多文件上传的方法
  9. 富士胶片携数据磁带亮相大数据产业博览会
  10. java程序员的基本修养_疯狂Java程序员的基本修养 (李刚著) pdf扫描版[63MB]
  11. 个人制作:AD库、元件库、封装库及3D模型,免费
  12. 大写罗马数字(大写罗马数字3)
  13. HTML站内搜索引擎
  14. hwd分别是长宽高_W H D在尺寸上代表什么??
  15. 弘辽科技:拼多多店铺评分多久更新一次?怎么提高?
  16. linux系统的常用命令:在线下载数据文件解压缩
  17. c语言版计算坐标方位角,直线坐标计算程序
  18. 使用Pyqt5制作IT7321仪器测试软件
  19. iOS15 切换上架App图标的最新方案
  20. 残差(residual)

热门文章

  1. 【每日新闻】看清小米生态链,再说它值多少钱 | 工信部:二月份查处“黑广播”违法犯罪案件206起
  2. 城管视频ai智能分析系统
  3. 发网易emil 报错javax.mail.AuthenticationFailedException: 550
  4. vue的调试工具 vue-devtools
  5. 基于linux 和qt 的 c ++跨平台云盘项目
  6. rdm最新版编译mac版本
  7. 前端开发找工作都有哪些靠谱途径?
  8. 四川大学网络空间安全学院,考研改科目了!
  9. rosrun无法执行相应的可执行程序
  10. 华为1+x 四项服务的创建