一个有趣的问题,足球比赛场次安排!

来源:互联网 发布:博客软件app 编辑:程序博客网 时间:2024/04/28 05:06

这是我在论坛上回的一个帖子,感觉很有意思就在这里保存下来,以免丢失!

原贴地址: http://community.csdn.net/Expert/topic/3964/3964967.xml?temp=.7246057

提问我稍加更改):

一共有n个足球队或更多,进行比赛,每两个球队之间有且只有一次比赛,
每天最大限度可以比 n/2 场,而且每组在一天内不能重复比赛
要求最短的时间内将各队全部的比赛结束!

我的解答:

假设足球队为1,2,3,4...,n.  那么总共的比赛场次为n*(n-1)/2.

如果球队数是奇数,则增加一个虚拟队,把球队数凑成偶数,这样,碰见虚拟队的球队相当于当场没有球赛.

那么现在可以推测出来一天最多只有n/2场球赛,否则肯定有球队一天超过一场球赛.组合上,也可以得到,肯定可以实现一天进行n/2场球赛,并且符合要求!

现在我们通过计算来得到一个赛程的安排,(这种安排可以有多种,这里只得到其中一组).先来看一个例子:

//例:6个球队,则输出这样内容,每组必须无重复
//则函数计算出5行,每行3对,2个一对,无重复的组合

1-2,3-4,5-6
2-4,3-5,1-6
3-2,4-6,1-5
4-5,3-1,2-6
5-2,1-4,3-6

我提供的算法主要思路就是从如下无重复的组合中提取一个组合,同时判断是否符合要求

(1,2),(1,3),(1,4),(1,5),(1,6)
(2,3),(2,4),(2,5),(2,6)
(3,4),(3,5),(3,6)
(4,5),(4,6)
(5,6)

具体见下面的算法步骤:
输入num(偶数)时,
用(x,y)模式表示x-y
弄一个邻居表TA,如下是(1,2,3,4,…,i,i+1,…num)的所有二位组合:
p1----> (1,2),(1,3),(1,4)…,(1, num)
p2----> (2,3),(2,4)…(2, num)
p3----> (3,4),…,(3, num)

pi---->(i,i+1),…,(i,num)

p(num-1)----> (num-1,num)
寻找一组的步骤如下:

初始化:定义集合SA,用来存储已经被包含的x,y,初始化SA={},
注意:未处理(x1,y1):表示(x1,y1)没有被尝试地包含在SA中,所谓尝试,是因为即使被包含进去,也可能被回滚掉!

第1步,取p1中第一个未处理(1,y1),SA ={1,y1};

第2步,如果2在SA中,进入第3步,
否则如果p2存在未处理(2,y2),取p2中第一个未处理(2,y2),SA =SA+{2,y2};
否则如果p2不存在未处理(2,y2),去掉最后加入SA的两个数,然后返回到最后加入元素到SA的那一步;

第3步,如果3在SA中,进入第4步,
否则如果p3存在未处理(3,y3),取p3中第一个未处理(3,y3),SA =SA+{3,y3};
否则如果p3不存在未处理(3,y3),去掉最后加入SA的两个数,然后返回到最后加入元素到SA的那一步;

第i步,如果i在SA中,进入第i+1步,
否则如果pi存在未处理(i,yi),取pi中第一个未处理(i,yi),SA =SA+{i,yi};
否则如果pi不存在未处理(i,yi),去掉最后加入SA的两个数,然后返回到最后加入元素到SA的那一步;


第num-1步,如果num-1在SA中,进入第num步,
否则如果p(num-1)存在未处理((num-1),y(num-1)),取p(num-1)中第一个未处理((num-1),y(num-1)),SA =SA+{(num-1),y(num-1)};
否则如果p(num-1)不存在未处理((num-1),y(num-1)),去掉最后加入SA的两个数,然后返回到最后加入元素到SA的那一步;

第num步,如果SA还未全包括{1,2,3,4, …,i,i+1,…num},去掉最后加入SA的两个数,然后返回到最后加入元素到SA的那一步;
否则,假设SA={x1,y1,x2,y2,…},则从邻居表中删除(x1,y1),(x2,y2),…;然后到初始化重新开始寻找下一组,直到邻居表中没有元素!

再给你一个例子:
当为6时,
邻居表,
p1----> (1,2),(1,3),(1,4),(1,5),(1,6)
p2----> (2,3),(2,4),(2,5),(2,6)
p3----> (3,4),(3,5),(3,6)
p4----> (4,5),(4,6)
p5----> (5,6)

找第1组过程:
第1步,p1取(1,2),SA={1,2};
第2步, 2在SA中,转下一步;
第3步,p3取 (3,4),SA={1,2,3,4};
第4步,4在SA中,转下一步;
第5步,p5取 (5,6),SA={1,2,3,4,5,6};
第6步,得到第一组(1,2),(3,4),(5,6),
从邻居表中删除(1,2),(3,4),(5,6),邻居表变为
p1----> (1,3),(1,4),(1,5),(1,6)
p2----> (2,3),(2,4),(2,5),(2,6)
p3----> (3,5),(3,6)
p4----> (4,5),(4,6)

找第2组过程:
第1步,p1取(1,3),SA={1,3};
第2步, p2取 (2,4),SA={1,3,2,4};
第3步,3在SA中,转下一步;
第4步,4在SA中,转下一步;
第5步,p5不存在未处理(5,y5),去掉最后加入SA的两个数,SA={1,3};然后返回到最后加入元素到SA的第2步,
第2步, p2取 (2,5),SA={1,3,2,5};
第3步,3在SA中,转下一步;
第4步,p2取 (4,6), SA={1,3,2,5,4,6};
第5步,5在SA中,转下一步
第6步,得到第二组(1,3),(2,5),(4,6),
从邻居表中删除(1,3),(2,5),(4,6),邻居表变为
p1----> (1,4),(1,5),(1,6)
p2----> (2,3),(2,4)(2,6)
p3----> (3,5),(3,6)
p4----> (4,5)

找第3组过程:

找第4组过程:

找第5组过程:

p1----> (1,6)
p2----> (2,3)
p3---->
p4----> (4,5)

可以找到5组为
(1,2),(3,4),(5,6)
(1,3),(2,5),(4,6)
(1,4),(2,6),(3,5)
(1,5),(2,4),(3,6)
(1,6),(2,3),(4,5)

结果正确吧?

以下是c++代码的模拟实现算法:


//引用////////////////////////////////////////////////////////////////////
#include <list>
#include <iostream>
using namespace std ;
//////////////////////////////////////////////////////////////////////

//类定义(start)///////////////////////////////////////////////
//(x,y)模式
class CPair{
public:
 int x;
 int y;
 int iMode;//iMode=0--未处理
           //iMode=1--处理过
           //iMode=2--已删除
public:
 CPair():x(0),y(0),iMode(0){ };
 CPair(const CPair& cp)
 {
  x = cp.x;
  y = cp.y;
  iMode = cp.iMode;
 };
public:
 CPair& operator=(CPair& cp)
 {
  x = cp.x;
  y = cp.y;
  iMode = cp.iMode;
  return *this;
 }
 void Print()
 {
  cout<<"("<<x<<","<<y<<")";
 }; 
};

typedef list<int> LISTINT;
typedef list<CPair> LISTPAIR;
typedef list<CPair*> LIST_PPAIR;
//结构定义(end)///////////////////////////////////////////////

//函数声明(start)///////////////////////////////////////////////
void InitNBTable(int iNum);
void FreeNBTable();
bool ExistInSA(int iTeamNum);
void ErazeTempMode();
void GetOneLine();
void GetAllLine(int iNum);
//函数声明(end)///////////////////////////////////////////////

//全局变量(start)///////////////////////////////////////////////
LISTPAIR* gl_pNBTanle;//存储邻居表
int       gl_iTeamCount;//球队数
LIST_PPAIR  gl_lstPair_SA; //集合SA
LISTINT   gl_lstInt_Steps;//记录向SA加入内容的步骤序列
//全局变量(end)///////////////////////////////////////////////

//程序入口(start)/////////////////////////////////////////////
void main()
{
   int iNum=0;
   cout<<"Please input a number(>0):";
   cin>>iNum;
   GetAllLine(iNum);
}
//程序入口(end)/////////////////////////////////////////////

//函数定义(start)/////////////////////////////////////////////
//InitNBTable
//初始化邻居表
//参数iNum:输入的球队个数(如果是奇数 则为 奇数+1)
void InitNBTable(int iNum)
{
 if(iNum <= 0)
  return;

 if(iNum % 2 != 0)
  ++iNum;//变成偶数,最后一个数代表空球队!
    gl_iTeamCount = iNum;

 gl_pNBTanle = new LISTPAIR[gl_iTeamCount-1];
 CPair cpInfo;
 cout<<"邻居表:"<<endl;
 for(int i = 1; i < gl_iTeamCount; ++i){
  for(int j = i+1; j < gl_iTeamCount + 1; ++j){
   cpInfo.x = i;
   cpInfo.y = j;
   cpInfo.iMode = 0;
   cpInfo.Print();
   gl_pNBTanle[i-1].push_back(cpInfo);
  }
  cout<<endl;
 }
 cout<<endl; 
}

//FreeNBTable
//释放邻居表占用的空间
void FreeNBTable()
{
 if(gl_pNBTanle == NULL)
  return;

 for(int i = 0; i < gl_iTeamCount-1; ++i){
  gl_pNBTanle[i].clear();
 }

 delete[] gl_pNBTanle;
}

//查找球队iTeamNum是否已经在SA中
bool ExistInSA(int iTeamNum)
{
 LIST_PPAIR::iterator iter;
 for (iter = gl_lstPair_SA.begin(); iter != gl_lstPair_SA.end(); ++iter){
  CPair* pInfo = *iter;
  if(pInfo->x == iTeamNum || pInfo->y == iTeamNum)
   return true;
 }

 return false;
}
//将用所有尝试标志恢复为未尝试
void ErazeTempMode()
{   
 LISTPAIR::iterator iter;
 for(int i = 0; i < gl_iTeamCount-1; ++i){
  //恢复尝试标志为未尝试
  for (iter = gl_pNBTanle[i].begin(); iter != gl_pNBTanle[i].end(); ++iter){
   CPair& cpInfo = *iter;
   if(cpInfo.iMode == 1){//尝试 
    cpInfo.iMode = 0; //未尝试
   }
  }
 }
}

//得到一行
void GetOneLine()
{   
 LISTPAIR::iterator iter;
 gl_lstInt_Steps.clear();
 gl_lstPair_SA.clear(); 
 ErazeTempMode();

 for(int i = 1; i < gl_iTeamCount;){   
  if(ExistInSA(i) == true)//i是否在SA中
  {
   //下一步
   ++i;
   continue;
  }
  
  //寻找第一个未处理的一对
  for (iter = gl_pNBTanle[i-1].begin(); iter != gl_pNBTanle[i-1].end(); ++iter){
   CPair* pInfo = &(*iter);
   if(pInfo->iMode == 0 && ExistInSA(pInfo->y) == false){//存在未处理的一对 
    pInfo->iMode = 1; //设置标志
    gl_lstPair_SA.push_back(pInfo);
    gl_lstInt_Steps.push_back(i);
    break;
   }
  }
  if(iter == gl_pNBTanle[i-1].end()){//不存在未处理的一对,回滚 
   //恢复尝试标志为未尝试
   for (iter = gl_pNBTanle[i-1].begin(); iter != gl_pNBTanle[i-1].end(); ++iter){
    CPair& cpInfo = *iter;
    if(cpInfo.iMode == 1){//尝试 
     cpInfo.iMode = 0; //未尝试
    }
   }
   
   i = gl_lstInt_Steps.back();//回滚
   gl_lstInt_Steps.pop_back();
   gl_lstPair_SA.pop_back();//去掉最后加入SA的两个数   
   continue;
  }
  
  //下一步
  ++i;
 }
 for (LIST_PPAIR::iterator iter2 = gl_lstPair_SA.begin();
     iter2 != gl_lstPair_SA.end(); ++iter2)
 {
  CPair* pInfo = *iter2;
  pInfo->iMode = 2;
  pInfo->Print();
 }
 
 cout<<endl;
}

//得到所有行
void GetAllLine(int iNum)
{
 if(iNum <= 0){
  cout<<"Please in put a number bigger than 0!"<<endl;
  return;
 }
 InitNBTable(iNum);
 cout<<"结果之一:"<<endl;
 for(int i = 0; i < gl_iTeamCount-1; i++){
  GetOneLine();
 }
 FreeNBTable();
}
//函数定义(end)/////////////////////////////////////////////

以下是代码执行的一个例子:

Please input a number(>0):12
邻居表:
(1,2)(1,3)(1,4)(1,5)(1,6)(1,7)(1,8)(1,9)(1,10)(1,11)(1,12)
(2,3)(2,4)(2,5)(2,6)(2,7)(2,8)(2,9)(2,10)(2,11)(2,12)
(3,4)(3,5)(3,6)(3,7)(3,8)(3,9)(3,10)(3,11)(3,12)
(4,5)(4,6)(4,7)(4,8)(4,9)(4,10)(4,11)(4,12)
(5,6)(5,7)(5,8)(5,9)(5,10)(5,11)(5,12)
(6,7)(6,8)(6,9)(6,10)(6,11)(6,12)
(7,8)(7,9)(7,10)(7,11)(7,12)
(8,9)(8,10)(8,11)(8,12)
(9,10)(9,11)(9,12)
(10,11)(10,12)
(11,12)

结果之一:
(1,2)(3,4)(5,6)(7,8)(9,10)(11,12)
(1,3)(2,4)(5,7)(6,8)(9,11)(10,12)
(1,4)(2,3)(5,8)(6,7)(9,12)(10,11)
(1,5)(2,6)(3,9)(4,10)(7,11)(8,12)
(1,6)(2,5)(3,10)(4,9)(7,12)(8,11)
(1,7)(2,8)(3,11)(4,12)(5,9)(6,10)
(1,8)(2,7)(3,12)(4,11)(5,10)(6,9)
(1,9)(2,10)(3,7)(4,8)(5,11)(6,12)
(1,10)(2,9)(3,8)(4,7)(5,12)(6,11)
(1,11)(2,12)(3,5)(4,6)(7,9)(8,10)
(1,12)(2,11)(3,6)(4,5)(7,10)(8,9)
Press any key to continue