赢者树概念:
对于n 名选手,赢者树是一棵含n 个外部节点, n-1个内部节点的完全二叉树,其中每个内部节点记录了相应赛局的赢家。
图型解析:
为决定一场比赛的赢家,假设每个选手有一得分且赢者取决于对两选手得分的比较。在最小赢者树(min winner tree)中,得分小的选手获胜;同理,在最大赢者树(max winner tree)中,得分大的选手获胜。有时,也可用左孩子对应的选手代表赢家节点。图 10-2a 给出含8名选手的最小赢者树,而图10-2b 给出含5名选手的最大赢者树。每个外部节点之下的数字表示选手得分。
赢者树的公式化描述:
利用完全二叉树的公式化描述方法来定义赢者树。n名选手(用e[1:n]表示)的赢者树需要n-1个内部节点(用t[1:n-1]表示)。如下图所示。
根据完全二叉树的节点排列关系可知,最底层最左端的内部节点编号为,其中s=[log2(n−1)](向下取整)。因此
最底层的内部节点数为 n- ,最底层的外部节点数 LowExt 为内部节点数的 2倍, 当n = 5, s = 2,t[4] 为最左端的内部节点, 也是t[]。对于任何外部节点 e [ i ],其父节点 t [ p ]满足以下公式:
赢者树代码描述:
/*
数组结构中 竞赛树 赢者树
对应书中代码:数据结构算法与应用c++描述
程序编写:比卡丘不皮
编写时间:2020年7月22日 10:42:17
*/
#pragma once
#include "all_error.h"
#include <iostream>
using namespace std;
template<class T>
int winner(T a[], int b, int c)
{
if (a[b] > a[c])
{
return b;
}
else
{
return c;
}
}
template<class T>
class WinnerTree
{
public:
WinnerTree(int TreeSize = 10) //初始化函数
{
//构造赢者树
MaxSize = TreeSize;
t = new int[MaxSize];
n = 0;
}
~WinnerTree() { delete[] t; } //析构函数
void Initialize(T a[], int size, int (*winner)(T a[], int b, int c));
int Winner() const { return (n) ? t[1] : 0; }
int Winner(int i) const { return (i < n) ? t[i] : 0; }
void RePlay(int i, int(*winner) (T a[], int b, int c));
//输出赢者树
void Output(ostream &out)
{
for (int i = 1; i < n; i++)
out << t[i] << " ";
cout << endl;
}
private:
int MaxSize; // 允许的最大选手数
int n; //当前大小
int LowExt; //最底层的外部节点
int offset; //2^k-1
int *t; //赢者树数组
T *e; //元素数组
void Play(int p, int lc, int rc, int(*winner)(T a[], int b, int c));
};
Initialize构造函数:
template<class T>
void WinnerTree<T>::Initialize(T a[], int size, int(*winner)(T a[], int b, int c))
{
// 对于数组a初始化赢者树
if (size > MaxSize || size <2)
{
throw OutOfBounds();
}
n = size;
e = a;
// 计算 s = 2 ^ log(n - 1) 赢者树中最后一个元素的编号
int s, i;
for (s = 1; 2 * s <= n - 1; s += s); //
LowExt = 2 * (n - s); //最外层选手数
offset = 2 * s - 1; //中间变量
// 最底层外部节点的比赛
for (i = 2; i <= LowExt; i += 2)
Play((offset + i) / 2, i - 1, i, winner);
// 处理其余外部节点
if (n % 2) //当n奇数时,内部节点和外部节点的比赛
{
Play(n / 2, t[n - 1], LowExt + 1, winner);
i = LowExt + 3; //下一个外部节点
}
else
{
i = LowExt + 2; //若n为偶数,加2后为右孩子
}
// i为右边剩余节点
for (; i <= n; i += 2)
Play((i - LowExt + n - 1) / 2, i - 1, i, winner);
}
Play函数定义:
template<class T>
void WinnerTree<T>::Play(int p, int lc, int rc, int(*winner)(T a[], int b, int c))
{
t[p] = winner(e, lc, rc);
// 若在右孩子处,则可能有多场比赛
while (p > 1 && p % 2)
{
t[p / 2] = winner(e, t[p - 1], t[p]);
p /= 2; //到父节点
}
}
重载<< 函数:
template<class T>
ostream & operator << (ostream & out, WinnerTree<T>& x)
{
x.Output(out);
return out;
}
RePlay函数:
template<class T>
void WinnerTree<T>::RePlay(int i, int(*winner)(T a[], int b, int c))
{
//针对元素i重新组织比赛
if (i<=0 || i>n)
{
throw OutOfBounds();
}
int p, //比赛节点
lc, //p的左孩子
rc; //p的右孩子
//找到第一个比赛节点及其子女
if (i <= LowExt) //从最底层开始
{
p = (offset + i) / 2; //p在竞赛树中的位置
lc = 2 * p - offset; //p的左孩子
rc = lc + 1; //p的右孩子
}
else //p不在最外层
{
p = (i - LowExt + n - 1) / 2;
if (2 * p == n-1)
{
lc = t[2 * p];
rc = i;
}
else
{
lc = 2 * p - n + 1 + LowExt;
rc = lc + 1;
}
}
t[p] = winner(e, lc, rc);
//剩余节点的比赛
p /= 2; //移到其父节点
for (; p >= 1; p /= 2)
t[p] = winner(e, t[2 * p], t[2 * p + 1]);
}
测试函数:
void testWinnerTree()
{
int a[] = { 0,4,2,5,7,10,13,3,6,11,8 };//a[1]开始计算的
WinnerTree<int> WT(10);
WT.Initialize(a, 10, winner);
cout << "当前选手中,获胜者的编号为:" << WT.Winner() << endl;
cout << "输出赢者树" << WT;
a[2] = 12;
WT.RePlay(2, winner);
cout << "当前选手中,获胜者的编号为:" << WT.Winner() << endl;
cout << "输出赢者树" << WT;
}
测试结果:
箱子装载问题的最先匹配算法
在箱子装载问题中,有若干个容量为c的箱子和n个待装入箱子中的物品。物品i需占s[i]个单元(0<s[i]<=c0<s[i]<=c)。所谓成功装载,是指能把所有物品都装入箱子而不溢出,而最优装载是指使用了最少箱子的成功装载。
最先匹配算法是指:物品按1,2,3,…,n的顺序装入箱子。假设箱子从左到右排列。每一物品i放入可承载它的最左箱子。
利用赢者树实现最先匹配算法时,假设有n个箱子,每个箱子的初始容量相同,都为c。
移动过程图:
放入数据为:8 6 5 2 5 4 8 10 3 5
当为8时,放第一个箱子里,箱子里还剩2,当6放入到第二个箱子里,余4,对应使用记录的是3号
代码过程:
/*
数组结构中 竞赛树 赢者树
对应书中代码:数据结构算法与应用c++描述
程序编写:比卡丘不皮
编写时间:2020年7月22日 16:49:03
*/
#pragma once
#include <iostream>
#include "WinnerTree.h"
using namespace std;
void FirstFitPack(int s[], int n, int c)
{
WinnerTree<int> * W = new WinnerTree<int>(n);
int *avail = new int[n + 1];
// 初始化n个箱子和赢者树
for (int i = 1; i <= n; i++)
avail[i] = c; //初始可用的容量
W->Initialize(avail, n, winner);
// 将物品放入箱子中
for (int i = 1; i<=n; i++)
{
// 找到有足够容量的第一个箱子
int p = 2; //从根的左子树开始查询
while (p < n)
{
int winp = W->Winner(p);
if (avail[winp] < s[i])
{
p++;
}
p *= 2;//移到左孩子
}
int b;
p /= 2; //当前节点索引
if (p < n)
{
//在一树节点处
b = W->Winner(p);
// 若b是右孩子,需要检查箱子 b - 1。
// 即使b是左孩子,这种检查也没有什么副作用
if (b>1 && avail[b-1] >= s[i])
{
b--;
}
}
else //p==n,即物品数为奇数,有一个颗树只有左孩子,当n为奇数时
{
b = W->Winner(p / 2);
}
cout << "Pack object " << i << " in bin " << b << endl;
avail[b] -= s[i]; //更新可用容量
W->RePlay(b, winner);
}
}
测试函数:
void testFirstFitPack()
{
int n, c; // 物品数量和箱子容量
cout << "Enter number of objects and bin capacity" << endl;
cin >> n >> c;
int *s = new int[n + 1]; //各物品所需的放置空间
for (int i = 1; i<=n; i++)
{
cout << "Enter space requirement of object " << i << endl;
cin >> s[i];
if (s[i]>c)
{
cout << "Object too large to fit in a bin" << endl;
exit(0);
}
}
FirstFitPack(s, n, c);
}
测试结果:
喜欢我博客的小伙伴,可以关注我博客,一起来好好学习吧。