由一道算法题是怎样想到DOTA和哈夫曼的

前几天看到有人转载的这样一道算法题:
/***************************************************************
有两个序列a,b,大小都有n,序列元素的值任意整数,无序;
要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b无素的和]之间的差最小;
例如:
  var a = [100, 99, 98, 1, 2, 3];
  var b = [1, 2, 3, 4, 5, 40];
***************************************************************/
大致思路是,先将两个数组快排得到:
1  2  3  98  99  100
1  2  3    4   5    40
然后归并成一个数组c:
1  1  2  2  3  3  4  5  40  98  99  100
然后从c中按顺序取值分别给数组a和b,同时注意,如果上次a分到了一个较大的数,那么下次就给它分配一个小一点的数,反之也是。

本来是想说一个母亲分给两个孩子零花钱或蛋糕的,但是钱币的值就那么几个,蛋糕的量不好得出来。还是用两个贼分赃比较形象:
两个贼要分偶数件物品,两人得的件数要相等,同时要分的价值尽量接近。
嗯,题目就是这么个意思。可是为什么一定要件数相等呢,似乎有点不和情理。还是过了大约半天之后,偶然再想想这个问题的时候,想到了天梯系统——就是在11上打DOTA的天梯系统。哈哈,我居然能把这题目想到游戏上去!现在类似于DOTA的LOL好像也有天梯系统,看来天梯系统是对战游戏大势所趋的了。为什么呢?公平,好像是这么一回事。不就是这道题么?两队人每边五个一共十个,每个人的等级或积分就是这数组里的值,把数组里的值的和分配的尽可能接近,不就是把两队人的实力分配得尽可能接近么?

图片

    网上有个平衡集合的问题,好像也是这么回事,只是它一开始就是一个数组,当然是偶数个数的元素。用的所谓的,回溯搜索法。从数组中取出一半的数,剩下的就是另一半的数。把所有的情况都找一遍,得到的差值最小的就是答案。是的这种相当于穷举的方法,在数不多的情况下还是蛮好用的。但是如果数很多的话,是无法完成的,时间复杂度已经超过指数级了。

除非是在天梯系统中,只有十个数字要处理,否则,应该想别的方法。还是说到两个贼分赃吧:
要两个人得到的件数一样多,把东西都放在那里,以回合制的方式进行:第一回合分别把最大的和第二大的给两个人,下一回合把剩下的最大的给已得的总和小的那个人。
具体怎么弄,我是用双端队列deque来实现的:
例如有一个数组:89,62,42,38,21,20,16,14,13,2将按顺序放入队列。

 

图片

 当然这只是个近似算法,但是在数据个数较多,分配较均匀的情况下,可以很快的找出比较精确的结果:

图片

/**********************************************用双端队列实现的近似算法******************************************************************/

#include<iostream>

#include<deque>

using namespace std;

deque<int> allnum;

void mysort(deque<int>& allnum,int a[],int lenth);

void display(int a[],int lenth);

int distribute(deque<int>& allnum,int a[],int b[]);

int main()

{

int a[]={176,781,3332,214,7685,11362,67,100,899,10000,32,114,5,112,67,100,211,221,421,215,212,67,100,1000,232,421,578,12,67,100};

int b[]={72,36673,3,23,22240,45,98,1990,777,211,112,334,215,12,67,100,143,212,214,521,612,67,100,1321,442,214,875,1112,67,100};

int n=30;

//int a[]={89,13,14,2,16};

//int b[]={42,62,21,20,38};

//int n=5;

mysort(allnum,a,n);

mysort(allnum,b,n);

cout<<"A数组:"<<endl;

display(a,n);

cout<<"B数组:"<<endl;

display(b,n);

cout<<"所有数平移后排序:"<<endl;

for(deque<int>::iterator iter=allnum.begin();iter!=allnum.end();++iter)

{

cout<<*iter<<' ';

}

cout<<endl;

cout<<"进行分配..."<<endl;

int dis=distribute(allnum,a,b);

cout<<"分配完成:"<<endl;

cout<<"A数组:"<<endl;

display(a,n);

cout<<"B数组:"<<endl;

display(b,n);

cout<<"两数组和数之差:"<<endl;

cout<<dis<<endl;

return 0;

}


void display(int a[],int lenth)

{

for(int i=0;i<lenth;i++)

{

cout<<a[i]<<' ';

}

cout<<endl;

}

int distribute(deque<int>& allnum,int a[],int b[])

{

int dis=0,index=0;

while(!allnum.empty())

{

int fpb=allnum.front()-allnum.back();//最大数与第二大数之差

int fpt=allnum.front()-allnum[1];//最大数与最小数之差

if(dis<0)//a中和数比b中小,a取最大数,b取较小数

{

if(abs(fpb+dis)<abs(fpt+dis))

{//dis为负数,用+dis表示差,用abs求绝对值

dis+=a[index]=allnum.front();

allnum.pop_front();

dis-=b[index]=allnum.back();

allnum.pop_back();

}

else

{

dis+=a[index]=allnum.front();

allnum.pop_front();

dis-=b[index]=allnum.front();

allnum.pop_front();

}

}

else//a中和数不比b中小,b取最大数,a取较小数

{

if((abs(fpb-dis)<abs(fpt-dis)))

{

dis-=b[index]=allnum.front();

allnum.pop_front();

dis+=a[index]=allnum.back();

allnum.pop_back();

}

else

{

dis-=b[index]=allnum.front();

allnum.pop_front();

dis+=a[index]=allnum.front();

allnum.pop_front();

}

}

index++;

}

return dis;

}

void mysort(deque<int>& allnum,int a[],int lenth)//创建顺序链表,从大到小的顺序

{

int i;

for(i=0;i<lenth;i++)

{

if(allnum.empty())

{

allnum.push_back(a[i]);

}

else

{

deque<int>::iterator iter=allnum.begin();

while(iter!=allnum.end())

{

if(*iter<a[i])

{

allnum.insert(iter,a[i]);

break;

}

++iter;

}

if(iter==allnum.end())

{

allnum.push_back(a[i]);

}


}

}

}

 /**********************************************用双端队列实现的近似算法******************************************************************/



好吧,当然应该有其它更好的方法来实现。但是说回到这个问题上来,要是不是天梯分配,只是两个贼分赃或其它情况,为什么一定要数量相等呢?

这样就产生了一个新的问题:

一个数组分成两个数组,使两个新数组之和尽量接近。两个数组元素不一定要数量相同。

例如数组:int num[]={34,32,234,221,122,152,133,56,213,623};可以分成{34,56,213,234,221,152}和{32,122,623,133}.两组和都为910.

当这个新问题在脑海中产生的时候,觉得和那个问题也没多大区别。

就在一个晚上在图书馆看书时,突然想到了二叉树,又想到了哈夫曼,立刻关上书。拿出笔在纸上画了一下,很快就有了思路:

 

图片

/*******************************************************************哈夫曼二叉树实现划分数组*********************************************************/

#include<iostream>

#include<deque>

using namespace std;

template<typename T>

class BinTreeNode//二叉树结点类的定义

{

typedef BinTreeNode<T>* nodepoint;

T data;//结点的数据域

nodepoint lchild;//左孩子指针

nodepoint rchild;//右孩子指针

nodepoint parent;//双亲指针

public:

BinTreeNode(){parent=NULL;lchild=NULL;rchild=NULL;}

BinTreeNode(T e)//通过元素值创建结点

:data(e),parent(NULL),lchild(NULL),rchild(NULL){}

T getdata(){return data;}

nodepoint getlchild(){return lchild;}

nodepoint getrchild(){return rchild;}

nodepoint getparent(){return parent;}

bool linklchild(nodepoint l)

{

if(lchild!=NULL)return false;

lchild=l;

l->parent=this;

return true;

}

bool linkrchild(nodepoint r)

{

if(rchild!=NULL)return false;

rchild=r;

r->parent=this;

return true;

}

};

int comparer(const void* number1, const void* number2)

{

return *(int*)number1 - *(int*)number2;//懒得自己写排序了,直接用qsort快排了

}

typedef BinTreeNode<int> mytree;

deque<int> one,two;// 两个队列用来存放最后结果

void display(mytree* tree,bool isone);//后序遍历二叉树,将结果保存到两个数组

int main()

{

deque<mytree*> pointlist;//二叉树结点指针队列

//int num[]={34,32,234,656,433,56,232,121,34,98,100,450,34,32,353,988,777,65,433,98,1111,983,645,232};//原数组

//int n=24;//数组长度

int num[]={34,32,234,221,122,152,133,56,213,623};

int n=10;

qsort(num,n,sizeof(int),comparer);//用快速排序从小到大排序num数组

int i;

cout<<"排序后的数组:"<<endl;

for(i=0;i<n;i++)

{

cout<<num[i]<<' ';

}

cout<<endl;

for(i=n-1;i>=0;i--)

{

mytree* temp=new mytree(num[i]);//为数组创建二叉树结点,将结点指针保存在队列中

pointlist.push_back(temp);//指针队列中是按从大到小排列的

}

while(pointlist.size()>1)//构建哈夫曼树,直到队列中只有一个结点,即为根结点

{

int dis=pointlist[0]->getdata()-pointlist[1]->getdata();//求最大两数的差

mytree* temp=new mytree(dis);//为差创建新结点

temp->linklchild(pointlist[0]);//最大数作为新结点的左孩子

temp->linkrchild(pointlist[1]);//第二大数作为新结点的右孩子

pointlist.pop_front();

pointlist.pop_front();//将两数出列

deque<mytree*>::iterator b=pointlist.begin(),e=pointlist.end();

for(;b!=e;++b)

{

if((*b)->getdata()<temp->getdata())

{

pointlist.insert(b,temp);

break;

}//在队列中找到比新结点小的元素,将新结点插入其前面。

}

if(b==e)pointlist.push_back(temp);//所有元素都比新结点大,将新结点插入队尾

}

mytree* root=pointlist[0];//将根结点取出

display(root,true);//将树上结点的元素分配到两数组中

int onesum=0,twosum=0;//用于计算两数组之和

cout<<"第一组数:"<<endl;

for(int k=0;k<one.size();k++)

{

onesum+=one[k];

cout<<one[k]<<' ';

}

cout<<endl;

cout<<"第一组数总和:"<<endl;

cout<<onesum<<endl;

cout<<"第二组数:"<<endl;

for(int j=0;j<two.size();j++)

{

twosum+=two[j];

cout<<two[j]<<' ';

}

cout<<endl;

cout<<"第二组数总和:"<<endl;

cout<<twosum<<endl;

cout<<"两组数总和之差:"<<endl;

cout<<root->getdata()<<endl;

return 0;

}

void display(mytree* tree,bool isone)

{

if(tree->getlchild()!=NULL)display(tree->getlchild(),isone);//左子树

if(tree->getrchild()!=NULL)display(tree->getrchild(),!isone);//右子树反转

if(tree->getlchild()==NULL&&tree->getrchild()==NULL)//该结点为叶子结点,没有子树

{

if(isone)one.push_back(tree->getdata());

else two.push_back(tree->getdata());

}

}

/*******************************************************************哈夫曼二叉树实现划分数组*********************************************************/ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值