51.3算法形式规范 #inelude <stdio.h> #include sstdlib,h> #define MAX SIZE 101 #define SWAP(x,y,t)((t)=(x),(x)=(y),(y)=t) void sort (int []int);/+selection sort void main(void) int i,n; 1nt1ist[MAX SIZE】: printf("Enter_the_number_of_numbers_to_generate:"); scanf("d",sn); if (n 1 n>MAX_SIZE) fprintf(stderr,"Improper_value_of.n/n"); exit(EXIT_FAILURE); for (i 0;i n;i++)(/randomly generate numbers./ 11st[i1=rand()1000: printf("d",list [i]) gort1igt,n】: pritnf("In_Sorted array:\n_"); for(1=0;i<ni+) /print out sorted numbers printf("8d",list [i]); printf("\n"): void sort(int list[],int n) int i,j.min,temp; for(i=0;isn-1:i+){ min i: for(行-i+1;jn:j+) if (list[i]list [min]) min i: SWAP(list [i],list [min],temp) 程序1.4选择排序
§】。3算 法形式规范 #土 nc△ ude (std土 O。 h) #土nc△ ude <s乜 d1土 b.h> 艹de£ 土ne V⒇ 《 SIZE △ 0△ 艹de£ 土ne sWAP(x`y`t) ((t) = (x)` (x) = (y)` (y) = t) vo土 d sort(土 nt []' 土 nt)` /★ seⅠ ecε Ion sOrε ★ / vo土 d ma土 n(vo土 d) ( 土n△ i` n氵 土nt 1⊥ st[MAX SIZEl` pr土 ntf("Enter凵 the凵 number凵 ⊙ £ 凵 numbers凵 t° 凵 generate:凵 ")′ scanf("%d"` &n)氵 土£ (n < △ || n > MAx_SIZE) ( £pr⊥ ntf(stderr` "Improper凵 va1ue凵 ⊙ f凵 n/n")` exit(EX工 T FAILURE)` ) £ or (土 = 0' 土 ( n` i++) ( /★ rand。 mⅠ y generaε e numbers */ 1ist[i] = rand() % 10OO氵 pr土 nt£ ("%d凵 凵 "′ 1土 st[土 ])氵 ) s⊙ rt(1主 st` n)氵 pr土 tnf("\n凵 s° rted凵 array:\n凵 ")′ for (立 〓 0` 土 ( n氵 ⊥ ++) /★ pr立 nε out sOrted numbers ★ / printf("%d凵 凵 "′ 1ist[⊥ ])氵 pr土 ntf("\n")氵 ) vo土 d sort(主 nt 1土 st[]` 土 n△ n) ( 土n△ i` j` m土 n` temp' £or (i = 0' 土 ( n-△ 氵 土 +十 ) ( m土 n = 土 氵 £or (j = i十 1' j ( n' j++) 土£ (1土 st[j] ( 1土 st[m土 n]) m土n = j' SWAp(1⊥ st[i]` 1土 st[m⊥ n]` temp)' ) ) 程序 1。4选 择排序
10 第1章基本概念 while (there are more intergers to check){ middle(left+right)/2; if (searchnum list [middle]) right middle-1; else if (searchnum =list [middle]) return middle; else left middle+1; 程序1.5查找有序表 。若两者相等,则返同0。 ·若前者大于后者,则返问正数(1)。 我们且然给出函数(程序1.6)和宏两种实现,以后总是用宏实现,因为它适应各种数据 类型。 int compare(int x,int y) {/compare x and y,return-1 for less than,o for equal 1 for greater + ig (x sy)return-1; else it (x==y)return 0; else return 1; 程序1.6比较两个整数 宏实现: #define CoMPARE(x,y)(((x)<(y))?-1 ((x)==(y))0 1) 我现在着手解决第一个子任务:确定是否还有未在找的数据。同忆最初的算法,比较 操作之后,数组的卜标值可能向左移,也可能向石移,这么一直移下去,最终或者找到,或 者下标交义,即左边卜标值人于右边下标值。本米左、右下标是全表的两个端点,仵找在两 者之间进行,一口交义,则说明再也没有待查找的数据元素了。把上述内容合成后,我们得 到卜面的折半查找程序(见程序1.7)。 以上有找策略称为折半查找。 上面两个例子用C函数实现算法,实际上,把大程序分解为易于处理的多个部分,每部 分用一个或多个函数实现,是函数机制的根本日标。分解之后,各函数令程序更易读,同时 由于各函数可以分别测试,程序正确性的儿率也提高了。通常,我」在定义函数之前,要先 户明函数,这样的好处是,编译程序在分析过程遇到一个图数调用,已经知道该函数的合法 定义将现在后续代码。C语言中的函数可以一组一组分开编译,创建逻辑上功能相关的函 数库
10 第 】章 基本概念 while (there are more intergers to check) { middle = (Ieft + right) / 2 ; if (searchnum < list [mj-dd]el ) right=middle-1-; else if (searchnLrm == list [middle] ) return middle; else Left. = middle + 1; \ 程序 1.5查 找有序表 ·若两者相等,则 返冂 0。 ·若前者大于后者,则 返冂正数 (1)。 我们虽然给出函数 (程序 1.6)和 宏两种实现,以 后总足用宏实现,囚 为它适应各种数据 类型。 int compare(int x, int y) { /* compare x and y, return -7 for Tess than, 0 for equa7, 7 for greater */ if (x < y) return -L; else if (x y) return 0; else return I; \ ) 程序 1.6比 较两个整数 宏实现: #def土 ne COMpARE(x` y) (((x) ( (y)) ? -1 : ((x) 〓 = (y)) ? O : 1) 我们现在着手解决第一个 rˉ任务:确 定足否还有未奄找的数据。冂忆最初的算法,比 较 操作之后,数 纽的 卜标值可能向左移,也 可能向右移,这 么一直移 卜去,最 终或者找到,或 者 卜标交义,即 左边 卜标值人 J·右边下标值。本来左、右 卜标是全表的两个端点,佥 找在两 者之间进行,一 口^交义,则 说明再也没有待杏找的数据元素了。把上述 内容合成后,我 们得 到 卜面的折半杏找程序 (见程序 1.7)。 以上杏找策略称为折半查找。 □ 上面两个例子用 C函 数实现算法,实 际上 ,把 大不V序 分解为易于处理的多个部分,每 部 分用一个或多个函数实现,是 函数机制的根本 口标。分解之后,各 函数令稆序更易读,同 时, 由 F各 函数可以分别测试,程 序正确性的儿率也提高了。通常,我 们在定义函数之前,耍 先 卢明函数,这 样的好处是,编 泽稆序在分析过程遇到 一个函数调用,已 经知道该函数的合法 定义将出现在后续代码。C语 言中的函数可以一组一细~分开编 泽,创 建逻辑上功能相关的函 数库
1.3算法形式规范 11 int binsearch(int list[],searchnum,int left,int right) /search list [o]list [1]<=.<list [n-1]for searchnum. Return its position if found.Otherwise return-1./ int middle; whi1e(left<right){ middle (left +right)/2; switch(COMPARE(1ist [middle],searchnum))( case-1:left middle 1; break; caae 0:return middle; case 1:right middle-1; return-1; 程序1.7查找顺序表 51.32递归算法 刚入行的程序员常常把函数看作被其他函数调的对象,数执行白牙代码,然后返同 调用程序,这种看法是片面的,事实上,函数不但可以调用白牙(直接递),还可以调用其 它函数,其它函数也可以再调用该函数(间接递归)。递函数调川机制,不仪功能强人,还 常常可以极人地简化问题,也就是说,如果不用递1方法,算法可能非常复尔。递1之所以 可以人人简化问题,正是这里讨论递的原内。 计算机专业的一些学生,经常会把递门方法视为某种神秘的技巧,看作仅适用于一小类 特定问题,如计算阶乘、计算Ackermann函数一类问题。这种观点很不企面。实际上,任何 可以用赋值话句、主E-e1e语句、h11e语句实现的函数,都是叮以递)实现的,而H递门 实现通常要比循环实现更易理解。 那么,什么时候用递]表述算法史合适呢?这里仪给!一种判据:如果问题的长述本身 就是递」定义,那么,白然而然,采用递I方法流很合适:求解阶乘如此,求解Fibonacci 数列也是如此,求解项式系数还是如此。二项式系数的定义是: n! (()-m网 该式可用下式递归计算: (回)-(m)+(-) 以卜通过两个例子论述递归算法的构造过程。第一个例子把例12的折半找函数转为 递归函数。第二个例子生成一个表中字符的所行置换。 例13(折半查找)程序17是折半个找的循环实现,要把这个函数转化为递1函数,我所 做的:作是:(1)判断递归结来而建立边界条件,(2)实现递归调川,每次递门调州都向彻底
口。3算 法形式规范 n // binsearch(int Iist[], searchnum, int 1eft, int search list [0] Return its position if found. Otherwise return int middle; while (left <= right) { middle = (1eft + rLghL) /2; switch(COMPARE (list [middte] , searchnum) ) i case -1: Ieft = middle + 1 ; break; case 0: return middle; caae 1: right = middle - L ; ) ) reLurn -7; 程序 1.7查 找顺序表 §1.3.2 递 归算法 刚入行的稆序员常常把函数看作被其他函数调川的对象,函 数执行 fJ身代码,然 后返冂 调用程序,这 种看法楚片面的。丰实上,函 数不但可以调抖J白身 (直接递丿l),还 HT以 调用其 它函数,其 它函数也可以再调用该函数 (广自J接递归)。递丿丨函数凋川机制,不 仅功能强人,还 常常可以极人地简化问题,也 就足说,如 呆不川递丿|方法 ,箅 法可能非常复杂。递丿l之所以 石1以 人人简化问题,正 足这里讨论递丿Tl的原lkl。 计算机专业的 一些学生,经 常会把递班l方法视为某种神秘的技巧,希 作仅适用 J·一小类 特定问题,如 计算阶乘、计算 Ackermam函 数 一类问题。这种观点很不仑面。实际上 ,任 何 可以用赋值语旬、土£-e△臼e语 句、wh主△eW丨勹实现的函数,都 足I刂^以递丿l实现 F向,而 且递归 实现通常要比循环实现更易理解。 那么,什 么时候用递归灰述笄法 吏合适昵?这 Ⅱ!仅给出一种判据:如 米问题的表述本身 就足递归定义,那 么 ,臼 然而然,采 用递归方法就很合适:求 解阶乘如此,求 解 ∏bonacci 数列也是如此,求 解工项式系数还足如此。工项式系数的定 义足: right ) for searchnum. - / -L. * / ㈠ ⑾ 换硎。 可 以 用 下数 函 该 式 归 递 (苈 )=rPJ!(彳 一 PPl)! 例 1.3(折 半查找)程 序 1.7是 折半杏找的循环实现, 做的 I∶作足:(1)判 断递归结束而建立边界条件,⑵ r把 例 1.2的折半佥找函数转为 要把这个函数转化为递归函数,我 们所 实现递归调用,每 次递丿:]调用都 向彻底
第1章基本概念 解决问题的日标前进一步。通过仔细阅读程序1.7的数binsearch,.我们发现个找的结水 有两种情形:其一是查找成功(list [middle】=searchnum),其.:是价找不成功(左:卜标 交义)。对查找成功的情形,程序无需改动:但wh11e句成替换成等价的1语句。 为了让递归调用一步步接近最终结果,函数调州参州分别是新的1北下标值利和新的 x9t下杯值。程序1.8是递实现的折半在找。注意,尽管代码改动了,递归函数被调用的 形式与非递归的形式完全一致, int binsearch(int list[],searchnum,int left,int right) (/search list (0]<list11]<=.<list (n-1]for searchnum Return its position if found.Otherwise return-1.+/ int middle: if(1 eft right) middle (left right)/2; awitch (COMPARE(list [middle],searchnum))( case-1:return binsearch(list,searchnum,middle+1,right); case 0:return middle; case 1:return binsearch(list,searchnum,left,middle-1); 1 return-1; 程序1.8折半查我的递归实现 例1.4(置换)给定n之1个元素的集合.打印这个集合所有可能的置换。例如,给定集合 {a,b,c},它的所有置换是{《a,b,c),(a,c,b),(b,a,c),(b,c,a),(c,a,b),(c,b,a)}。容易看出,给定 n个元,共有!种置换。我通过观察集合{a,b,c,d,得到生成所行置换的简单算法,以 卜是算法的构造过程: (1)a跟在(b,c,d)的所有置换之后。 (2)b跟在(a,c,d)的所有置换之厅。 (3)c跟在(a,b,d)的所置换之后。 (4)d跟在(a,b,c)的所有置换之后。 “跟作所有·置换之后”是构造递]算法的关键,这句话启发我,如果解决了n一1 个元素的置换问题,n个元的置换问题就可以解决了。由此,我出序1.9。程序中 的1ist是字符数组。注意,开始调用函数的形式是pexm(1ist,0,n-1),这个函数递 生成所有置换,直到i=n。 读者可以州三个元素的集合{a,b,c}仿真程序1.9的执行。每次递归调用pexm都生成参 数1ist、1、n的局部新副本,每次调甲1都会改变,而n不变。参数1ist是指向数组的 针,数组的值在调用过程也保持不变。 ◇
’ 一 第 】章 基本概念 解决问题的口标由I进 一步。通过仔细阅读稆序 1.7的 函数 b土nsearch,我 们发现企找的纬束 有 两 种 情 形 :其 一 是 杏 找 成 功 (1土 st[m⊥ dd1e]=searchnum),其 ∷ 是 今 找 不 成 功 (/E朽 卜 标 交叉)。对佥找成功的情形,程 序无需改动;但 wh±1e语 句丿ψ竹换成笱价的 圭f语 句。 为了让递归调用一步步按近最终结宋,函 数 调川参鞋分别足新的 1e£t卜 标伉和新的 r土ght卜 标值。程序 1.8足 递归实现的折半今找。注悫,丿求符代码改动了,递 归函数被调川的 形式与非递归的形式完全一致 。 □ inL binsearch(int list[], searchnum, int left, int right) {/" search Iist [0] <= Tist [7] <= <= l-ist [n-tJ f or searchnum. Return its position if found. Otherwise return -7. * / int middle; if (left <= right) { middle = (Ieft + r:-ghL) /2; switch (COMPARE (list lmiddle] , searchnum) ) { case -1: relurn binsearch(1ist, searchnum, middle t I, right); ease 0: return middle; case 1: return binsearch(Iist, searchnum, Ieft, middle - 1); ) ) return -I; ) 程序 1,8折 半查找的递归实现 例 ⒈4(置 换)给 定 彳≥1个 元素的集合,打 印这个集合所仃llJ能的置换。例如,给 定集合 伽 'D'cl,它 的 所 有 置 换 是 f(四 '乡 'c)'(伢 'c'D)'(抄 '%c)'(乙 `c'日 )'(c'`'乙 )`(c'D'伢 ))o容 易 膏 出 ,给 定 ″个元素,共 有 彳!种 置换。我们通过观察集合 (四'D'c''),得 到土成所有置换f向简单算法,以 卜是算法的构造过程: (1)伢 跟 F+~(乙'c`')的 所仃苴换乏后。 ⑵ 乙跟在 (伢'c'歹)的 所有置换乏后。 ⑶ c跟 在 (伢`D'')的 所仃置换之后。 (茌)'跟 在 (伢'乙'c)的 所有置换之后。 “跟在所有 .·置换乏后 ”足构迕递丿:1算法的关键,这 旬活启发我们,如 宋解决了 ″-1 个元素的置换问题,则 彳个元素的置换门题就可以解决了。由此,我 们写出柠序 1.9。稆序中 的 1土st是 字符数组。注意,开 始 调用函数的形式足 perm(Ⅱ st`O`″ -1),这 个函数递少l 生成所有置换,直 到 J=彳 。 读者可以用二个元素的集合 扣、乙、cl仿 萁稆序 1,9的 执行。每次递归凋川 perm都 生成参 数 1土st、 ⊥、n的 局部新副本,每 次调HJ土 都会改变,而 n不 变。参数 1土st是 指 向数细的指 针,数 组的值在调用过稆也保持不变。 □
S1.3算法形式规范 13 void perm(char list,int i,int n) (/generate all the permutations of list(i]to list (n]+/ int j,temp if(i==n) for (j-0;j<-n;j++)printf(sc",list(j]); }e18e{ /list [i]to list (n]has more than one permutation, generate these recursively + for (j i;j <n;j++) SWAP(list [i],list[j],temp) perm(list,i+1,n): SWAP(list [i],list [j],temp) 程序1.9递归置换生成程序 在后续章节,我们还要给出更多递归函数,本书人量算法都是递算法,特别在第4 章的表算法'与第5章的一义树算法中,递归都会人域出现。 习题 上面的例子论述了如何将问题转化成程序,我避开了有关数据抽象与算法设计策略 的内容,仅专注于由问题的英语描述向函数的转换过程,或由循环算法向递归算法的转换。 卜列习题要求读者练习以上技能。对每个编程题,试着先确定算法,然后把它翻译成函数 再证明它是正确的。正确性的“证明”方法,可以是算法分析,也可以用恰当的测试数据。 1.请看以下两个句子: (a)使方程x”+y=z”有正整数解x,z的最人n值是2吗? b)把5除以0存入x然后转到语句10 两句话都不能满足算法判据中的某一条,指出分别是哪一条。 2.给定多项式 A(x)=anx+an-1x”-1+.+1x+0x0, 求多项式在o处的值,可HHorner规则。 A(xo)=(.(am0+n-1)x0++a1)x0+a0), Horner规则使多项式求值所需乘法次数最少。写出用Horner规则求值的C程序
§】。3算 法形式规范 13 vo土 d perm(char ★ 1土 s△ ` 土 nt 土 ` 土 nt n) ( /★ generaε e a且 Ι the permuε at立 ons Of Ι IstfiJ ε O Ⅰ 立 sε fnJ ★ / 土n△ j` temp氵 土 £ (土 ==n) ( for (j=0` j<=n氵 j++) pr主 nt£ ("老 C"` 1ist[j])氵 pr土 ntf("凵 凵 凵 凵 ")′ ) e△ge ( /★ Ⅰ iSt f1J to Ⅰ 立 sε fnJ has mOre than one permu亡 aε 立 on` generaε e 亡 hese recursⅠ veⅠ y */ f° r (j = 土 ` j <= n氵 j++) ( sWAp(1土 st[i]` 1土 st[j]` temp)氵 perm(1土 st` 土 十 △ ` n)` sWAp(1土 st[土 ]` 1土 st[j]` temp)氵 ) ) ) 程序 1.9递 归置换生成程序 在后续章节 ,我 们还要给 出更多递归函数,本 |氵大鞋算法都是递丿∷J算法,特 别在第 砼 章的表算法 JJ第 5章 的工义树算法屮,递 归都会人甘出现。 习题 上面的例f论 述了如何将 问题转化成Ts+序,我 们避开了仃关数据抽象 与算法设计策略 的内容,仅 专注 T由 问题的英语描述 向函数的转换过程,或 由循环算法 向递归算法的转换。 卜列习题要求读者练习以上技能。对每个编程题,试 着先确定算法,然 后把它翻 泽成函数, 再证明它楚正确的。正确性的 “证明 ”方法,可 以足算法分析,也 可以用恰当的测试数据。 1.请 看以 卜两个句子: (a)使 方程 x彳+y彳 =严 有正整数解x'y'Z的 晟人 ″值是 2吗 ? (b)把 5除 以 0存 入 t然 后转到语句 10。 两句话都不能满足算法判据中的某一条,指 出分别是哪一条。 2.给 定多项式 A(x)=%?x″ +rI/l1X冫 】 1+. 。+伢 1t1+伢 0γ 0' 求多项式在 幻 处的值,可 用 Horner规 则。 A(xo)=(· . ((伢 ″ xo+n,I1)X0+. · +伢 1)γ o+四 o)' Horner规 则使多项式求值所需乘法次数最少。写出用 Horner规 则求值的 C程 序