1.
引言
2005
年
10
月
25
~
26
日,包括笔者在内的十多位成员组队参加了武汉原动力的野外拓展(
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
个成员均匀分散在一个大圆周上(以一个小正方形模拟一个成员,这些成员的名字为
0
~
9
),其源代码为:
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
队员分散
“分配数字”实现的是给每个成员随机的分配一个
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_NUM
个
3/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
给出了分配数字后的显示结果。
图
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
{
//
绘制外框
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
给出了队员集合后的显示结果。
图
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;