算法 {多路归并,二路归并,第K大数}
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_ind
和 b_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大数}相关推荐
- 《算法进阶50讲》K大数
文章目录 前言 一.概念 二.排序 1.题目描述 2.算法思路 3.时间复杂度 4.源码分析 三.哈希表 1.题目描述 2.算法思路 3.时间复杂度 4.源码分析 四.堆 1.题目描述 2.算法思路 ...
- 【外排序】外排序算法(磁盘排序、磁带排序) 外存设备结构分析 败者树多路归并 最佳归并树白话讲解
外排序 外排序概述 外排序的基本方法是归并排序法 例子 总结 存储设备(可忽略) 磁带 磁带结构 磁盘 硬盘结构 块 硬盘上的数据定位 磁盘排序 磁盘排序过程 1.生成初始顺串 方法1(常规方法): ...
- POJ 2104 K-th Number(区间第k大数)(平方切割,归并树,划分树)
题目链接: http://poj.org/problem? id=2104 解题思路: 由于查询的个数m非常大.朴素的求法无法在规定时间内求解. 因此应该选用合理的方式维护数据来做到高效地查询. 假设 ...
- uva 11997 K Smallest Sums 优先队列处理多路归并问题
题意:K个数组每组K个值,每次从一组中选一个,共K^k种,问前K个小的. 思路:优先队列处理多路归并,每个状态含有K个元素.详见刘汝佳算法指南. 1 #include<iostream> ...
- 77. Leetcode 1439. 有序矩阵中的第 k 个最小数组和 (堆-技巧二-多路归并)
技巧二 - 多路归并其实这个技巧,叫做多指针优化可能会更合适,只不过这个名字实在太过朴素且容易和双指 针什么的混淆,因此我给 ta 起了个别致的名字 - 多路归并.多路体现在:有多条候选路线.代码上, ...
- java 蓝桥杯 算法训练 区间k大数查询(题解)
试题 算法训练 区间k大数查询 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一行包含一个数n,表示 ...
- 蓝桥杯 算法训练 区间k大数查询(水题)
算法训练 区间k大数查询 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一行包含一个数n,表示序列长度. ...
- 蓝桥杯 算法训练 区间k大数查询 --c++
试题 算法训练 区间k大数查询 提交此题 评测记录 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一 ...
- [HIT-DB-Lab3] 数据库的多路归并算法及其实现
HIT战德臣老师的数据库lab3, 目的是用代码实现一个两阶段多路归并算法, 处理数据, 具体要求如下 目的 思路 先看看ppt上有关内容: 两趟多路归并算法其实不难理解, 如果你的内存足够大, 你可 ...
最新文章
- mac redies install
- mysql删除数据后不释放空间问题
- noip2006总结
- eclipse集成tomcat运行web时提示引入jar包的类找不到的解决办法
- php测试框架,PHPUnit使用
- 漫步凸分析七——凸函数闭包
- python单元测试unittest
- ajaxFileUpload.js插件支持多文件上传的方法
- 富士胶片携数据磁带亮相大数据产业博览会
- java程序员的基本修养_疯狂Java程序员的基本修养 (李刚著) pdf扫描版[63MB]
- 个人制作:AD库、元件库、封装库及3D模型,免费
- 大写罗马数字(大写罗马数字3)
- HTML站内搜索引擎
- hwd分别是长宽高_W H D在尺寸上代表什么??
- 弘辽科技:拼多多店铺评分多久更新一次?怎么提高?
- linux系统的常用命令:在线下载数据文件解压缩
- c语言版计算坐标方位角,直线坐标计算程序
- 使用Pyqt5制作IT7321仪器测试软件
- iOS15 切换上架App图标的最新方案
- 残差(residual)
热门文章
- 【每日新闻】看清小米生态链,再说它值多少钱 | 工信部:二月份查处“黑广播”违法犯罪案件206起
- 城管视频ai智能分析系统
- 发网易emil 报错javax.mail.AuthenticationFailedException: 550
- vue的调试工具 vue-devtools
- 基于linux 和qt 的 c ++跨平台云盘项目
- rdm最新版编译mac版本
- 前端开发找工作都有哪些靠谱途径?
- 四川大学网络空间安全学院,考研改科目了!
- rosrun无法执行相应的可执行程序
- 华为1+x 四项服务的创建