k=i
EndIf
Nexti
d=a(k)
Fori=kTonlength-1-j
a(i)=a(i+1)
Nexti
a(nlength-j)=d
Nextj
'输出结果
Fori=nlength-1Tonlength-sStep-1'删数过程
TxtOutput.Text=TxtOutput.Text+"删除的第"+Str(nlength-i)+"个数"+Str(a(i))+vbCr+vbLf
Nexti
'最后的数
Fori=0Tonlength-s-1
TxtOutput.Text=TxtOutput.Text+Str(a(i))
Nexti
EndSub
(上程序在VB60Win2000下调试通过)
小结
这就是有趣的贪心算法,说是贪得无厌可以,说是守住当前的既得利益,以此为基础,再稳扎稳打地进行下一步也行!
滴水不漏——列举法破解难题
问:
“列举法是种什么样子的算法呢?
”
答:
“列举法是比贪心法还要贪得多的算法,列举法也是一种比较笨但却很有效的算法,他想要的东东,一种情况他都不想落下,大有宁可错杀一千,不可放过一个的阵势。
下面举例说明。
”
什么是列举法
列举是针对问题所有的可能一一查看是不是符合条件,有些“宁肯错杀一千,不可放过一个”的作风。
下面的老题最能说明这种情况。
示例:
百钱买百鸡
公鸡3元每只,母鸡5元每只,小鸡1元3只,一百元钱买一百只鸡。
请求出公鸡,母鸡和小鸡的数目。
编程简析
我们做最极端的假设,公鸡可能是0-100,母鸡也可能是0-100,小鸡还可能是0-100,将这三种情况用循环套起来,那就是1000000种情况。
这就是列举法。
为了将题目再简化一下,我们还可以对上述题目进行一下优化处理:
假设公鸡数为x,母鸡数为y,则小鸡数是100-x-y,也就有了下面的方程式:
3*x+5*y+(100-x-y)/3=100
从这个方程式中,我们不难看出大体的情况:
公鸡最多有33只,最少是没有,即x的范围是0-33;母鸡最多20只,最少0只,即母鸡的范围是0-20;有了公鸡母鸡,小鸡数自然就是100-x-y只。
可能的方案一共有34*21种,在这么多的方案中,可能有一种或几种正好符合相等的条件。
电脑怎样工作呢?
计算机事实上就是将上述34*21种方案全部过滤一遍,找出符合百钱买百鸡条件的(也即上式),只要符合,这就是我们要的输出结果。
程序实现
我们怎样将这34*21种方案罗列出呢?
这么多的方案,最好的办法是还是用循环。
可用循环和循环的嵌套,一个关于公鸡数和一个关于母鸡数的循环套起来,就能将所有的方案都遍历。
后面的问题成了怎样判断哪一个方案是我们寻找的符合条件和方案呢?
只能根据百钱买百鸡了,即3*x+5*y+(100-x-y)/3=100作为条件,在条件成立的一方输出x,y,和100-x-y的值就行了,这是分支要解决的问题,程序的整体结构有了,两个嵌套循环中套分支。
界面源程序
界面非常简单,建立一标准EXE工程,其caption设为“百钱买百鸡”,一切OK。
我们将代码加给Form_Click(),即窗体的单击事件,将来运行时,我们只要用鼠标单击一下窗体,程序就执行了。
源程序如下:
OptionExplicit
PrivateSubForm_Click()
Dimx,yAsInteger'声明变量
Forx=0To33
Fory=0To20
If3*x+5*y+(100-x-y)/3=100Then
Print"公鸡,母鸡和小鸡数分别为:
";x,y,100-x-y
EndIf
Nexty
Nextx
EndSub
(上程序在VB60Win2000下调试通过)
题目的结果有多组,正和我们刚开始的所想相符。
小结
这就是列举法,将可能的情况一网打尽;不过在应用过程中,我们最好还是做些优化,不然,要浪费好多没必要浪费的时间。
镜里照镜——递归法破解难题
问:
“前几种办法的确名如其法,比较笨。
有没有比较潇洒一点的算法?
递归属不属于些类算法呀?
”
答:
“递归一种非常奇妙和美妙的算法形式,奇妙美妙的背后是比较难理解。
但用起来却异常简洁。
”
什么是递归
说白了递归就象我们讲的那个故事:
山上有座庙,庙里有个老和尚,老和尚在讲故事,它讲的故事是:
山上有座庙,庙里有个老和尚,老和尚在讲故事,它讲的故事是:
……也就是直接或间接地调用了其自身。
就象上面的故事那样,故事中包含了故事本身。
因为对自身进行调用,所以需对程序段进行包装,也就出现了函数。
函数的利用是对数学上函数定义的推广,函数的正确运用有利于简化程序,也能使某些问题得到迅速实现。
对于代码中功能性较强的、重复执行的或经常要用到的部分,将其功能加以集成,通过一个名称和相应的参数来完成,这就是函数或子程序,使用时只需对其名字进行简单调用就能来完成特定功能。
函数又可分为自定义的和系统附带的,但不管是自定义的还是系统的,他们都对相应的功能进行了封装,以利于我们经常性地使用。
例如我们的对一个小数取整数INT()函数,不论什么样的小数,往()中一放,将来得到的值就自动将小数去除了。
函数执行完将返回一个值,当然这个值可以是各种类型的,子程序仅仅执行一个过程,不返回数值。
函数和子程序是执行递归的干将。
示例:
小猴吃枣
小猴第一天摘下若干枣子,当即吃掉了一半,不过瘾又多吃了一个;第二天吃了剩下的一半又多吃了一个;以后每一天都吃了前一天剩下的一半多一个。
到第十天小猴再想吃时,见到只剩下一只枣子了。
问第一天这堆枣子有多少?
从上题中我们可看到一个令人欣喜的规律,第十天为1,第九到第一天中后一天与1的和的两倍与前一天相等。
下面就对这一规律做了描述:
PrivateFunctionmonkey(ByValxAsInteger)AsInteger
Ifx>=10Then
monkey=1
Else
monkey=2*(monkey(x+1)+1)
EndIf
EndFunction
我们定义monkey()函数的时候通过monkey()自身来进行了定义,这就是递归。
递归是个特殊的循环,是一个有着非常美妙的循环规则的循环。
上题中我们只要将monkey
(1),即第一天打印出来,一切OK。
而这中间究竟是怎么工作的,我们可以不管。
正是有了monkey()函数,在对其自身调用的过程中实现了我们的所求,关于函数、子程序和他们之间发生的故事还有很多,仅仅列举了其中奇妙的几点,还有许多东东等着您的发现和利用。
小结
函数和子程序是程序瘦身计划的一部分,通过它们可以使程序中的代码适当减肥,长度维持在一个更合理的位置。
这种作用和循环的瘦身作用一起,使一个执行很长的代码可以变得很简洁。
这也更适合我们利用计算机作为工具的目的:
人类做尽量少的工作,计算机仍能解决原先的问题。
另一个奇妙之处是:
他们创造了递归!
各个击破——分治法破解难题
问:
“问题不能一下子解决,难道不能分开解决吗,有没有算法能实现各个击破以求解决问题呢?
”
答:
“可以的,通过各个击破的方法解决问题的算法叫做分治法。
下面我们通过示例来看一下。
”
什么是分治法
为了解决一个问题,算法有时需不止一次地对自身进行调用,来解决相类似的子问题。
这样的算法通常称为分治法:
将原问题分成n个规模较小而结构与原问题相似的子问题。
下面通过排序的一种方法来看一下。
希尔排序即是采用分治法来进行排序的,又称做缩小增量排序,其思想是:
把已经在数组中的数据按下标的一定增量分组,对分出的每一小组使用插入排序,随着增量逐渐减小,所分成的组包含的数据越来越多,直到减小到1时,整个数据合并成一组,构成一组有序数,则完成排序。
示例:
十个数,从大到小排序。
数据放在一个数组a(10)中,假如原始数据如下:
70.53.57.28.30.77.1.76.81.70,则排序过程如下:
增量值
5:
77.53.76.81.70.70.1.57.28.30.
2:
77.81.76.70.70.57.28.53.1.30.
1:
81.77.76.70.70.57.53.30.28.1.
其中上面三个增量值对应的都是以该增量完成本轮排序后的情况,看增量为5时要和原始数据比较,增量为2的情况要和5比较,1要和2比较,这样其中的规律就清楚了。
子程序如下
要用实现希尔排序,关键是把握好增量的变化情况和最终结束的控制,设置变量gap为增量,其值取要排序的所有数据的个数的二分之一(本例中为5),比较时先将第1个数同第6个比,较大的放到前面,较小的放到后面,2同7,直至全部比较完成;下一次用现在的gap的二分之一作为增量,再进行增量大小转换;…;当其为0时结束。
原无序序列排成了有序序列了。
从上面分析中不难看出,通过和gap增量有关的两重嵌套循环就能将排序功能实现。
详细源程序如下:
Subshellsort(ByValnAsInteger)'希尔排序子程序
Dimi,j,gapAsInteger
Dimk,x AsInteger
gap=Int(n/2)'置初值
Whilegap>0
Fori=gap+1Ton
j=i-gap
Whilej>0
Ifa(j) x=a(j)
a(j)=a(j+gap)
a(j+gap)=x
j=j-gap
Else
j=0
EndIf
Wend
Nexti
gap=Int(gap/2)’减小增量
‘输出结果
TxtList.Text=TxtList.Text+Str(gap)+":
"
Fork=1Ton
TxtList.Text=TxtList.Text+Str(a(k))+"."
Nextk
TxtList.Text=TxtList.Text+vbCr+vbLf
Wend
EndSub
其他源程序
希尔排序按钮对应的源程序如下:
PrivateSubCmdShell_Click()'希尔排序
DimiAsInteger
TxtList.Text=""
Txtorigin.Text=""
Fori=1To10'输入原始数据
a(i)=Int(Rnd*100)
Txtorigin.Text=Txtorigin.Text+Str(a(i))+"."
Nexti
'调用子程序排序并输出中间结果
Callshellsort(10)
EndSub
小结
在进行希尔排序时,需注意增量序列的取值方法,并且使这些序列中的值没有除1之外的公因子,且最后一个增量值必须为1。
能解决问题的办法都是好办法,问题不一定整体解决才好。
这就是分治的思想。
乱打误撞——模拟法破解难题
问:
“电脑解决确定问题可做到手到擒来,对于电脑中实现一个不确定的问题,例如彩票或抽奖,怎样做呢?
”
答:
“算法的美妙在于其准确和确定,而另有一种价值则在于其不确定,象我们的抽奖程序和彩票程序。
确定的问题电脑可以处理,不确定的问题电脑也能处理,随机函数就是实现电脑中不确定事件的重要砝码。
下面我们通过示例来看一下。
”
随机函数的出现
通过语言编程一般来说对事物的认识是很确定的了,是一就是一,是二就是二,还有一个问题,有一些不那么确定的事情该如何处理,象我们的彩票抽奖,如果是确定的了,那也就不用抽了,恐怕也就没人玩了。
对于这一类的事情,该怎么办呢?
语言中为我们提供了随机函数,也就是说通过它得到的一个值将是不能确定的。
随机函数产生的秘密
计算机常常需要模拟随机选择的数目,有多种不同的方法可以产生具有随机性质的数,由于通过此种系统的方法产生的不是真正的随机数,所以一般称做伪随机数。
最常用的产生伪随机数的方法称为线性同余法。
公式如下,选择四个数:
模数m,乘数a,增量c和种数x0,使2≤a生成的办法是逐次同余:
xn+1=(axn+c)modm
应用和变通
随机函数有一个范围,即Rnd函数返回小于1但大于或等于0的小数值。
但通常我们要解的问题不在这个范围内,如何解决呢?
示例:
最简单的抽奖程序,做一个猜1-100之间数的游戏。
因为随机函数的范围是一个0-1之间的小数,和题目要求的范围相差很大。
所以,当我们用到的值不在这个范围之内时,我们可以想点变通的办法。
要想做到从1-100之间进行取数,必须扩大100倍才行。
不难计算RND*100的范围却不是1-100,而是0-100,不包括0和100,怎样就是1-100了呢?
加上一就有了,范围成了1-101,不包括1和101,只要对得到的数只取整数,这个数只要这样表达就出来了,正好INT()函数起到这样的作用:
INT(RND*100+1)
所以程序也非常简单:
PrivateSubForm_Click()’单击窗体
PrintINT(RND*100+1)
EndSub
其中中间的代码就完成了我们题目的要求。
所以针对上述不确定的问题时,要利用好随机函数,并适当地对其做某些变通,这样问题就得到解决了。
小结
随机函数是程序设计中一道亮丽的风景。
这个函数是非常有用的,她可能是计算机语言中唯一没有理性的东东了。
就好象我们人类所具有的现省心的想法,妙手偶得之的佳句。
正因为这个唯一性,也就不难看出她在计算机语言中的地位了。