【C/C++】胜者树与败者树:多路归并排序的利器

文章目录

  • 胜者树与败者树:多路归并排序的利器
    • 1 胜者树简介
      • 1.1 定义
      • 1.2 胜者树结构与原理
        • 1.2.1 构造流程
        • 1.2.2 归并过程
    • 2 败者树简介
      • 2.1 背景场景
      • 2.2 基本定义
      • 2.3 败者树结构和原理
        • 2.3.1 树的构造(初始建树)
        • 2.3.2 查询和更新
    • 3 胜者树 vs 败者树
    • 4 胜者树示例代码
    • 5 败者树代码示例
    • 6 总结

胜者树与败者树:多路归并排序的利器

“胜者树”(Winner Tree)和“败者树”(Loser Tree)都是完全二叉树结构,前者广泛用于多路归并排序和优先级选择问题中,尤其是在归并排序器、堆式选择算法中有重要应用,后者常用于多路归并排序中(例如外部排序),是**胜者树(Winner Tree)**的一种变体。


1 胜者树简介

1.1 定义

  • 胜者树是一棵完全二叉树,每个叶子结点表示一个选手(或者一个序列当前值),
  • 每个非叶子节点存储当前两名子选手的胜者(较小者)索引。
  • 根节点最终存储的是所有选手中最小值的索引(即冠军/胜者)。

胜者树常用于:

  • ✅ 多路归并排序
  • ✅ 外部排序(归并多个文件)
  • ✅ 堆选择算法(优先队列)
  • ✅ 归并K个有序序列/流式合并

1.2 胜者树结构与原理

1.2.1 构造流程

假设我们要从 k 个已排好序的序列中进行归并,每个序列的当前元素是一个“选手”:

  1. 叶子结点存放 k 个选手的索引(或值)。
  2. 相邻两个选手进行比较,较小者(胜者)进入上层节点。
  3. 一直递归比较,直到根节点,表示最终胜者。
  4. 内部节点都记录每次比赛的胜者索引。
1.2.2 归并过程

每轮输出根节点对应的值(最小值),然后用该“选手”所在的序列下一项替代,并自底向上更新路径即可,时间复杂度为 O(log k)


2 败者树简介

2.1 背景场景

假设你有多个已经排好序的数组(或文件),想把它们合并成一个大的有序序列。这就叫多路归并。当有很多路时(比如上百个文件),直接线性比较效率很低,败者树可以高效解决这个问题。

2.2 基本定义

  • 败者树是一棵完全二叉树,用于记录多路归并中每轮比较的失败者(较大者)。
  • 树的内部结点记录的是败者,而不是胜者。
  • 整棵树的根节点并不表示最小值,而是指向在当前轮比赛中被打败的选手。

败者树常用于:

  • 外部排序中的多路归并排序
  • 优先级选择器(类似小根堆)
  • K 路归并排序器(如归并 16 个文件,找到最小项)

2.3 败者树结构和原理

假设有 k 个已排好序的输入序列,败者树的构建步骤如下:

2.3.1 树的构造(初始建树)
  1. k 个选手(对应于每个输入序列的第一个元素)作为叶子节点。
  2. 比较相邻两个选手,将较大的(败者)向上传递,较小的(胜者)继续往上走。
  3. 最后胜者会落在“胜者路径”上,沿路的结点记录被其打败的对手(即败者)。
  4. 树的根并不是最终胜者,而是最后一次比较的败者。
2.3.2 查询和更新
  • 每次输出最小元素(胜者)。
  • 然后将该序列推进一个新元素,再从该叶子节点重新进行比较并更新路径,时间复杂度是 O(log k)

3 胜者树 vs 败者树

特性胜者树败者树
内部节点记录胜者(最小)败者(较大)
根节点胜者(最小值)败者
插入/更新效率O(log k)O(log k)
查询最小值直接查根查 tree[0] 对应叶子
代码逻辑复杂度相对较简单相对复杂
实际使用优先队列、归并排序等多路归并排序优化

4 胜者树示例代码

#include <iostream>
#include <vector>
#include <climits>
#include <cassert>class WinnerTree {
private:int k;std::vector<int> tree;   // 完全二叉树结构,1-based,tree[1] 是根,tree[0] 存储最终 winner 索引std::vector<int> leaves; // 叶子数组,存储实际数据public:WinnerTree(const std::vector<int>& init) : k(init.size()), leaves(init) {// 树大小需要 2*k(包含叶子和内部节点)tree.resize(2 * k, -1);build();}void build() {// 初始化叶子节点(从 tree[k] 到 tree[2k - 1])for (int i = 0; i < k; ++i) {tree[k + i] = i;}// 从底向上构建 winner treefor (int i = k - 1; i > 0; --i) {int left = tree[i * 2];int right = tree[i * 2 + 1];if (right == -1 || (left != -1 && leaves[left] <= leaves[right])) {tree[i] = left;} else {tree[i] = right;}}tree[0] = tree[1]; // winner 存到 tree[0]}void adjust(int leafIdx) {int pos = k + leafIdx;while (pos > 1) {int sibling = (pos % 2 == 0) ? pos + 1 : pos - 1;int parent = pos / 2;int left = tree[parent * 2];int right = (parent * 2 + 1 < (int)tree.size()) ? tree[parent * 2 + 1] : -1;if (right == -1 || (left != -1 && leaves[left] <= leaves[right])) {tree[parent] = left;} else {tree[parent] = right;}pos = parent;}tree[0] = tree[1];}int winner() const {return tree[0];}int value(int i) const {return leaves[i];}void popAndReplace(int idx, int newValue) {leaves[idx] = newValue;adjust(idx);}
};int main() {std::vector<int> init = {3, 6, 1, 9};WinnerTree wt(init);for (int i = 0; i < 4; ++i) {int win = wt.winner();std::cout << wt.value(win) << " ";wt.popAndReplace(win, INT_MAX); // 替换为无穷大,模拟归并过程}return 0;
}

输出结果:

1 3 6 9

5 败者树代码示例

#include <iostream>
#include <vector>
#include <climits>class LoserTree {
private:int k;std::vector<int> tree;   // 内部节点,size k,tree[0]为胜者叶子编号std::vector<int> leaves; // 叶子值,size kpublic:explicit LoserTree(const std::vector<int>& input) : k((int)input.size()), tree(k, -1), leaves(input) {build();}void build() {// 初始化所有内部节点为-1for (int i = 0; i < k; ++i) tree[i] = -1;for (int i = k - 1; i >= 0; --i) {adjust(i);}}void adjust(int s) {int t = s;int parent = (s + k) / 2;while (parent > 0) {if (parent >= k) {std::cerr << "ERROR: parent index out of range: " << parent << std::endl;std::cerr << "k = " << k << std::endl;std::exit(1);}// debug输出当前比较的节点和值// std::cout << "Adjust: parent = " << parent << ", t = " << t//           << ", leaves[t] = " << leaves[t]//           << ", tree[parent] = " << tree[parent]//           << ", leaves[tree[parent]] = " << (tree[parent] == -1 ? INT_MAX : leaves[tree[parent]])//           << std::endl;if (tree[parent] == -1 || leaves[t] < leaves[tree[parent]]) {std::swap(t, tree[parent]);}parent /= 2;}tree[0] = t;}int winner() const { return tree[0]; }int value(int idx) const {if (idx < 0 || idx >= k) return INT_MAX;return leaves[idx];}void popAndReplace(int idx, int newVal) {if (idx < 0 || idx >= k) {std::cerr << "popAndReplace: idx out of range: " << idx << std::endl;return;}leaves[idx] = newVal;adjust(idx);}
};int main() {std::vector<int> input = {20, 15, 30, 10};LoserTree lt(input);std::cout << "Winner index: " << lt.winner() << std::endl;std::cout << "Winner value: " << lt.value(lt.winner()) << std::endl;lt.popAndReplace(lt.winner(), 25);std::cout << "After replacement:" << std::endl;std::cout << "Winner index: " << lt.winner() << std::endl;std::cout << "Winner value: " << lt.value(lt.winner()) << std::endl;return 0;
}

6 总结

胜者树:

优点描述
✅ 高效查询最小值时间复杂度 O(1),更新 O(log k)
✅ 结构清晰比堆更适合用于 K 路归并选择
✅ 实用性强外排序、文件合并、流式排序等场景常见

败者树:

  • 败者树是一种适合多路归并排序的高效数据结构。
  • 每次找最小值和更新的操作是 O(log k)
  • 通常用于数据量过大、需要外部存储的场景(如磁盘文件排序)。
  • 实现比堆略复杂,但效率在多路归并时更优。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/82032.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础设计模式——第二部分:创建型模式 - 原型模式

第二部分&#xff1a;创建型模式 - 5. 原型模式 (Prototype Pattern) 我们已经探讨了单例、工厂方法、抽象工厂和生成器模式。现在&#xff0c;我们来看创建型模式的最后一个主要成员——原型模式。这种模式关注的是通过复制现有对象来创建新对象&#xff0c;而不是通过传统的…

C++(初阶)(十九)——红黑树

红黑树 红黑树概念规则实现结点插入变色变色参考代码&#xff1a; 查找查找参考代码 遍历 红黑树检查完整代码 概念 红⿊树是⼀棵⼆叉搜索树。它的每个结点增加⼀个存储位来表示结点的颜⾊&#xff0c;可以是红色或者黑色&#xff08;并不会出现第三种颜色&#xff09;。 通过…

Mistral AI 开源最新 Small 模型——Devstral-Small-2505

Devstral 是一款专为软件工程任务设计的代理型大语言模型&#xff08;LLM&#xff09;&#xff0c;由 Mistral AI 和 All Hands AI 合作开发 &#x1f64c;。Devstral 擅长使用工具探索代码库、编辑多个文件以及驱动软件工程代理。该模型在 SWE-bench 上表现出色&#xff0c;使…

CDGA|一线二线企业数据治理项目目前发展状况

一线城市与二线城市企业在数据治理项目的发展状况上存在一定差异&#xff0c;主要体现在目标、资源投入、策略实施以及文化培育等方面。 一线城市企业数据治理项目发展状况 ‌数据治理目标全面系统‌&#xff1a; ‌数据质量与安全‌&#xff1a;一线城市的大型企业通常拥有海量…

Lyra学习笔记1地图角色加载流程

目录 1 地图加载流程1.1 默认Experience的加载1.2 加载角色1.3 加载场景中的几个传送点 2 几个内建类的笔记2.1 UDataAsset2.2 UAssetManager 纯个人笔记&#xff0c;有错误欢迎指正&#xff0c;学习阶段基本看到不会的就写一写&#xff0c;最后有时间会梳理整体结构 先看完了官…

SurfaceFlinger及Android应用RenderThread角度观察Jank丢帧卡顿

SurfaceFlinger及Android应用RenderThread角度观察Jank丢帧卡顿 CPU、GPU、Display 三个部分&#xff1a;CPU 负责计算帧数据&#xff0c;把计算好的数据交给 GPU&#xff0c;GPU 会对图形数据进行渲染&#xff0c;渲染好后放到 buffer &#xff08;图像缓冲区&#xff09;存起…

《牛客》数组中出现次数超过一半的数字

牛客的刷题之路不停歇 ⌓‿⌓ 不积跬步无以至千里&#xff0c;不积小流无以成江海 The harder you work,the luckier you will be 题目及示例 题目链接 描述 给一个长度为 n 的数组&#xff0c;数组中有一个数字出现的次数超过数组长度的一半&#xff0c;请找出这个数字。 例…

七彩喜康养护理——科技赋能下的全周期健康守护

在当今社会&#xff0c;随着人们健康意识的不断提高&#xff0c;护理行业逐渐走向专业化、精细化&#xff0c;而七彩喜智养护理作为一种新兴的护理方式&#xff0c;逐渐受到了广泛的关注和应用。 它不仅仅是针对单一病症的治疗护理&#xff0c;而是一种全面的、全方位的健康管…

【爬虫】12306自动化购票

上文&#xff1a; 【爬虫】12306查票-CSDN博客 下面是简单的自动化进行抢票&#xff0c;只写到预定票&#xff0c;没有写完登陆&#xff0c; 跳出登陆后与上述代码同理修改即可。 感觉xpath最简单&#xff0c;复制粘贴&#xff1a; 还有很多写法&#xff1a; 官网地址&#…

Java设计模式之组合模式:从入门到精通(保姆级教程)

文章目录 1. 组合模式概述1.1 专业定义1.2 通俗解释1.3 模式结构2. 组合模式详细解析2.1 模式优缺点2.2 适用场景3. 组合模式实现详解3.1 基础实现3.2 代码解析4. 组合模式进阶应用4.1 透明式 vs 安全式组合模式4.2 组合模式与递归4.3 组合模式与迭代器5. 组合模式在实际开发中…

游戏如何应对反编译工具dnspy

Unity Mono 是 Unity 引擎默认的脚本运行时环境&#xff0c;由跨平台的开源 .NET 框架实现&#xff0c;它允许开发者使用 C# 等编程语言编写游戏逻辑&#xff0c;凭借简单易用的开发环境和高效的脚本编译速度&#xff0c;得到了众多游戏的青睐。 在 Mono 模式下&#xff0c;游…

腾讯云证书过期提醒的应对措施,Caddy 自动管理的 Let‘s Encrypt 证书.

用腾讯的免费证书&#xff0c;90天需要换一次。 Caddy 自动管理的 Lets Encrypt 证书. 在网站上按F12然后找到security选项&#xff0c;然后选择View certifcate 就可以看到证书的有效期。 完全无需操作 你的网站实际使用的是 Caddy 自动管理的 Lets Encrypt 证书&#xff0c;…

[Java实战]Spring Boot整合Elasticsearch(二十六)

[Java实战]Spring Boot整合Elasticsearch&#xff08;二十六&#xff09; 摘要&#xff1a;本文通过完整的实战演示&#xff0c;详细讲解如何在Spring Boot项目中整合Elasticsearch&#xff0c;实现数据的存储、检索和复杂查询功能。包含版本适配方案、Spring Data Elasticsea…

【关联git本地仓库,上传项目到github】

目录 1.下载git2.绑定用户3.git本地与远程仓库交互4.github项目创建5.上传本地项目到github6.完结撒花❀❀❀&#xff01;&#xff01;&#xff01; 1.下载git git下载地址&#xff1a;https://git-scm.com/downloads 下载安装后创建快捷地址&#xff1a;&#xff08;此处比较…

[Vue]路由基础使用和路径传参

实际项目中不可能就一个页面&#xff0c;会有很多个页面。在Vue里面&#xff0c;页面与页面之间的跳转和传参会使用我们的路由: vue-router 基础使用 要使用我们需要先给我们的项目添加依赖:vue-router。使用命令下载: npm install vue-router 使用路由会涉及到下面几个对象:…

软考-软件工程开发模型

软考-软件工程开发模型 参考视频&#xff1a; 软件工程概述&开发模型 &#xff0c;配合视频理解更清晰&#xff5e; 软件的生命周期为&#xff1a;需求分析、软件设计、软件开发、运行维护直至被淘汰 几个阶段。 软件工程支持 4 个活动&#xff0c;简称 PDCA&#xff0c…

【写在创作纪念日】基于SpringBoot和PostGIS的各省东西南北四至极点区县可视化

目录 前言 一、空间检索简介 1、空间表结构 2、四至空间检索 二、前后端实现 1、后端实现 2、前端集成 三、成果展示 1、东部省份 2、西部省份 3、南部省份 4、北部省份 5、中部省份 四、总结 前言 在当今数字化时代&#xff0c;地理信息数据的分析与可视化对于众…

智能守护校园“舌尖安全“:AI视频分析赋能名厨亮灶新时代

引言&#xff1a; 在校园食品安全备受关注的今天&#xff0c;一套融合视频监控管理平台与AI视频分析盒子的智能解决方案正在全国多地学校食堂悄然落地&#xff0c;为传统的"名厨亮灶"工程注入科技新动能。这套系统不仅实现了后厨操作的"透明化"&#xff0…

【软件设计师】计算机网络考点整理

以下是软件设计师考试中 ​​计算机网络​​ 的核心考点总结&#xff0c;帮助您高效备考&#xff1a; ​​一、网络体系结构与协议​​ ​​OSI七层模型 & TCP/IP四层模型​​ 各层功能&#xff08;物理层-数据链路层-网络层-传输层-会话层-表示层-应用层&#xff09;对应协…

Starrocks的CBO基石--统计信息的来源 StatisticAutoCollector

背景 本文来从底层代码的实现来分析一下Starrocks怎么获取统计信息&#xff0c;这些统计信息在后续基于CBO的代价计算的时候有着重要的作用 本文基于Starrrocks 3.3.5 结论 Starrocks的统计信息的收集是通过周期性的运行一系列的SQL&#xff08;以分区为维度&#xff0c;如果…