作者:宋宝华  e-mail:[email]21cnbao@21cn.com[/email]
1.引言
2005102526日,包括笔者在内的十多位成员组队参加了武汉原动力的野外拓展(Outward Bound)。在攀岩悬崖之前,教官组织了这样的一个游戏项目:
教官将团队里的所有成员分开,然后用布条蒙上大家的眼睛,接着给每人一个3位或4位的数字。他要求成员们蒙着眼睛集合,在不说话也看不到彼此的情况下,在限定的时间内,按所分得数字的大小顺序排成一条线。
要成功地完成这个游戏的确有相当的难度,成员们唯一可以借助的分辨彼此大小的手段可能是摸手指、拍肩膀或跺脚等,而要在限定的时间内分别出所有的大小并排成一条线却依赖一个好的排序算法。
最后我们团队失败了,我们这群都有一定数据结构和算法学习背景的所谓IT人败在了这个游戏的面前。最后,教官对我们进行了一番“团队合作精神如此重要”之类的教育云云。
本文对排序算法的全面论述将从这个游戏说开去,并用Visual C++ 6.0编写一个示例工程以动画演示这个游戏中的成员以各种算法实现成功排序的过程。排序算法是数据结构学科的经典内容,也是计算机科学中最重要的研究问题之一。由于它的应用广泛和固有的理论上的重要性,2000年它被列为对科学和工程计算的研究与实践影响最大的10大问题之一。对于排序的研究既有理论上的重要意义,又有实际应用价值。它在计算机图形、计算机辅助设计、机器人、模式识别、及统计学等领域具有广泛应用。
常见的排序算法有起泡排序、直接插入排序、简单选择排序、快速排序、堆排序等。在演示完各种排序算法的动态过程后,本文将给出面对特定问题时选用合适排序算法的原则。
2.演示工程
单击此处下载演示工程。
Visual C++编写一个基于对话框的程序,我们假设待排序的队员的总数为10,排序的整个过程为(每个步骤对应一个菜单):
1)队员分散:模拟教官将队员分散开来的过程
对应的菜单为:IDM_Disperse_Member
菜单标题为:队员分散
2)分配数字:模拟教官给每个队员一个3位或4位的数字的过程
对应的菜单为:IDM_CREAT_NUMBER
菜单标题为:产生数字
3)集合:所有成员在获得数字后,为进行排序,他们需要先集合
对应的菜单为:IDM_Muster_Member
菜单标题为:队员集合
4)排序:成员们依据摸手指、拍肩膀或跺脚等手段进行由小到大的排序,又依据排序算法分为:
a.冒泡法
对应的菜单为:IDM_Bubble_Sort
b.交换排序
对应的菜单为:IDM_Exchange_Sort
c.选择排序
对应的菜单为:IDM_Selection_Sort
d.插入排序
对应的菜单为:IDM_INSERT_SORT
e.快速排序
对应的菜单为:IDM_QUICK_SORT
整个对话框的消息映射关系为:
BEGIN_MESSAGE_MAP ( CSortDlg, Cdialog )
      //{{AFX_MSG_MAP ( CSortDlg )
      ON_WM_SYSCOMMAND()
      ON_WM_PAINT()
      ON_WM_QUERYDRAGICON()
      ON_COMMAND ( IDM_Disperse_Member, OnDisperseMember )
      ON_COMMAND ( IDM_CREAT_NUMBER, OnCreatNumber )
      ON_COMMAND ( IDM_Muster_Member, OnMusterMember )
      ON_COMMAND ( IDM_Bubble_Sort, OnBubbleSort )
      ON_COMMAND ( IDM_Exchange_Sort, OnExchangeSort )
      ON_COMMAND ( IDM_Selection_Sort, OnSelectionSort )
      ON_COMMAND ( IDM_INSERT_SORT, OnInsertSort )
      ON_COMMAND ( IDM_QUICK_SORT, OnQuickSort )
      //}}AFX_MSG_MAP
END_MESSAGE_MAP()
“队员分散”实现的功能是将10个成员均匀分散在一个大圆周上(以一个小正方形模拟一个成员,这些成员的名字为09),其源代码为:
void CSortDlg::OnDisperseMember()
{
  CRect rect;
  GetClientRect(&rect);
  CClientDC dc(this);
  dc.SetBkColor(RGB(180, 180, 180));
  dc.FillRect(&rect, &CBrush(RGB(180, 180, 180)));
  //将待排序的对象分散在一个圆上
  for (int i = 0; i < SORT_OBJECT_NUM; i++)
  {
    //绘制边框
    dc.DrawEdge(&CRect(rect.right / 2+150 * cos(2 *PI * i / 10.0) - 10,
      rect.bottom / 2+150 * sin(2 *PI * i / 10.0) - 10, rect.right / 2+150 *
      cos(2 *PI * i / 10.0) + 10, rect.bottom / 2+150 * sin(2 *PI * i / 10.0) +
      10), BDR_RAISEDINNER, BF_RECT);
    //显示名称
    CString strName;
    strName.Format("%d", i);
    dc.TextOut(rect.right / 2+150 * cos(2 *PI * i / 10.0) - 5, rect.bottom / 2
      +150 * sin(2 *PI * i / 10.0) - 8, strName);
  }
}
1给出了队员分散后的显示结果。
从趣味游戏到排序算法(1)_休闲
从趣味游戏到排序算法(1)_休闲
从趣味游戏到排序算法(1)_休闲
从趣味游戏到排序算法(1)_休闲
1 队员分散
“分配数字”实现的是给每个成员随机的分配一个3位或4位的数字,其源代码为:
void CSortDlg::OnCreatNumber()
{
  CRect rect;
  GetClientRect(&rect);
  CClientDC dc(this);
  dc.SetBkColor(RGB(180, 180, 180));
  // Seed the random-number generator with current time
  srand((unsigned)time(NULL));
  //产生SORT_OBJECT_NUM3/4位的数
  for (int i = 0; i < SORT_OBJECT_NUM; i++)
  {
    int temp;
    temp = rand();
    if (temp % 2 == 0)
    //产生3位数
    {
      while (temp % 1000 < 100)
      {
        temp = rand();
      }
      sortObject[i].iNumber = temp % 1000;
    }
    else
     //产生4位数
    {
      while (temp % 10000 < 1000)
      {
        temp = rand();
      }
      sortObject[i].iNumber = temp % 10000;
    }
    //初始化序号和名称
    sortObject[i].iName = i;
    sortObject[i].iSeq = i;
    //显示获得的数字
    CString strNum;
    strNum.Format("%4d", sortObject[i].iNumber);
    dc.TextOut(rect.right / 2+150 * cos(2 *PI * i / 10.0) - 15, rect.bottom / 2
      +150 * sin(2 *PI * i / 10.0) - 30, strNum);
  }
}
2给出了分配数字后的显示结果。
从趣味游戏到排序算法(1)_游戏_05
从趣味游戏到排序算法(1)_游戏_05
从趣味游戏到排序算法(1)_游戏_05
从趣味游戏到排序算法(1)_游戏_05
2 分配数字
“队员集合”实现的功能是将所有成员一字排开,其源代码为:
void CSortDlg::OnMusterMember()
{
      CClientDC dc (this) ;
 
 
 
 
      CRect rect;
      GetClientRect(&rect) ;
      InitObjectCoord(rect);//初始化一字排开的坐标
 
 
 
 
      dc.SetBkColor(RGB(180,180,180));
      dc.FillRect(&rect,&CBrush(RGB(180,180,180)));
 
 
 
 
      for(int i=0;i<SORT_OBJECT_NUM;i++)
      {
             //绘制外框
             dc.DrawEdge(&CRect(
                    objectCoord[i].x-10,
                    objectCoord[i].y-10,
                    objectCoord[i].x+10,
                    objectCoord[i].y+10),
                    BDR_RAISEDINNER,
                    BF_RECT
                    );
             //显示名称
             CString strName;
             strName.Format("%d",i);          
             dc.TextOut(objectCoord[i].x-5,
                    objectCoord[i].y-8,
                    strName);     
             //显示获得的数字
             CString strNum;
             strNum.Format("%4d",sortObject[i].iNumber);            
             dc.TextOut(objectCoord[i].x-15,
                    objectCoord[i].y-30,
                    strNum);
      }
}
其中所调用的InitObjectCoord函数获取一字排开时所均匀分配的坐标,其原型为:
void InitObjectCoord(CRect &clientRect)
{
  for (int i = 0; i < SORT_OBJECT_NUM; i++)
  {
    objectCoord[i].x = 30 +
               i * (clientRect.right - 60) / (SORT_OBJECT_NUM - 1);
    objectCoord[i].y = clientRect.bottom / 2;
  }
}
其中的objectCoord定义为:
POINT objectCoord[SORT_OBJECT_NUM];
3给出了队员集合后的显示结果。
从趣味游戏到排序算法(1)_休闲_09
从趣味游戏到排序算法(1)_休闲_09
从趣味游戏到排序算法(1)_休闲_09
从趣味游戏到排序算法(1)_休闲_09
3 队员集合
上述CSortDlg::OnCreatNumber()CSortDlg::OnMusterMember()函数中用到的sortObject定义为:
SortObject sortObject[SORT_OBJECT_NUM];
SortObject数组,而SortObject的含义则为待排序的成员,其定义为:
typedef struct tagSortObject
{
      int iName;  //待排序对象名:即0~9
      int iSeq;    //排序的序号
      int iNumber; //教官给出的数字
}SortObject;