(1小时数据结构)数据结构c++描述(二十一) --- 竞赛树及其装箱应用

赢者树概念:

        对于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]表示)。如下图所示。  

     根据完全二叉树的节点排列关系可知,最底层最左端的内部节点编号为2^s,其中s=[log2(n−1)](向下取整)。因此
最底层的内部节点数为 n-2^{s} ,最底层的外部节点数 LowExt 为内部节点数的 2倍,  当n = 5, s = 2,t[4] 为最左端的内部节点,
     也是t[2^{s}]。对于任何外部节点 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);
}

测试结果:

喜欢我博客的小伙伴,可以关注我博客,一起来好好学习吧。
 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值