数据结构与算法C++描述(13)---竞赛树及其在箱子装载问题中的应用
2017-11-01 20:48
537 查看
1、竞赛树的相关概念
一般将竞赛树分为赢者树和输者树。所谓赢者树,就是对于n名选手,赢者树是一棵含n个外部节点,n-1个内部节点的完全二叉树,其中每个内部节点记录了相应赛局的赢家。同理,对于输者树,每个内部节点记录了相应赛局的输家。一个赢者树如下图所示。上图中,黑色框中字母a,b,c,…,h为选手编号,下面的数字为选手得分。
为了利用计算机更方便的描述赢者树,假定每个赢者树都是完全二叉树。
2、赢者树的公式化描述
利用完全二叉树的公式化描述方法来定义赢者树。n名选手(用e[1:n]表示)的赢者树需要n-1个内部节点(用t[1:n-1]表示)。如下图所示。下面探讨外部节点e[i]与其父节点t[p]间的关系。
根据完全二叉树的节点排列关系可知,最底层最左端的内部节点编号为2s,其中s=[log2(n−1)](向下取整)。由于共有n-1个内部节点,因此,最后一个内部节点编号为n-1。那么,最底层的内部节点数为(n−1)−2s+1=n−2s个。而最底层的外部节点数(称为LowExt)为内部节点数的2倍,即LowExt=2∗(n−2s)。若最底层的内部节点没有将该层填满,该层会存在外部节点,并且编号从LowExt+1开始。从而,该层外部节点个数为n−(LowExt+1)+1=n−LowExt。设中间变量offset=2(s+1)−1。对于外部节点e[i],与其父节点t[p]间满足如下函数关系:
3、赢者树的C++描述
对一个赢者树的操作主要包括:初始化赢者树(Initialize()函数实现);
获取最终的胜利者(Winner()函数实现);
获取在内部节点i比赛的获胜者(Winner(int i)函数);
若选手的得分发生改变,需重新进行比赛(RePlay()函数实现);
输出竞赛树(Output()函数实现)。
3.1 赢者树类声明及简单函数的实现
对于赢者树类,私有成员包括树的最大容量MaxSize、树的当前元素个数CurrentSize、最底层的外部节点数LowExt、中间变量offset、赢者树数组*t、选手数组 *e,已经在节点p进行比赛函数Play()。程序如下:/*-------------------------------竞赛树类--------------------------*/ template <class T> class WinnerTree { public: WinnerTree(int TreeSize = 10) { //构造赢者树 MaxSize = TreeSize; t = new int[MaxSize]; CurrentSize = 0; } ~WinnerTree() { delete[] t; }; //初始化赢者树,a为选手数组,size为选手数,Winner用于得到a[b]和a[c]之间的赢家 void Initialize(T a[], int size, int(*winner)(T a[], int b, int c)); //获取胜利者 int Winner()const { return (CurrentSize) ? t[1] : 0; } //返回在内部节点i比赛的赢者 int Winner(int i)const { return (i < CurrentSize) ? t[i] : 0; } //重新比赛 void RePlay(int i, int(*winner)(T a[], int b, int c)); //输出赢者树 void Output(ostream &out) { for (int i = 1; i < CurrentSize; i++) out << t[i]<< " "; cout << endl; } private: int MaxSize; //树的最大容量 int CurrentSize; //树的当前容量 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)); };
程序中的winner函数为获取数组a[]中,b、c两节点的赢者函数,以大者获胜为例,代码如下:
//获得a[b]和a[c]中的最大者,返回最大者的索引值 int winner(WinnerNode a[], int b, int c) { if (a[b].data >= a[c].data) //较大者获胜 return b; else return c; }
程序中WinnerNode类为赢者树节点类,描述了每个节点的索引值和数据值。
/*-----------------------------竞赛树节点类--------------------------*/ template <class T> class WinnerTree; class WinnerNode { //声明友元函数,实现winner对类成员的访问 friend int winner(WinnerNode [], int, int); friend void main(void); friend void FirstFitPack(int [], int, int); private: int key, //索引值 data; //数据值 };
3.2 比赛函数—Play()函数
函数模拟节点p处的比赛,lc和rc是t[p]的左右孩子。//比赛,在t[p]处开始比赛,lc和rc是t[p]的孩子 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; //到父节点 } }
3.3 初始化赢者树—Initialize()函数
函数将一个元素为赢者树节点的数组a[],转化成一个赢者树。依据公式(1),遍历所有的内部节点。根据winner函数,给出每个内部节点的比赛结果。最终,形成一个赢者树。//初始化赢者树,a为选手数组,size为选手数,Winner用于得到a[b]和a[c]之间的赢家 template <class T> void WinnerTree<T>::Initialize(T a[], int size, int(*winner)(T a[], int b, int c)) { if (size > MaxSize || size < 2) throw BadInput(); CurrentSize = size; e = a; //计算s=2^log(n-1),赢者树中最后一个元素的编号 int i, s; for (s = 1; 2 * s <= CurrentSize - 1; s += s); LowExt = 2 * (CurrentSize - s); //最外层选手数 offset = 2 * s - 1; //中间变量 //最外层外部节点的比赛 for (i = 2; i <= LowExt; i += 2) //选取右孩子进行比赛 Play((offset + i) / 2, i - 1, i, winner); //外部其余节点间进行比赛 if (CurrentSize % 2) //当n为奇数时,内部节点和外部节点进行比赛 { Play(CurrentSize / 2, t[CurrentSize - 1], LowExt + 1, winner); i = LowExt + 3; //下一个外部节点 } else i = LowExt + 2; //若n为偶数,加2后为右孩子 //i为右边剩余节点 for (; i <= CurrentSize; i += 2) Play((i - LowExt + CurrentSize - 1) / 2, i - 1, i, winner); }
3.4 重新比赛—RePlay()
选手i的比分改变后,从该选手的父节点开始,重新进行比赛,调整赢者树中的内部节点。//选手i的值改变后,重新比赛 template <class T> void WinnerTree<T>::RePlay(int i, int(*winner)(T a[], int b, int c)) { if (i <= 0 || i > CurrentSize) throw OutOfRange(); int p, //比赛节点 lc, //p的左孩子 rc; //p的右孩子 //找到第一个比赛节点及其子女 if (i <= LowExt) //若i位于最外层 { p = (offset + i) / 2; //p在竞赛树中的位置 lc = 2 * p - offset; //p的左孩子 rc = lc + 1; //p的右孩子 } else //p不在最外层 { p = (i - LowExt + CurrentSize - 1) / 2; if (2 * p == CurrentSize - 1)//与竞赛树中内部节点比赛 { lc = t[2 * p]; //左孩子 rc = i; //右孩子 } else { //左孩子 lc = 2 * p - CurrentSize + 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]); }
3.4 重载操作符”<<”
//重载操作符"<<" template <class T> ostream &operator<<(ostream &out, WinnerTree<T> &WT) { WT.Output(out); return out; }
3.5 测试
int a[] = { 4,2,5,7,10,13,3,6,11,8 }; WinnerTree<WinnerNode> WT; WinnerNode element[20]; for (int i = 1; i <= 10; i++) element[i].data = a[i - 1]; WT.Initialize(element, 10, winner); cout << "当前选手中,获胜者的编号为:" << WT.Winner() << endl; cout << "输出赢者树" << WT; element[2].data = 12; WT.RePlay(2, winner); cout << "当前选手中,获胜者的编号为:" << WT.Winner() << endl; cout << "输出赢者树" << WT;
测试结果:
4、箱子装载问题的最先匹配算法
在箱子装载问题中,有若干个容量为c的箱子和n个待装入箱子中的物品。物品i需占s[i]个单元(0<s[i]<=c)。所谓成功装载,是指能把所有物品都装入箱子而不溢出,而最优装载是指使用了最少箱子的成功装载。最先匹配算法是指:物品按1,2,3,…,n的顺序装入箱子。假设箱子从左到右排列。每一物品i放入可承载它的最左箱子。
利用赢者树实现最先匹配算法时,假设有n个箱子,每个箱子的初始容量相同,都为c。
4.1 算法流程图如下:
4.2 代码如下:
//放置箱子的最先匹配算法,s[]为各物品所需要的空间,n为物品数量,c为箱子容量 void FirstFitPack(int s[],int n,int c) { WinnerTree<WinnerNode> *W = new WinnerTree<WinnerNode> ; WinnerNode *avail = new WinnerNode[n+1]; //箱子 //初始化n个箱子和赢者树 for (int i = 1; i <= n; i++) avail[i].data = 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].data < s[i]) //左子树容量小于物品大小 p++; p *= 2; //移动到其左孩子 } int b; p /= 2; //当前节点索引 if (p < n) //在内部节点 { b = W->Winner(p); //若b是右孩子,需要检查箱子b-1 if (b > 1 && avail[b - 1].data >= s[i]) b--; } else //p==n,即物品数为奇数,有一个颗树只有左孩子,取其根节点的赢者 b = W->Winner(p / 2); cout << "物品 " << i << " 放入箱子 " << b << endl; avail[b].data -= s[i]; //更新箱子容量 W->RePlay(b, winner); //重新生成竞赛树 } }
4.3 测试
/*-------------------------------箱子放置的最先匹配算法----------------------------*/ int n, c; //n为物品个数,c为箱子容量 cout << "请输入待放置的物品个数n: "; cin >> n; cout << "请输入箱子的容量c: "; cin >> c; int *s = new int[n + 1]; //各物品所需的放置空间 cout << "请输入各物品所需空间: "; for (int i = 1; i <= n; i++) { cin >> s[i]; if (s[i] <= 0 || s[i]>c) throw BadInput(); } FirstFitPack(s, n, c);
测试结果:
参考:
[1] 数据结构算法与应用:C++描述(Data Structures, Algorithms and Applications in C++ 的中文版)
相关文章推荐
- Eclipse下开发QT4应用,看不见添加的控件的问题及其解决方法
- Web应用开发过程中常见的几个中文问题及其解决方法
- 算法之递推及其应用(递推关系的建立及在信息学竞赛中的应用 安徽 高寒蕊)
- C/C++刁钻问题各个击破之位运算及其应用实例(1)
- 数组字符串问题------求数组前k小的元素,及其应用
- 【算法竞赛入门经典】树的最大独立集、树的唯一性问题 例题9-13 UVa1220
- 【常见Web应用安全问题】---13、Blind SQL/XPath injection
- MongoDB 3: 使用中的问题,及其应用场景
- 【学习笔记----数据结构13-哈夫曼树及其应用】
- sizeof的应用及其问题
- 13.赫夫曼树及其应用
- Android_AsyncTask详解及其应用(三)_图片错位以及AsyncTask重复创建的问题
- 数据结构与算法(Java描述)-7、链式堆栈及其栈的应用
- 【常见Web应用安全问题】---13、Blind SQL/XPath injection
- 线性表的应用——求解两个多项式相加问题描述(顺序表求解)
- 【Jason's_Knowledge】算法竞赛中的快速排序及其应用
- 栈及其应用 - C语言实现(摘自数据结构与算法分析 C语言描述)
- MongoDB 3: 使用中的问题,及其应用场景
- [LeetCode] “全排列”问题系列(一) - 用交换元素法生成全排列及其应用,例题: Permutations I 和 II, N-Queens I 和 II,数独问题
- 图的点着色、区间着色问题及其应用(基于贪心思想的DFS回溯法求点着色问题和区间着色算法求解任务调度问题)