1、1(1)用计算机求解问题的步骤:1、问题分析 2、数学模型建立 3、算法设计与选择 4、算法指标 5、算法分析 6、算法实现7、程序调试 8、结果整理文档编制(2)算法定义:算法是指在解决问题时,按照某种机械步骤一定可以得到问题结果的处理过程(3)算法的三要素1、操作 2、控制结构 3、数据结构算法具有以下 5 个属性:有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。确定性:算法中每一条指令必须有确切的含义。不存在二义性。只有一个入口和一个出口可行性:一个算法是可行的就是算法描述的操作是可以通过已经实现的基本运算执行有限次来实现的。输入:一个算法有零个或多个输入,这些
2、输入取自于某个特定对象的集合。输出:一个算法有一个或多个输出,这些输出同输入有着某些特定关系的量。算法设计的质量指标:正确性:算法应满足具体问题的需求;可读性:算法应该好读,以有利于读者对程序的理解;健壮性:算法应具有容错处理,当输入为非法数据时,算法应对其作出反应,而不是产生莫名其妙的输出结果。效率与存储量需求:效率指的是算法执行的时间;存储量需求指算法执行过程中所需要的最大存储空间。一般这两者与问题的规模有关。经常采用的算法主要有迭代法、分而治之法、贪婪法、动态规划法、回溯法、分支限界法迭代法 也称“辗转法” ,是一种不断用变量的旧值递推出新值的解决问题的方法。利用迭代算法解决问题,需要做
3、好以下三个方面的工作:一、确定迭代模型。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。二、建立迭代关系式。所谓迭代关系式,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决迭代问题的关键,通常可以使用递推或倒推的方法来完成。23、对迭代过程进行控制。在什么时候结束迭代过程?这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的
4、控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。编写计算斐波那契(Fibonacci)数列的第 n 项函数 fib(n) 。斐波那契数列为:0、1、1、2、3、,即:fib(0)=0;fib(1)=1;fib(n)=fib(n-1)+fib(n-2) (当 n1 时)。写成递归函数有:int fib(int n) if (n=0) return 0;if (n=1) return 1;if (n1) return fib(n-1)+fib(n-2);一个饲养场引进一只刚出生的新品种兔子,这种兔子从出生的下一个月开始,每月新生一只兔子,新生的兔子也如此繁殖。如果所有的兔子都不死去,
5、问到第 12 个月时,该饲养场共有兔子多少只?分析: 这是一个典型的递推问题。我们不妨假设第 1 个月时兔子的只数为 u 1 ,第 2 个月时兔子的只数为 u 2 ,第 3 个月时兔子的只数为 u 3 ,根据题意,“这种兔子从出生的下一个月开始,每月新生一只兔子”,则有u 1 1 , u 2 u 1 u 1 1 2 , u 3 u 2 u 2 1 4 ,根据这个规律,可以归纳出下面的递推公式:u n u n 1 2 (n 2)对应 u n 和 u n 1 ,定义两个迭代变量 y 和 x ,可将上面的递推公式转换成如下迭代关系:y=x*2x=y让计算机对这个迭代关系重复执行 11 次,就可以算出
6、第 12 个月时的兔子数。参考程序如下:clsx=1for i=2 to 12y=x*2x=ynext iprint yend分而治之法31、 分 治 法 的 基 本 思 想 任 何 一 个 可 以 用 计 算 机 求 解 的 问 题 所 需 的 计 算 时 间 都 与 其 规 模 N 有 关 。 问 题的 规 模 越 小 , 越 容 易 直 接 求 解 , 解 题 所 需 的 计 算 时 间 也 越 少 。 例 如 , 对 于 n 个 元 素的 排 序 问 题 , 当 n=1 时 , 不 需 任 何 计 算 ; n=2 时 , 只 要 作 一 次 比 较 即 可 排 好 序 ; n=3时 只
7、 要 作 3 次 比 较 即 可 , 。 而 当 n 较 大 时 , 问 题 就 不 那 么 容 易 处 理 了 。 要 想 直 接 解决 一 个 规 模 较 大 的 问 题 , 有 时 是 相 当 困 难 的 。分 治 法 的 设 计 思 想 是 , 将 一 个 难 以 直 接 解 决 的 大 问 题 , 分 割 成 一 些 规 模 较 小 的相 同 问 题 , 以 便 各 个 击 破 , 分 而 治 之 。 分 治 法 所 能 解 决 的 问 题 一 般 具 有 以 下 几 个 特 征 : ( 1) 该 问 题 的 规 模 缩 小 到 一 定 的 程 度 就 可 以 容 易 地 解 决 ;
8、 ( 2) 该 问 题 可 以 分 解 为 若 干 个 规 模 较 小 的 相 同 问 题 , 即 该 问 题 具 有 最 优 子 结 构性 质 ; ( 3) 利 用 该 问 题 分 解 出 的 子 问 题 的 解 可 以 合 并 为 该 问 题 的 解 ; ( 4) 该 问 题 所 分 解 出 的 各 个 子 问 题 是 相 互 独 立 的 , 即 子 问 题 之 间 不 包 含 公 共 的子 子 问 题 。 3、 分 治 法 的 基 本 步 骤 分 治 法 在 每 一 层 递 归 上 都 有 三 个 步 骤 : ( 1) 分 解 : 将 原 问 题 分 解 为 若 干 个 规 模 较 小
9、, 相 互 独 立 , 与 原 问 题 形 式 相 同 的子 问 题 ; ( 2) 解 决 : 若 子 问 题 规 模 较 小 而 容 易 被 解 决 则 直 接 解 , 否 则 递 归 地 解 各 个 子 问题 ; ( 3) 合 并 : 将 各 个 子 问 题 的 解 合 并 为 原 问 题 的 解 。快速排序在这种方法中, n 个元素被分成三段(组):左段 l e f t,右段 r i g h t 和中段 m i d d l e。中段仅包含一个元素。左段中各元素都小于等于中段元素,右段中各元素都大于等于中段元素。因此 l e f t 和 r i g h t 中的元素可以独立排序,并且不必对
10、 l e f t 和 r i g h t 的排序结果进行合并。m i d d l e 中的元素被称为支点 ( p i v o t )。图 1 4 - 9 中给出了快速排序的伪代码。 / /使用快速排序方法对 a 0 :n- 1 排序 从 a 0 :n- 1 中选择一个元素作为 m i d d l e,该元素为支点 把余下的元素分割为两段 left 和 r i g h t,使得 l e f t 中的元素都小于等于支点,而right 中的元素都大于等于支点 递归地使用快速排序方法对 left 进行排序 递归地使用快速排序方法对 right 进行排序 所得结果为 l e f t + m i d d
11、l e + r i g h t 考察元素序列 4 , 8 , 3 , 7 , 1 , 5 , 6 , 2 。假设选择元素 6 作为支点,则 6 位于 m i d d l e;4,3,1,5,2 位于 l e f t;8,7 位于 r i g h t。当 left 排好序后,所得结果为1,2,3,4,5;当 r i g h t 排好序后,所得结果为 7,8。把 right 中的元素放在支点元素之后, l e f t 中的元素放在支点元素之前,即可得到最终的结果 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 。 4把元素序列划分为 l e f t、m i d d l e 和 r i
12、g h t 可以就地进行(见程序 1 4 - 6) 。在程序1 4 - 6 中,支点总是取位置 1 中的元素。也可以采用其他选择方式来提高排序性能,本章稍后部分将给出这样一种选择。 程序 14-6 快速排序 template void QuickSort(T*a, int n) / 对 a0:n-1 进行快速排序 / 要求 an 必需有最大关键值 quickSort(a, 0, n-1); template void quickSort(T a, int l, int r) / 排序 a l : r , ar+1 有大值 if (l = r) return; int i = l, / 从左至右
13、的游标 j = r + 1; / 从右到左的游标 T pivot = al; / 把左侧= pivot 的元素与右侧= pivot 的元素 i = i + 1; while (a pivot); if (i = j) break; / 未发现交换对象 Swap(a, aj); / 设置 p i v o t al = aj; aj = pivot; quickSort(a, l, j-1); / 对左段排序 quickSort(a, j+1, r); / 对右段排序 贪婪法它采用逐步构造最优解的思想,在问题求解的每一个阶段,都作出一个在一 定标准下看上去最优的决策;决策一旦作出,就不可再更改。制
14、定决策的依据称为贪婪准则。贪婪法是一种不追求最优解,只希望得到较为满意解的方法。贪婪法一般可以快速得到满意的解,因为它省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪婪法常以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪婪法不要回溯。背包问题 问题描述:有不同价值、不同重量的物品 n 件,求从这 n 件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。#includevoid main()int m,n,i,j,w50,p50,pl50,b50,s=0,max;printf(“输入背包容量 m,物品种类 n :“);scanf
15、(“%d %d“,for(i=1;iplmax/wmax)max=j;plmax=0;bi=max;for(i=1,s=0;sj。因此,对于约束集 D 具有完备性的问题 P,一旦检测断定某个 j 元组(x 1,x 2,x j)违反 D 中仅涉及 x1,x 2,x j 的一个约束,就可以肯定,以(x 1,x 2,x j)为前缀的任何 n 元组(x 1,x 2,x j,x j+1,x n)都不会是问题 P 的解,因而就不必去搜索它们、检测它们。回溯法正是针对这类问题,利用这类问题的上述性质而提出来的比枚举法效率更高的算法。回溯法首先将问题 P 的 n 元组的状态空间 E 表示成一棵高为 n 的带权
16、有序树 T,把在 E 中求问题 P 的所有解转化为在 T 中搜索问题 P 的所有解。树 T 类似于检索树,它可以这样构造: 设 Si 中的元素可排成 xi(1) ,x i(2) ,x i(mi-1) ,|S i| =mi,i=1,2,n。从根开始,让 T 的第 I 层的每一个结点都有 mi 个儿子。这 mi 个儿子到它们的双亲的边,按从左到右的次序,分别带权 xi+1(1) ,x i+1(2) ,x i+1(mi) ,i=0 ,1,2,n-1。照这种构造方式,E 中的一个 n 元组(x 1,x 2,x n)对应于 T 中的一个叶子结点,T 的根到这个叶子结点的路径上依次的 n 条边的权分别为
17、x1,x 2,x n,反之亦然。另外,对于任意的0in-1,E 中 n 元组(x 1,x 2,x n)的一个前缀 I 元组(x 1,x 2,x i)对应于 T中的一个非叶子结点,T 的根到这个非叶子结点的路径上依次的 I 条边的权分别为8x1,x 2,x i,反之亦然。特别,E 中的任意一个 n 元组的空前缀() ,对应于 T 的根。因而,在 E 中寻找问题 P 的一个解等价于在 T 中搜索一个叶子结点,要求从 T 的根到该叶子结点的路径上依次的 n 条边相应带的 n 个权 x1,x 2,x n 满足约束集 D 的全部约束。在 T 中搜索所要求的叶子结点,很自然的一种方式是从根出发,按深度优先
18、的策略逐步深入,即依次搜索满足约束条件的前缀 1 元组(x 1i) 、前缀 2 元组(x 1,x 2) 、,前缀 I元组(x 1,x 2,x i) ,直到 i=n 为止。在回溯法中,上述引入的树被称为问题 P 的状态空间树;树 T 上任意一个结点被称为问题 P 的状态结点;树 T 上的任意一个叶子结点被称为问题 P 的一个解状态结点;树 T 上满足约束集 D 的全部约束的任意一个叶子结点被称为问题 P 的一个回答状态结点,它对应于问题 P 的一个解。【问题】 n 皇后问题 问题描述:求出在一个 nn 的棋盘上,放置 n 个不能互相捕捉的国际象棋“皇后”的所有布局。 这是来源于国际象棋的一个问题
19、。皇后可以沿着纵横和两条斜线 4 个方向相互捕捉。如图所示,一个皇后放在棋盘的第 4 行第 3 列位置上,则棋盘上凡打“”的位置上的皇后就能与这个皇后相互捕捉。1 2 3 4 5 6 7 8 Q 从图中可以得到以下启示:一个合适的解应是在每列、每行上只有一个皇后,且一条斜线上也只有一个皇后。 求解过程从空配置开始。在第 1 列至第 m 列为合理配置的基础上,再配置第 m+1列,直至第 n 列配置也是合理时,就找到了一个解。接着改变第 n 列配置,希望获得下一个解。另外,在任一列上,可能有 n 种配置。开始时配置在第 1 行,以后改变时,顺次选择第 2 行、第 3 行、直到第 n 行。当第 n
20、行配置也找不到一个合理的配置时,就要回溯,去改变前一列的配置。得到求解皇后问题的算法如下: 输入棋盘大小值 n; m=0; good=1; do if (good) 9if (m=n) 输出解; 改变之,形成下一个候选解; else 扩展当前候选接至下一列; else 改变之,形成下一个候选解; good=检查当前候选解的合理性; while (m!=0); 在编写程序之前,先确定边式棋盘的数据结构。比较直观的方法是采用一个二维数组,但仔细观察就会发现,这种表示方法给调整候选解及检查其合理性带来困难。更好的方法乃是尽可能直接表示那些常用的信息。对于本题来说, “常用信息”并不是皇后的具体位置,
21、而是“一个皇后是否已经在某行和某条斜线合理地安置好了” 。因在某一列上恰好放一个皇后,引入一个一维数组(col ) ,值 coli表示在棋盘第 i 列、coli行有一个皇后。例如:col3=4 ,就表示在棋盘的第 3 列、第 4 行上有一个皇后。另外,为了使程序在找完了全部解后回溯到最初位置,设定 col0的初值为 0 当回溯到第 0 列时,说明程序已求得全部解,结束程序运行。为使程序在检查皇后配置的合理性方面简易方便,引入以下三个工作数组: (1) 数组 a ,ak表示第 k 行上还没有皇后; (2) 数组 b ,bk表示第 k 列右高左低斜线上没有皇后; (3) 数组 c ,ck表示第 k
22、 列左高右低斜线上没有皇后; 棋盘中同一右高左低斜线上的方格,他们的行号与列号之和相同;同一左高右低斜线上的方格,他们的行号与列号之差均相同。 初始时,所有行和斜线上均没有皇后,从第 1 列的第 1 行配置第一个皇后开始,在第 m 列 colm行放置了一个合理的皇后后,准备考察第 m+1 列时,在数组 a 、b 和 c 中为第 m 列,colm行的位置设定有皇后标志;当从第 m 列回溯到第m-1 列,并准备调整第 m-1 列的皇后配置时,清除在数组 a 、b 和 c 中设置的关于第 m-1 列,colm-1行有皇后的标志。一个皇后在 m 列,colm行方格内配置是合理的,由数组 a 、b 和
23、c 对应位置的值都为 1 来确定。细节见以下程序: 【程序】 # include # include # define MAXN 20 int n,m,good; int colMAXN+1,aMAXN+1,b2*MAXN+1,c2*MAXN+1;void main() int j; char awn; printf(“Enter n: “); scanf(“%d”, 10for (j=0;j#includestruct Queueint weight ;struct Queue* next ;int bestw = 0 ; / 目前的最优值Queue* Q; / 活结点队列Queue* lq
24、 = NULL ;Queue* fq = NULL ;13int Add(int w)Queue* q ;q = (Queue*)malloc(sizeof(Queue) ;if(q =NULL)printf(“没有足够的空间分配n“) ;return 1 ;q-next = NULL ;q-weight = w ;if(Q-next = NULL)Q-next = q ;fq = lq = Q-next ; /一定要使元素放到链中elselq-next = q ;lq = q ;/ lq = q-next ;return 0 ;int IsEmpty()if(Q-next=NULL)retu
25、rn 1 ;return 0 ;int Delete(int/ fq = Q-next ;tmp = fq ;w = fq-weight ;Q-next = fq-next ; /*一定不能丢了链表头*/fq = fq-next ;free(tmp) ;return 0 ;void EnQueue(int wt,intelse Add(wt); / 不是叶子int MaxLoading(int w, int c, int n) / 返回最优装载值/ 为层次 1 初始化int err ; /返回值int i = 1; / 当前扩展结点的层int Ew = 0; / 当前扩展结点的权值bestw
26、= 0; / 目前的最优值Q = (Queue*)malloc(sizeof(Queue) ;Q-next = NULL ;Q-weight = -1 ;err = Add(-1) ; /标记本层的尾部if(err)return 0 ;while (true) / 检查左孩子结点if (Ew + wi = c) / xi = 1EnQueue(Ew + wi, bestw , i, n);/ 右孩子总是可行的EnQueue(Ew, bestw, i, n); / xi = 0Delete(Ew); / 取下一个扩展结点if (Ew = -1) / 到达层的尾部if (IsEmpty() ret
27、urn bestw;if(in)Add(-1); / 同层结点的尾部Delete(Ew); / 取下一扩展结点i+; / 进入下一层14 int main()int n =0 ; int c = 0 ;int i = 0 ;int* w ;FILE *in , *out ;in = fopen(“input.txt“ , “r“) ;out = fopen(“output.txt“ , “w“) ;if(in=NULL|out=NULL)printf(“没有输入输出文件n“) ;return 1 ;fscanf(in , “%d“ , fscanf(in , “%d“ , w = (int*)malloc(sizeof(int)*(n+1) ;for(i =1 ; i=n ; i+)fscanf(in , “%d“ , MaxLoading(w , c , n) ;fprintf(out , “%dn“ , bestw) ;return 0 ;