一文读懂胜者树与败者树


胜者树和败者树是在排序和归并排序算法中常用的两种数据结构,它们在大规模数据排序中具有高效性和良好的稳定性。本篇博客将详细介绍这两种数据结构。

1.为什么要使用外部排序?

外部排序是用于对超出计算机内存容量的大型数据集进行排序的一种算法。在排序过程中,需要将数据集分成多个较小的子集,并在内存中对每个子集进行排序,然后再将排序后的子集合并起来。这种算法通常会利用硬盘等外部存储设备来协助处理数据,因此被称为“外部排序”。

以下是一些使用外部排序算法的理由:

  • 大规模数据集:当数据集太大,无法在计算机的内存中完全装入时,外部排序算法是一个很好的选择。例如,在处理大型数据库或超大规模文件时,通常需要使用外部排序算法。

  • 节约内存:当内存受限时,外部排序算法也是很有用的。例如,在移动设备等资源受限的计算机上运行排序操作时,使用外部排序算法可以避免占用过多的内存。

  • 并行处理:外部排序算法还可以通过将数据集分成多个块并对每个块进行并行处理来进一步提高性能。这意味着可以使用多个处理器或计算机来同时处理数据集,从而加快排序速度。

2.外部排序如何合并子集?

在外部排序算法中,合并子集是一个关键的步骤,这个过程通常是通过多路归并算法来实现的。

多路归并算法通常使用一个优先队列(也称为最小堆)来保存各个子集中的数据。在合并过程中,首先从各个子集中读取一个元素,并将它们插入到优先队列中。队列会自动将它们排序,因此队列顶端的元素是当前最小的元素。我们将队列顶端的元素取出,并将它插入到磁盘文件中。然后我们从该元素所在的子集中读取下一个元素,并将它插入到队列中,这样队列中的元素数保持不变。这个过程一直重复,直到所有元素都被读取出来,合并完成。

具体而言,合并子集的过程如下:

1.从每个子集中读取第一个元素,并将它们插入到一个优先队列中,以便从中选取最小元素。
2.从队列中取出最小的元素,并将它添加到一个磁盘文件中。
3.从包含该最小元素的子集中读取下一个元素,并将它插入到队列中。
4.重复步骤2和3,直到队列为空,所有元素都被读取出来。

这个算法保证了所有元素都会按照从小到大的顺序被写入到磁盘文件。虽然每个子集中的元素数量可能很大,但是每次只需要读取一个元素到内存中,并且优先队列的大小通常比每个子集要小得多,因此该算法能够处理非常大的数据集。

3.优先队列使用什么数据结构?

外部排序多路归并时,常用的数据结构有:

  • 胜者树
  • 败者树

面对这三个数据结构,应该选择哪一个呢?

4.堆

堆(Heap)是一种高效的优先级队列。

堆满足下列性质:

  • 堆是一棵完全二叉树。
  • 堆中某个结点的值总是不大于或不小于其父结点的值。

当堆中某个结点的值总是不小于父结点的值,为小顶堆,根结点最小。

当堆中某个结点的值总是不大于父结点的值,为大顶堆,根结点最大。

在这里插入图片描述

取出一个元素总是发生在堆顶,因为堆顶的元素是最小的(小顶堆中)。

然后使用堆中最后一个元素来填补空缺位置,然后对顶部元素进行下沉,如果左右孩子有比自己小的,则选择选择最小的那个进行交换。重复进行下沉操作,以满足堆的性质。

在这里插入图片描述

5.胜者树

胜者树(Winner Tree)是一种常用于排序和归并排序算法中的数据结构。

胜者树满足下列性质:

  • 胜者树一棵完全二叉树。其中的叶结点是要排序的元素,非叶结点是两个子结点中胜者的代表。
  • 根节点代表着所有元素中的胜者。

每个叶子节点相当于一个选手,每个中间节点相当于一场比赛,每一层相当于一轮比赛。

胜者树的一个优点是,如果一个选手的值改变了,可以很容易地修改这棵胜者树。只需要沿着从该结点到根结点的路径修改这棵二叉树,而不必改变其他比赛的结果。

下面是选择一个最小的数字为胜利者。

在这里插入图片描述

在下一次输出第二小的数字时候,只需要把 1 所在的数组里面的元素加进去,然后从叶子节点到根节点一直比较得出第二小的值,这样就减少了很多次无用的比较。

在这里插入图片描述

6.败者树

败者树(Loser Tree)是一种常用于排序和归并排序算法中的数据结构。

胜者树满足下列性质:

  • 胜者树一棵完全二叉树。其中的叶结点是要排序的元素,非叶结点是两个子结点中败者的代表。
  • 根节点代表着所有元素中的败者。

败者树是胜者树的一种变体,它也是一棵完全二叉树。和胜者树不同的是,败者树的节点存储的是败者。

在败者树中,用父结点记录其左右子结点进行比赛的败者,而让胜者参加下一轮的比赛。败者树的根结点记录的是败者,需要加一个结点来记录整个比赛的胜利者。

在这里插入图片描述

7.为什么要选择败者树

采用败者树可以简化重构的过程。

在用胜者树的时候,每个新元素上升时,首先需要获得父节点,然后再获得兄弟节点,然后再比较。

在使用败者树的时候,每个新元素上升时,只需要获得父节点并比较即可。

所以总的来说,减少了访存的时间。

其实现在程序的主要瓶颈在于访存了,计算倒几乎可以忽略不计了。

所以,外部排序多路归并时,优先队列应该使用败者树。


参考文献

OpenAI ChatGPT
数据结构- 堆的原理和常见算法问题 - 春水煎茶
数据结构:胜者树与败者树 - CSDN博客
堆,赢者树,败者树的区别与联系 - CSDN博客

### 胜者的概念 胜者是一种特殊的选择,其主要目的是快速找到一组数据中的最小值或最大值。它是一棵完全二叉,在叶子节点上存储待比较的数据项,而内部节点则保存其子节点之间的获胜者(即较小或较大值)。通过这种方式,根节点始终表示整个集合的最小值或最大值[^1]。 #### 特性 - **完全二叉**:胜者必须是一个完全二叉,这意味着除了最后一层外,其余各层都是满的,并且最后一层的节点都尽可能靠左排列。 - **存储位置**:实际参比较的数据存放在叶子节点中,而非叶子节点仅用于记录胜利者的索引。 - **功能**:主要用于高效地维护动态集合的最大值或最小值,常应用于锦标赛排序或其他需要频繁查询最值的场景。 ### 胜者的实现 以下是基于 Python 的简单胜者实现示例: ```python class WinnerTree: def __init__(self, data): self.n = len(data) # 叶子节点数量 self.tree_size = 2 * self.n - 1 # 总节点数 self.tree = [None] * self.tree_size # 初始化数组 self.data = data # 输入数据 self.build_winner_tree() def build_winner_tree(self): """构建胜者""" start = self.tree_size - self.n # 非叶节点起始位置 for i in range(start, self.tree_size): # 设置叶子节点 self.tree[i] = i - start for i in reversed(range(0, start)): # 自底向上填充非叶节点 left_child = 2 * i + 1 right_child = 2 * i + 2 if self.data[self.tree[left_child]] < self.data[self.tree[right_child]]: self.tree[i] = self.tree[left_child] else: self.tree[i] = self.tree[right_child] def get_min(self): """获取当前最小值""" winner_index = self.tree[0] return self.data[winner_index], winner_index def replace_and_rebuild(self, index, new_value): """替换指定索引处的值并重新调整胜者""" old_value = self.data[index] self.data[index] = new_value if new_value < old_value: parent = (index + self.n // 2 - 1) // 2 while parent >= 0: child_left = 2 * parent + 1 child_right = 2 * parent + 2 if self.data[self.tree[child_left]] < self.data[self.tree[child_right]]: self.tree[parent] = self.tree[child_left] else: self.tree[parent] = self.tree[child_right] parent = (parent - 1) // 2 ``` 上述代码定义了一个 `WinnerTree` 类来管理胜者的操作。其中包含了创建胜者、获取最小值以及更新某个元素后重建部分的方法。 ### 应用场景 胜者通常被用来解决以下问题: - 动态集合中最值的快速访问。 - 锦标赛排序算法的核心组件之一。 - 在多路归并过程中帮助选出每一轮的最小/最大值。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值