=i;
m:
=a[p];
需要n-1次比较得到max,而再经过n-2次比较得到min,共进行2*n-3次比较可以找出极大元和极小元。
用分治法可以较少比较次数地解决上述问题:
如果集合中只有1个元素,则它既是最大值也是最小值;
如果有2个元素,则一次maxnum(i,j)一次minnum(i,j)就可以得到最大值和最小值;
如果把集合分成两个子集合,递归的应用这个算法分别求出两个子集合的最大值和最小值,最后让子集合1的最大值跟子集合2的最大值比较得到整个集合的最大值;让子集合1的最小值跟子集合2的最小值比较得到整个集合的最小值。
可得解题思想:
{maxmin}
①if问题不可分:
n=2
②问题的解求得(两个元素时):
对两元素进行比较;return;
③fori:
=1tondiv2dob[i]:
=a[i]
④maxmin(ndiv2,b,max1,min1),其中max1和min1为解1
⑤fori:
=1tondiv2dob[i]:
=a[i+ndiv2]
⑥maxmin(ndiv2,b,max2,min2),其中max2和min2为解2
⑦max:
=maxnum(max1,max2);
min:
=minnum(min1,min2);
{maxmin}
其中对函数maxnum的求精:
functionmaxnum(a,b:
integer):
integer;
begin
ifa>bthenmaxnum:
=aelsemaxnum:
=b;
end;
分析比较次数:
比较运算均在函数maxnum和minnum中进行,
当n=2时,比较次数T(n)=1
当n>2时,比较次数T(n)=2T(n/2)+2
∵n是2的k次幂
∴设n=2^k
2、快速排序
快速排序是基于分治策略的一个排序算法。
按照分治法的思想分析快速排序:
(1)分解(Divide)以元素a[p]为基准元素将a[p:
q-1]划分为三段a[p:
q-1],a[q]和a[q+1:
r],使得a[p:
q-1]中任何一个元素都小于a[q],a[q+1:
r]中任何一个元素大于等于a[q],下标q在划分中确定。
(2)递归求解(Conquer)通过递归调用快速排序算法分别对a[p:
q-1]和a[q+1:
r]进行排序。
(3)合并(Merge)由于a[p:
q-1]和a[q+1:
r]的排序都是在原位置进行的,所以不必进行任何合并操作就已经排好序了。
在上面三个步骤中,第一步:
分解是关键。
一次快速排序确定划分元素的位置,具体参见排序算法----快速排序
3、归并排序
归并排序也是基于分治策略的另一个算法。
归并的思路是把两个已经排好序的数组合并为一个。
(源程序)
2-路归并排序示例:
初始值
E
Y
U
K
S
L
B
一趟归并排序
E Y
K U
L S
B
两趟归并排序
E K U Y
B L S
三趟归并排序
B E K L S U Y
习题:
对数字49384097761327进行归并排序
proceduremergesort(varr,r1:
listtype;s,t:
integer);
{r,r1:
均为链表,存储排序数据;过程mergesort(r,r1,s,t)完成把链表r中的关键字进行归并排序、并存放到链表r1中,其中s是下界t是上界}
{过程merge(r2,s,m,t,r1)把链表r2中排好序的子链r2[s..m]和子链r2[m+1..t]合并到r1中}
if问题不可分then
求解
ifs=tthen
r1[s]:
=r[s]
else
else
(1)分出问题的一个子问题1,并求解子问题1
mergesort(r,r2,s,(s+t)div2);
(2)分出问题的一个子问题2,求解子问题2
mergesort(r,r2,(s+t)div2,t);
(3)合并子问题1和子问题2
merge(r2,s,(s+t)div2,t,r1);
4、[循环赛问题](1999年广东省青少年信息学奥林匹克竞赛第三题:
棒球联赛)
问题描述:
广州市体委将举行一次由N支队伍(队伍编号为1..N)参加的少年棒球联赛。
联赛总共不能多于N轮(同一时间内若干支队进行一次比赛为一轮),并且每两支队之间必须而且仅必须进行一次比赛。
请编程为这次比赛设计一个赛程表。
循环赛问题可以用分治法解决。
下面是先假定n=2^k
proceduretable(k:
integer;a:
array[1..u1,1..u2]ofinteger);
varn,i,j,m,s,t:
integer;
begin
n:
=1;
fori:
=1tokdon:
=n*2;
fori:
=1tondoa[1,i]:
=i;
m:
=1;
fors:
=1tokdo
begin
n:
=n/2;
fort:
=1tondo
fori:
=m+1to2*mdo
forj:
=m+1to2*mdo
begin
a[i,j+(t-1)*m*2]:
=a[i-m,j+(t-1)*m*2-m];
a[i,j+(t-1)*m*2-m]:
=a[i-m,j+(t-1)*m*2];
end;
m:
=m*2;
end;{fors}
end;
三、练习题
[二分检索]假定在A[1..9]中顺序存放这九个数:
-7,-2,0,5,16,43,57,102,291要求检索291,16,101是否在数组中。
给定已排好序的n个元素A1,A2,A3,…,An,找出元素x是否在A中,如果x在A中,指出它在A中的位置。
用到递归调用的源程序binSearch.pas
非递归源程序binSe2.pas
贪心算法
相关知识:
一维数组|多维数组|栈|队列|串
一、算法思想
贪心法的基本思路:
——从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。
当达到某算法中的某一步不能再继续前进时,算法停止。
该算法存在问题:
1.不能保证求得的最后解是最佳的;
2.不能用来求最大或最小解问题;
3.只能求满足某些约束条件的可行解的范围。
实现该算法的过程:
从问题的某一初始解出发;
while能朝给定总目标前进一步do
求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
二、例题分析
1、[背包问题]有一个背包,背包容量是M=150。
有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品
A
B
C
D
E
F
G
重量
35
30
60
50
40
10
25
价值
10
40
30
50
35
40
30
分析:
目标函数:
∑pi最大
约束条件是装入的物品总重量不超过背包容量:
∑wi<=M(M=150)
(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
(2)每次挑选所占空间最小的物品装入是否能得到最优解?
(3)每次选取单位容量价值最大的物品,成为解本题的策略。
源程序
2、[单源最短路径]一个有向图G,它的每条边都有一个非负的权值c[i,j],“路径长度”就是所经过的所有边的权值之和。
对于源点需要找出从源点出发到达其他所有结点的最短路径。
E.Dijkstra发明的贪婪算法可以解决最短路径问题。
算法的主要思想是:
分步求出最短路径,每一步产生一个到达新目的顶点的最短路径。
下一步所能达到的目的顶点通过如下贪婪准则选取:
在未产生最短路径的顶点中,选择路径最短的目的顶点。
设置顶点集合S并不断作贪心选择来扩充这个集合。
当且仅当顶点到该顶点的最短路径已知时该顶点属于集合S。
初始时S中只含源。
设u为G中一顶点,我们把从源点到u且中间仅经过集合S中的顶点的路称为从源到u特殊路径,并把这个特殊路径记录下来(例如程序中的dist[i,j])。
每次从V-S选出具有最短特殊路径长度的顶点u,将u添加到S中,同时对特殊路径长度进行必要的修改。
一旦V=S,就得到从源到其他所有顶点的最短路径,也就得到问题的解。
Dijkstra.pas
3、[机器调度]现有N项任务和无限多台机器。
任务可以在机器上处理。
每件任务开始时间和完成时间有下表:
任务
a
b
c
d
e
f
g
开始(si)
0
3
4
9
7
1
6
完成(fi)
2
7
7
11
10
5
8
在可行分配中每台机器在任何时刻最多处理一个任务。
最优分配是指使用的机器最少的可行分配方案。
请就本题给出的条件,求出最优分配。
三、练习题:
已知5个城市之间有班机传递邮件,目的是为了寻找一条耗油量较少的飞行路线。
5个城市的联系网络如图所示。
图中编号的结点表示城市,两个城市之间的连线上的值表示班机沿该航线已行的耗油量,并假定从城市i到j和城市j到i之间的耗油量是相同的。
分析:
1.运用贪心思想:
在每一步前进的选择上,选取相对当前城市耗油量最小的航线;
2.图解:
若从1出发,有图:
总耗油量=141-2-5-3-4-1
但若路线改为:
1-5-3-4-2-1,则总耗油量=13
所以,这样的贪心法并不能得出最佳解。
3.改善方案:
从所有城市出发的信心过程,求最优的。
编程:
1.数据结构:
城市联系网络图的描述(图的邻接矩阵的描述):
const
c=array[1..5,1..5]ofinteger=((0,1,2,7,5),
(1,0,4,4,3),
(2,4,0,1,2),
(7,4,1,0,3));
2.贪心过程:
begin
初始化所有城市的算途径标志;
设置出发城市V;
fori:
=1ton-1do{n-1个城市}
begin
s:
=从V至所有未曾到过的城市的边集中耗油量最少的那个城市;
累加耗油量;
V:
=s;
设V城市的访问标志;
end;
最后一个城市返回第一个城市,累加耗油量;
end;
3.主过程:
实现改善方案
begin
fori:
=1tondo
begin
cost1:
=maxint;{初始化}
调用贪心过程,返回本次搜索耗油量cost;
ifcostend;
输出;
end.
greed.pas
递归
★递归策略:
一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
注意:
(1)递归就是在过程或函数里调用自身;
(2)在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
★简单的递归调用
1.例1:
用递归策略求N!
的解。
N!
=1*2*3*...*N
分析:
(1)不运用递归的解法(ppro9.pas)
(2)运用递归策略(ppro10.pas)
N!
=1*2*3*...*N
=[1*2*3*...*(N-1)]*N
(N-1)!
=1*2*3*...*(N-1)
设f(N)=N!
那么f(N-1)=(N-1)!
则f(N)=f(N-1)*N
这就是递归式子,由于式子中有N-1,所以N>=1,递归出口的条件是N=1时。
函数模式:
functionf(n:
integer):
longint;
var
...
begin
if递归出口的时候then
f:
=1
else
f:
=f(n-1)*n;
end;
2.例2:
用递归策略求斐波那契数。
(ppro11.pas)
112358132134...
分析:
将上述的数存于数组A中,可知A[n]=A[n-1]+A[n-2]
由此有:
A(n)=A(n-1)+A(n-2)
由于上式中出现了n-1和n-2,所以n>=2,
则递归出口的条件是N=1或N=2
3.例3:
Hanoi(汉诺塔)问题:
(hanoi.pas或ppro12.pas)
有N个圆盘,依其半径大小自下而上套在A柱上,每次只允许移动一个最上面的圆盘到另外的柱子上(B柱和C柱)。
但不允许任何柱子出现大盘在上、小盘在下的情况。
设计出将A柱上的N个盘子移到C柱上的方法。
以5个盘子为例子,先分析具体情况。
抽象分析:
1、目标:
A盘上的N个盘子从A--(B)-->C,可以用B作为过渡盘。
实现:
①A柱上面的N-1个盘子(1号——N-1号)从A--(C)--〉B
②把A最下面最大的那一个盘子(第N个)从A-----〉C
③再把(1号——N-1号)那N-1个盘子从B---(A)--〉C
2、目标:
解决1之②:
把一个盘从一个柱子搬到另一个柱子上,很容易解决。
3、目标:
解决1之①,实现:
①A柱上面(1号——N-2号)N-2个盘子从A--()--〉C
②A柱此时最下面的最大的盘子(第N-1个)从A-----〉C
③再把(1号——N-2号)那N-2个盘子从C---()---〉B
4、目标:
解决1之③,实现:
①B柱上面(1号——N-2号)N-2个盘子从B--()--〉A
②B柱此时最下面的最大的盘子(第N-1个)从B-----〉C
③再把(1号——N-2号)那N-2个盘子从A---()---〉C
……
一直到递归的出口的条件:
当盘只有一个的时候。
程序模式:
proceduremovedisk(a,c);
begin
……
end;
procedurehanoi(其他参数,a,b,c);
begin
if……then
else……
end;
Begin
用户输入N的值;
hanoi(参数表)
End.
★练习:
1.用递归的算法把数组中的N个数按颠倒的次序重新存放。
Pex10_23.pas
2.用递归算法完成:
有52张牌,使它们全部正面朝上,第一轮是从第2张开始,凡是2的倍数位置上的牌翻成正面朝下;第二轮从第3张牌开始,凡是3的倍数位置上的牌,正面朝上的翻成正面朝下,正面朝下的翻成正面朝上;第三轮从第4张牌开始,凡是4的倍数位置上的牌按上面相同规则翻转,以此类推,直到第一张要翻的牌超过52为止。
统计最后有几张牌正面朝上,以及它们的位置号。
Pex9_09.pas
回溯法
相关知识:
深度优先|广度优先:
三角形|剪枝搜索|递归与回溯|素数环|八皇后问题|跳马问题|实例:
2的幂次方
一、回溯法:
回溯法是一个既带有系统性又带有跳跃性的的搜索算法。
它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。
如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。
否则,进入该子树,继续按深度优先的策略进行搜索。
回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。
而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。
这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
二、算法框架:
1、问题的解空间:
应用回溯法解问题时,首先应明确定义问题的解空间。
问题的解空间应到少包含问题的一个(最优)解。
2、回溯法的基本思想:
确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。
这个开始结点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为一个新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。
换句话说,这个结点不再是一个活结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
运用回溯法解题通常包含以下三个步骤:
(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索;
3、递归回溯:
由于回溯法是对解空间的深度优先搜索,因此在一般情况下可用递归函数来实现回溯法如下:
proceduretry(i:
integer);
var
be