最近学习《
编程之美》第1.3节,最初自己看完题目在
Linux下使用
C语言进行了实现。其中题目要求输出最优化的方案(题目如下图),自己并没有想到很好的最优方案,因此只是按照自己的理解先进行了实现。
实现方法如下: 首先是建模,即将实际问题转化为程序实现方法。分析了翻饼过程类似于排序,只不过是特殊的排序,每次排序只能从开头元素到指定位置之间的元素进行逆序,因此
设定数组的开头代表一摞烙饼的最上端,每次对数组排序需从数组第一个元素开始。受排序冒泡实现启发,每次排序将最大的烙饼排到最底下,当最大的排在最底下(数组最末尾)时,可以对除最大之外的烙饼进行翻转,因此使用
递归实现。 下面是程序是实现(未看解答之前):
#
include
<stdio.h
>
void cakeSort(
int cake[],
int size);
//排序函数,并输出排序方法
void reverse(
int array[],
int size);
//数组逆序
int biggestFind(
int array[],
int size);
//查找数组最大元素并输出位置
int step
=
0;
int main(
void)
{
int cake[]
= {
2,
4,
1,
5,
3};
int size
=
sizeof(cake)
/
sizeof(
int);
cakeSort(cake, size);
printf(
“共需要%d步翻转\n”, step);
int i;
for(i
=
0; i
< size; i
++) {
printf(
“%d”, cake[i]);
}
printf(
“\n”);
return
0;
}
void cakeSort(
int cake[],
int size)
{
int maxLocation;
if(size
>
1) {
maxLocation
= biggestFind(cake, size);
//获取最大值位置
if(maxLocation
==
0) {
//最大值在开头,则reverse整个数组
reverse(cake, size);
step
++;
printf(
“第%d步:翻转上面的%d个饼\n”, step, size);
cakeSort(cake, size
–
1);
}
else
if(maxLocation
== size
–
1) {
//最大值在末尾,则对前面的数字进行排序
cakeSort(cake, size
–
1);
}
else {
//最大值在中间某位置,则将开始到最大值位置处的子数组进行reverse,使最大值放在开头处
reverse(cake, maxLocation
+
1);
step
++;
printf(
“第%d步:翻转上面的%d个饼\n”, step, maxLocation
+
1);
reverse(cake, size);
step
++;
printf(
“第%d步:翻转上面的%d个饼\n”, step, size);
cakeSort(cake, size
–
1);
}
}
}
//功能:返回数组中最大值所在的位置
#
include
<stdio.h
>
int biggestFind(
int array[],
int size)
{
int location, max;
//存储最大值所在位置和最大值
int i;
if(size
<
=
0)
{
printf(
“Null array!\n”);
return
–
1;
}
max
= array[
0];
for(i
=
0, location
=
0; i
< size; i
++)
{
if(array[i]
> max)
{
max
= array[i];
location
= i;
}
}
return location;
}
//将数组逆置
void reverse(
int array[],
int size)
{
int i, temp;
for(i
=
0; i
< size
/
2; i
++) {
temp
= array[size
–
1
– i];
array[size
–
1
– i]
= array[i];
array[i]
= temp;
}
} 在Linux下使用gcc编译通过,测试了几个测试用例都没没有发现错误,至少实现了自己最初的设想。然后怀着些许满意的心情看了书中的解答,发现自己想的太简单了。然后又使用了一个测试用例:数组{2, 4, 1, 5, 3},发现确实是有问题的,如果使用自己的方法需要翻转7次才能达到结果,而如果先将2和4翻转一下,再按照上面的方法却只需要5次就可以。而且看完书后的感觉是自己根本就没按照题目要求来解答,
题目要求的是输出最优方案,也就程序需要解决的问题是查找最优的方案,而不是输出排序后的结果以及排序方案。 看完书中的解答,仍然不是太明白具体的解法,分析书中的程序,发现该实现使用了穷举法,穷举了所有的翻转方法,然后输出最优的方案,其中每次翻转仍然采用的递归方案。书中的程序存在一些小的问题,下面是我修正后侧程序实现,只修改了一点点。 C++程序实现:修改之处:1.将声明和实现分开;2.修正了程序中的一个印刷bug;3.修改了程序输出 CPrefixSorting.h
#
ifndef CPREFIXSORTING_H
#
define CPREFIXSORTING_H
#
include
<assert.h
>
#
include
<stdio.h
>
class CPrefixSorting
{
public
:
CPrefixSorting();
~CPrefixSorting();
void Run(
int
* pCakeArray,
int nCakeCnt);
//计算最优翻转方法
void Output();
//输出最优翻转实现
protected
:
private
:
void Init(
int
* pCakeArray,
int nCakeCnt);
//初始化成员变量,实现了平常我们在构造函数中实现的功能
int UpBound(
int nCakeCnt);
//计算上界
int LowerBound(
int
* pCakeArray,
int nCakeCnt);
//计算下界
void Search(
int step);
//排序的主函数,递归实现,查找出最优翻转方法
bool IsSorted(
int
* pCakeArray,
int nCakeCnt);
//判断是否排序完成
void Revert(
int nBegin,
int nEnd);
//逆序一个数组
private
:
int
* m_CakeArray;
//给每一个大小不一的烙饼设定一个数值,数值的大小代表烙饼的大小,相邻大小烙饼数值相差1,该数组存放烙饼的数值
int m_nCakeCnt;
//烙饼个数
int m_nMaxSwap;
//最大翻转次数,最初设为上界,即烙饼个数的2倍
int
* m_SwapArray;
//存放翻转结果,数组的每一个数值代表翻转的上面的多少个烙饼
int
* m_ReverseCakeArray;
//表示当前烙饼信息数组
int
* m_ReverseCakeArraySwap;
//当前翻转烙饼交换信息数组
int m_nSearch;
// 表示进行了收索的次数
};
#
endif
// CPREFIXSORTING_H CPrefixSorting.cpp 其中最重要的实现就是函数Search的实现
#
include
“/home/linux/BeautyOfProgramming/C1_3CakeSort_C++Version/CakeSort/include/CPrefixSorting.h”
CPrefixSorting
:
:CPrefixSorting()
{
m_nCakeCnt
=
0;
m_nMaxSwap
=
0;
}
CPrefixSorting
:
:
~CPrefixSorting()
{
if(m_CakeArray
!= NULL)
{
delete m_CakeArray;
}
if(m_SwapArray
!= NULL)
{
delete m_SwapArray;
}
if(m_ReverseCakeArray
!= NULL)
{
delete m_ReverseCakeArray;
}
if(m_ReverseCakeArraySwap
!= NULL)
{
delete m_ReverseCakeArraySwap;
}
}
void CPrefixSorting
:
:Run(
int
* pCakeArray,
int nCakeCnt)
{
Init(pCakeArray, nCakeCnt);
m_nSearch
=
0;
Search(
0);
}
void CPrefixSorting
:
:Output()
{
for(
int i
=
0; i
< m_nMaxSwap; i
++)
{
printf(
“%d step: revert %d Cakes above\n”, i, m_SwapArray[i]);
}
printf(
“\n |Search Times| : %d\n”, m_nSearch);
printf(
“Total Swap times = %d\n”, m_nMaxSwap);
}
void CPrefixSorting
:
:Init(
int
* pCakeArray,
int nCakeCnt)
{
assert(pCakeArray
!= NULL);
assert(nCakeCnt
>
0);
m_nCakeCnt
= nCakeCnt;
//init cake array
m_CakeArray
=
new
int[m_nCakeCnt];
assert(m_CakeArray
!= NULL);
for(
int i
=
0; i
< m_nCakeCnt; i
++) {
m_CakeArray[i]
= pCakeArray[i];
}
//set max swap info
m_nMaxSwap
= UpBound(m_nCakeCnt);
//init swap result array
m_SwapArray
=
new
int[m_nMaxSwap
+
1];
assert(m_SwapArray
!= NULL);
//init swap result info
m_ReverseCakeArray
=
new
int[m_nCakeCnt];
assert(m_ReverseCakeArray);
for(
int i
=
0; i
< m_nCakeCnt; i
++) {
m_ReverseCakeArray[i]
= m_CakeArray[i];
}
m_ReverseCakeArraySwap
=
new
int[m_nMaxSwap];
assert(m_ReverseCakeArraySwap);
}
//find upbound
int CPrefixSorting
:
:UpBound(
int nCakeCnt)
{
return nCakeCnt
*
2;
}
//find downbound
int CPrefixSorting
:
:LowerBound(
int
* pCakeArray,
int nCakeCnt)
{
int t, ret
=
0;
//get the min number of swap, according the current sort info of array
for(
int i
=
1; i
< nCakeCnt; i
++)
{
//判断位置相邻的两个酪饼,是否为尺寸排序上相邻的
t
= pCakeArray[i]
– pCakeArray[i
–
1];
if((t
==
1)
|| (t
==
–
1))
{
}
else
{
ret
++;
}
}
return ret;
}
void CPrefixSorting
:
:Search(
int step)
{
int i, nEstimate;
m_nSearch
++;
//estimate the min search of swap
nEstimate
= LowerBound(m_ReverseCakeArray, m_nCakeCnt);
if(step
+ nEstimate
> m_nMaxSwap)
{
return;
}
//if sorting finished, output
if(IsSorted(m_ReverseCakeArray, m_nCakeCnt))
{
if(step
< m_nMaxSwap)
{
m_nMaxSwap
= step;
for(i
=
0; i
< m_nMaxSwap; i
++)
{
m_SwapArray[i]
= m_ReverseCakeArraySwap[i];
}
}
return;
}
for(i
=
1; i
< m_nCakeCnt; i
++)
{
Revert(
0, i);
m_ReverseCakeArraySwap[step]
= i
+
1;
Search(step
+
1);
Revert(
0, i);
//将翻转过的数组重置
}
}
bool CPrefixSorting
:
:IsSorted(
int
* pCakeArray,
int nCakeCnt)
{
for(
int i
=
1; i
< nCakeCnt; i
++)
{
if(pCakeArray[i
–
1]
> pCakeArray[i])
{
return
false;
}
}
return
true;
}
void CPrefixSorting
:
:Revert(
int nBegin,
int nEnd)
{
assert(nEnd
> nBegin);
int i, j, t;
//revert info
for(i
= nBegin, j
= nEnd; i
< j; i
++, j
—)
{
t
= m_ReverseCakeArray[i];
m_ReverseCakeArray[i]
= m_ReverseCakeArray[j];
m_ReverseCakeArray[j]
= t;
}
} 对上面的实现在linux下使用Code:blocks编译通过,并能输出正确结果。如下图所示:图中显示为使用测试用例:数组{2, 4, 1, 5, 3}的结果。
几点
总结: 1. 学习上面C++实现中函数Search的实现,
穷举法与递归法组合实现,递归在穷举实现的循环中!!! 2. 学习这种迭代式的
思考方法,一开始的方案可能并不最优,但是不断的查找问题并解决。 3. 上面的方案仍然存在可优化的地方。虽然上面的方法肯定可以找到最优方案,但是实现并不是最优的,比如上面的搜索次数可以降低。书中提到,可以通过改进上界和下界来提高搜素效率(这也正是程序中使用上界和下届的方法),如何改进呢?
关于上界的分析:如果对于烙饼每次都需要翻转两次才能将最大的烙饼放在最低端,那么n个烙饼最多需要2(n-1)次就可排序成功。将上面程序的实现中上界改为(nCakeCnt – 1) * 2,之后运行,结果如下:
很明显搜素次数下降了400多次!!!!可见修改上界和下届可以很好的降低搜素次数。 使用一个测试用例自己来分析一遍上面的
算法,发现有许多无用的翻转。据作者说有一些这方面的论文,查找了一下确实发现了一些,有空研究一下。