看数据结构写代码(66) 败者树

计算机的 内存 是 有限的,无法 存入 庞大的数据。当 遇到 大数据需要排序时,我们 需要 将 这些 数据
分段 从 硬盘里 读到 内存中,排好序,再 写入到 硬盘中,这些段 叫做 归并段。最后将 这些 分段 合并 成 一个 最终  完整 有序的 数据。

这里 操作的 时间 =  内部 排序 时间 +  外存读写时间 + 内部归并所需时间。

其中 外存 读写时间 最耗时,外存读写时间 = 读写次数 * 读写数据的时间 ,读写 数据的时间 因 设备 性能 而 影响,我们 无法控制,我们 只能 控制 读写次数。读写 次数 和 归并过程 有关。 当 我们 采用 2路 归并 和 5路 归并,显然 5路 归并的 所 读写的 次数 更少。(具体 看 课本)。可是 当 我们扩大 归并的  路树的同时,内部 归并的 时间 却 因此 上升。

我们 用 败者树 来解决  由 归并的 路数 增加 而 导致的 内部归并 时间 上升的 问题。

败者树 和 胜者树是 个 相反的概念,败者树的 父节点  保存 失败者,而 胜者树 的 父节点 保存 胜利者。他们 都是 完全二叉树,并且 都是 将 胜利者 继续 根 上层 继续 比较。

下面 给出 败者树的 代码:

欢迎指出 代码不足

// LoseTree.cpp : 定义控制台应用程序的入口点。
//败者树,父节点保存失败者的信息,胜利者继续 比赛

#include "stdafx.h"
#include <climits>
#define K	5//5路平衡归并
#define MEM_SIZE 4//内存最多存储3组数据.(多加了一个最大值数据)
typedef int LoseTree[K];//败者树的非终端节点

typedef struct ExNode{//败者树的叶子节点
	int key;
}External[K+1];

//测试数组,假设 内存只能 放入 3组数据,并且 内存 已经将这些数据排好序了。
//现在 需要归并这些数据
static int testArray[K][MEM_SIZE] = {
	{10,15,16,INT_MAX},
	{9,18,20,INT_MAX},
	{20,22,40,INT_MAX},
	{6,15,25,INT_MAX},
	{12,37,48,INT_MAX},
};

//调整函数,和 所有 祖先比较,替换败者 和 最终胜利者 t[0]
void adjust(LoseTree t,External ex,int i){
	int f = (i + K) / 2;
	while (f > 0){
		if (ex[i].key > ex[t[f]].key){
			int temp = i;
			i = t[f];//i 保存 胜利者,继续 比较
			t[f] = temp;//有新的败者了.
		}
		f = f / 2;
	}
	t[0] = i;//最终胜利者
}

//创建败者树..
void createTree(LoseTree  tree,External  ex){
	for (int i = 0; i < K; i++){//初始化叶子节点
		ex[i].key = testArray[i][0];
	}
	ex[K].key = INT_MIN;//为了让第一次 所有 都是 失败者
	for (int i = 0; i < K; i++){//初始化非叶子节点,
		tree[i] = K;
	}
	for (int i = K-1; i >= 0; i--){//调整叶子节点,顺序不能反
		adjust(tree,ex,i);
	}
}

void inputNewKey(External ex,int winIndex){
	ex[winIndex].key = testArray[winIndex][1];
	//前移
	for (int i = 0; i < MEM_SIZE -1; i++){
		testArray[winIndex][i] = testArray[winIndex][i+1];
	}
}

//归并函数
void K_Merge(){
	LoseTree t;//非叶子节点
	External ex;//叶子节点
	createTree(t,ex);
	int winIndex = t[0];//胜利者 坐标
	while (ex[winIndex].key != INT_MAX){
		printf("%d\t",ex[winIndex].key);
		inputNewKey(ex,winIndex);
		adjust(t,ex,winIndex);
		winIndex = t[0];
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	K_Merge();
	return 0;
}

假设 内存 一次 只能 载入 3 个数据(INT_MAX不算):

static int testArray[K][MEM_SIZE] = {

{10,15,16,INT_MAX},

{9,18,20,INT_MAX},

{20,22,40,INT_MAX},

{6,15,25,INT_MAX},

{12,37,48,INT_MAX},

};

最终 归并 这些 数据的 结果为:

时间: 2024-08-11 09:55:42

看数据结构写代码(66) 败者树的相关文章

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(57) AVL树的删除

上一节 已经说了 AVL树的插入 操作,可是 只有 插入,没有删除,怎么能叫 动态 查找表呢. 呵呵,博主 赶紧 去 研究了一番.下面 是成果: AVL树的删除 大致 分为 两大块: 1. 查找节点 并 删除 2. 保持 删除 后 平衡因子的 影响 1. 首先 找到 这个 节点,如果 节点 不存在,直接 退出 函数 if (*tree == NULL){//没找到 return false; } 2.如果 存在,分为 四种情况:(根 二叉 排序树的 删除 类似) 1.节点 为 叶子 节点,直接

看数据结构写代码(59) 键树的双链表示法

杂谈; 打败自己的 往往不是敌人,而是自己.坚持不易,且行且珍惜. 键树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高 键树的根节点不包含字符,除根节点外的非叶子节点都只包含一个字符,叶子节点存储具体信息.: 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串:

看数据结构写代码(54)次优查找树

查找顺序表时,若 每个元素的概率 都相等 用 二分查找 效率 最高.但是 如果 概率 不相等时,(SOST)静态最优查找表 效率 要高于 二分查找.静态最优查找表 是 使得 从 根 到 每个节点的路径 长度 和 权值 乘积 之和 最小. 书上说的 静态最优 查找树的创建 时间 复杂度 较高,所以 用 次优 查找树(NOST) 代替. 下面 上代码: // Nost.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <cstd

看数据结构写代码(34) 树与回溯法(二)排序树(8皇后问题)

套用回溯 公式程序: void backtrack (int t) { if (t > n) { // 到达叶子结点,将结果输出 output (x); } else { // 遍历结点t的所有子结点 for (int i = f(n,t); i <= g(n,t); i ++ ) { x[t] = h[i]; // 如果不满足剪枝条件,则继续遍历 if (constraint (t) && bound (t)) backtrack (t + 1); } } } 写出 8皇后问

看数据结构写代码(60 ) 键树的多重链表表示(Trie树)

trie树,是用 树的 多重链表来表示 树的.每个节点 有 d 个指针域.若从键树中的某个节点到叶子节点的路径上每个节点都只有一个孩子,则可以把 路径上的所有节点压缩成一个叶子节点,且在叶子节点中 存储 关键字 以及 根关键字相关的信息. 当节点的度 比较大时,选择 Trie树,要比 双链表树更为合适. tire树的 数据 压缩 是 挺与众不同的. 下面 给出 具体的 代码: 源代码工程文件网盘地址:http://pan.baidu.com/s/1cyTg6 // TrieTree.cpp :

看数据结构写代码(33) 树与回溯法(一) 子集树

回溯法 是 一种 在 穷举 中,裁剪 不满足 条件 的 分支,已达到 提高 效率的 方法.其基本原型 是 树的 先序遍历,从 树根 到 树叶的路径 是 问题的 一个 解. 回溯法的基本框架 =  确定 解空间 + 深度优先遍历 + 裁剪函数 + 确定结果函数 其中 解空间,分为 子集树 和 排序树. 具体 概念 详解:参考 点击打开链接  和 点击打开链接 递归算法通用 模板如下: 回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法. // 针对N叉树的递归回溯方法 void

看数据结构写代码(67) 置换 _ 选择排序(完结篇)

杂谈: 严蔚敏版<数据结构(C语言版)> 一书 终于看完了.这是 一个完结,也是 一个新的开端.<算法导论> 已到手. 置换选择排序的思想 是 将 归并段 尽量 变的 更大,而不是根据 内存 大小 限制在 固定的 大小. 这样 可以 利用赫夫曼树 来 进行 最优归并树,从而 使 外存 读写次数 最少. 下面给出 具体 代码:欢迎指出代码不足. // Replace_Selcetion.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环