概念
(1) 迭代技术。反复执行一系列操作的最简单方法就是使用迭代结构,比如C语言中的for语句。
(2) 递归程序设计。C语言及其他众多语言都允许函数递归,即函数可以直接或间接地调用自己。对新手程序员来说,编写迭代程序通常比写递归程序更安全。递归程序更易于编写、分析和理解。
(3) 归纳证明。“归纳证明”是用来表明命题为真的一项重要技术。
(4)归纳定义。计算机科学的很多重要概念,特别是那些涉及数据模型的,最好用归纳的形式来定义,也就是我们给出定义该概念最简单形式的基本规则,以及可用来从该概念较小实例构建更大实例的归纳规则。
迭代
使用迭代,程序和算法可以在不需要单独指定大量相似步骤的情况下,执行重复性的任务,如“执行下一步骤1000次”。编程语言使用像C语言中的while语句和for语句那样的循环结构,来实现迭代算法。
下面举排序的例子:
要排序具有n个元素的表,我们需要重新排表中的元素,使它们按照非递减顺序排列。排序不仅会整理好各值的顺序,使每个元素的值小于等于接下来那个元素的值,而且不会改变每个值出现的次数。
只要表的元素有“小于”的顺序可言,也就是具备我们通常用符号<表示的关系,就可对这些元素排序。例如,如果这些值是实数或整数,那么符号<就表示实数或整数的小于关系;如果这些值是字符串,就按字符串的词典顺序来排列。有时候,当元素比较复杂,比如当元素是结构体时,就可能使用每个元素的一部分(比如某个特定字段)来进行比较。
a≤b这一比较关系总是表示,要么a<b,要么a和b具有相同的值。如果a1≤a2≤…≤an,也就是说,如果这些值有着非递减顺序,那么我们就说表(a1, a2, …, an)是已排序的。排序是这
样一种操作,它接受任意表(a1, a2, …, an),并生成满足如下条件的表(b1, b2, …, bn)。
(1) 表(b1, b2, …, bn)是已排序的;
(2) 表(b1, b2, …, bn)是原表的排列。也就是说,表(a1, a2,…, an)中的每个值出现的次数,和那些值出现在(b1, b2, …, bn)中的次数是一模一样的。
排序算法接受任意的表作为输入,并生成对输入进行过排列的已排序表作为输出。
假设要对一个具有n个整数的数组A按照非递减顺序排序。我们可以通过对这个步骤的迭代来完成该工作:找出尚不在数组已排序部分的一个最小元素,将其交换到数组未排序部分的第一个位置。在第一次迭代中,我们在整个数组A[0..n-1]中找出(“选取”)一个最小元素,并将其与A[0]互换位置。在第二次迭代中,我们从A[1..n-1]中找出一个最小元素,并将其与A[1]互换位置。继续进行这种迭代。在开始第i+1次迭代时,A[0..i-1]已经是将A中较小的i。个元素按照非递减顺序排序了,而数组中余下的元素则没有特定的顺序。在第i+1次迭代开始前数组A的状态如图所示。
在第i +1次迭代中,要找出A[i..n-1]中的一个最小元素,并将其与A[i]互换位置。因此,在经过第i +1次迭代之后,A[0..i]已经是将A中较小的i+1个元素按照非递减顺序排序了。在经过第 n +1次迭代之后,就完成了对整个数组的排序。
1 void SelectionSort(int A[],int n) 2 { 3 int i, j, small, temp; 4 for (i = 0; i < n-1; i++) 5 { 6 //将small置为剩余最小元素第一次出现时的下标 7 small = i; 8 for ( j = i+1; j < n; j++) 9 { 10 if (A[j] < A[small]) 11 { 12 small = j; 13 } 14 } 15 //到达这里时,small是A[i..n-1]中第一个最小元素的下标 16 //现在交换A[small]与A[i] 17 temp = A[small]; 18 A[small] = A[i]; 19 A[i] = temp; 20 } 21 }
在排序时,我们会对要排序的值进行比较操作。通常只对值的特定部分进行比较,而用于比较的这个部分就称为键。
归纳证明
归纳证明。“归纳证明”是用来表明命题为真的一项重要技术。下面是归纳证明最简单的一种形式。
我们有与变量n相关的命题S(n),希望证明S(n)为真。要证明S(n),首先要提供依据,也就是n为某个值时的命题S(n)。例如,我们可以令n = 0,并证明命题S(0)。接着,我们必须对归纳步骤加以证明,我们要证明,对应参数某个值的命题S,是由对应参数前一个值的相同命题S得出的,也就是说,对所有的n≥0,可从S(n)得到S(n+1)。例如,S(n)可能是常见的求和公式
这是说1到n这n个整数的和等于n(n+1)/2。特例可以是S(1),即等式在n为1时的情况,也就是1=1×2/2。归纳步骤就是要表明,由可以得出,前者就是S(n),是等式本身,而后者则是S(n+1),就是用n+1替换了等式中的n。
数学归纳法是种实用的技巧,可用来证明命题 S (n ) 对所有非负整数n都为真,或者更一般地说,对所有不小于某个下限的整数都成立。
现在,假设 S (n ) 是有关整数n的任意命题。在对命题 S (n ) 最简单的归纳证明形式中,要证明以下两个事实。
(1) 依据情况。多为 S(0) ,不过,依据可以是对应任意整数k的 S (k ) ,这样就是证明只有在n≥k时命题 S( n)成立。
(2) 归纳步骤。我们要证明对所有的 n≥0 (或者如果依据为 S ( k) ,则是对所有的 n≥k ),都可由 S (n ) 推出 S ( n+1) 。在证明过程中的这个部分,我们假设命题 S ( n) 为真。S ( n) 称为归纳假设,而且要假设它为真,接着我们必须证明 S ( n+1) 为真。
例子:
作为数学归纳法的示例,我们来证明如下命题 S (n )
命题。对任意的 n≥0 ,都有 S (n ) :
这就是说,从2的0次幂到2的n次幂,2的整数指数幂之和要比2的 n +1次幂小1。例如,1+2+4+8=16-1,证明过程如下。
依据。要证明该依据,我们将等式 S ( n) 中的n替换为0,这样 S ( n) 就成了
(2.2)
对i=0,等式(2.2)左边的和式中只有一项,这样(2.2)左边的和是20, 也就是1,而等式(2.2)右边是21-1,也就是2-1,其值同样是1。因此我们证明了 S (n ) 的依据,也就是说,我们证明了对于 n=0,该等式成立。
归纳。现在必须要证明归纳步骤。我们假设 S (n ) 为真,并证明将该等式中的n替换为 n +1后等式也成立。要证明的等式 S(n+1) 如下
(2.3)
要证明等式(2.3)成立,我们先要考虑等式左侧的和
这个和几乎与 S (n ) 左侧的和一模一样, S (n ) 左侧的和为
只不过等式(2.3)左侧多了i=n+1时的项,也就是2n+1这一项。因为可以假定归纳假设S(n)在等式的证明过程中为真,所以应该将S(n)利用起来。可以将等式(2.3)中的和分为两个部分,其中之一是S(n)中的和。也就是说,要讲i=n+1时的最后一项分离出来,将其写成
(2.4)。
现在可以利用S(n)了,可以用S(n)的右边2n+1-1来替换等式(2.4)中的
,于是有(2.5)
将等式(2.5)的右边化简后就成了2X2n+1-1,也就是2n+2 -1.现在可以看到等式(2.5)左侧的和值,与等式(2.3)的左边相同,而等式(2.5)的右边也与等式(2.3)的右边相同。因此,就利用等式S(n)证明了等式(2.3)的正确性,这段证明过程就是归纳步骤。由此得出的结论是,S(n)对每个非负整数n都成立。
在归纳证明中,我们先证明了 S(0) 为真。接下来要证明,如果 S ( n) 为真,那么 S (n+ 1) 是成立的。不过为什么接着能得出 S ( n) 对所有 n≥0 都为真呢?我们会提供两个“证据”。某位数学家曾指出,我们证实归纳法有效的每个“证据”,都需要归纳证据本身,因此就根本没有证据。从技术上讲,归纳肯定能作为公理,然而很多人会发现以下直觉认识也是有用的。
在接下来的内容中,我们假设作为依据的值是 n=0 。也就是说,我们知道 S(0) 为真,而且对于所有大于0的n,如果 S ( n) 为真,那么 S ( n+1) 为真。如果作为依据的值是其他整数,也可以做出类似的论证。
第一个“证据”:归纳步骤的迭代。假设要证明对某个特定的非负整数a有 S ( a) 为真。如果a = 0 ,只要援引归纳依据 S(0) 的真实性即可。如果 a > 0 ,那么就要进行如下论证。从归纳依据可知 S(0) 为真。对于命题“ S ( n) 可推出 S ( n+1) ”,若将n替换为0,就成了“ S(0) 可推出 S(1) ”。因为我们知道 S(0) 为真,现在就知道 S(1) 为真。类似地,如果用1替换n,就有“ S(1) 可推出 S(2) ”,这样一来就知道 S(2) 也为真。用2来替换n,则有“ S(2) 可推出 S(3) ”,所以 S(3) 也为真,以此类推。不管a取什么值,最终都能得到 S ( a) ,这样就完成了归纳。
第二个“证据”:最少反例。假设至少有一个n的值可以使 S (n ) 不为真。设a是令 S (a ) 为假的最小非负整数。如果a=0 ,就与我们的归纳依据 S(0) 相互矛盾,所以a一定是大于0的。不过如果a > 0,而且a是令 S ( a) 为假的最小非负整数,那么 S ( a) 肯定为真。现在,在归纳步骤中,如果用a -1代替n,就会有 S (a- 1)可推出 S (a ) 。因为 S ( a-1) 为真,那么 S ( a) 肯定为真,又相互矛盾了。因为我们假设存在非负整数n使 S (n ) 为假,并引出了矛盾,所以 S ( n) 对任何n≥0 都一定为真。
现在我们要介绍“检错码”的例子。检错码本身就是个有意思的概念,而且引出了一段有趣的归纳证明。当我们通过数据网络传输信息时,会将字符(字母、数字、标点符号,等等)编码成位串,即0和1组成的序列。此时假设字符是由7位表示的。不过通常每个字符要传输不止7位,而第8位可以用来检测一些简单的传输错误。也就是说,偶尔有那么一个0或1会因为传输噪声发生改变,结果接收到的就是相反的位,进入传输线路的0成了1,而1成了0。如果通信系统能在8位中的一位发生变化时发出通知,从而发出重传信号,将会很有用。
要检测某一位的改变,必须保证任意两个表示不同字符的位序列不只有一个位置不同。不然的话,如果那个位置发生变化,结果就成了代表另一个字符的代码,可能将没法检测到错误
的发生。例如,如果一个字符使用位序列01010101表示,而另一个由01000101表示,那么如果左起第4个位置发生改变,就会将前者变成后者。
要确保不同字符的代码不只有一个位置不同,方法之一是在惯用于表示字符的7位码前加上一个奇偶校验位。如果位序列中有奇数个1,则称其具有奇校验。如果位序列中有偶数个1,则其具有偶校验。我们选择的编码方式是以具有偶校验的8位码来表示字符,也可以选用带奇校验的代码。通过明智地选择校验位,我们可使奇偶校验成为偶校验。
用来表示字符A的传统的ASCII(音“ask-ee”,表示American Standard Code for InformationExchange,即“美国信息交换标准码”)7位码是1000001。该序列的7位中已经有偶数个1,所以我们为其加上前缀0,得到01000001。用来表示C的传统代码是1000011,这和表示A的7位码只在第6位是不同的。不过,这个代码具有奇校验,所以我们给它加上前缀1,从而产生具有偶校验的8位码11000011。请注意,在给表示A和C的代码前加上校验位后,就有了01000001和11000011这两个位序列,它们的第1位和第7位这两位是不同的,我们总是能选择一个奇偶校验位加到7位码上,从而让得到的8位码中有偶数个1。如果表示字符的7位码本来就有偶校验,就选择0作为其奇偶校验位,而对本具有奇校验的7位码,则是选择奇偶校验位1。不管哪种情况,8位码中都是包含偶数个1。
两个具有偶校验的位序列不可能只有一个位置不同。如果两个这样的位序列只有一个位置不同,那么其中一个肯定要比另一个多一个1。因此,一个序列必然具有奇校验,而另一个则是偶校验,与我们都具有偶校验的假设是矛盾的。因此可得,通过加上奇偶校验位使1的数量为偶,可为字符创建检错码。
奇偶校验位模式是相当“高效”的,从某种意义上讲,它让我们可以传输很多不同的字符。请注意,n位的位序列有2n 个,因为我们可以为第一位选择二值(0或1)之一,可以为第二位选择二值之一,等等,总共可形成2×2×…×2(n个2相乘)个位串,所以,最多有望能用8位来表示28=256个字符。
然而,在奇偶校验模式中,只能选择其中7位,第8位是无从选择的。因此最多可以表示27即128个字符,而且能检测某一位上的错误。这样也不错,我们可以用256个中的128个,也就是 8位码所有可能组合的一半,来作为字符的合法代码,还能检测某一位中出现的错误。
类似地,如果我们使用n位的位序列,选择其中一位作为奇偶校验位,那么就能用 n 1位的位序列加上合适的奇偶校验位前缀(其值由另外那 n -1位确定)来表示2n-1个字符,n位的位序列有2n个,我们可以表示2n中的2n-1个,或者说是可能字符数的一半,而且可以检测位序列中任意一位的错误。
有没有可能检测多个错误,并使用超过位序列多于一半的可能组合作为合法代码呢?下面的证明说明这是不可能的。这里的归纳证明使用的命题对0来说不为真,所以我们必须选用一个更大的归纳依据,也就是1。
(证明好麻烦,不写了)
归纳证明必须在某个地方表述“……而且通过归纳假设我们可知……”。如果没有的话,就不算是归纳证明。
简单归纳的模板:
(1)指定带证明的命题S(n)。表明自己要通过对n的归纳,对所有n≥i0,证明S(n)。这里的i0是作为归纳依据的常数,通常i0是0或1,不过它也可以是任意整数。直观地解释n的含义,比如,n是码字的长度。
(2)陈述依据情况,S(i0)。
(3)证明依据情况,也就是解释S(i0)为何为真。zhon
(4)陈述对某些n≥i0,假设有S(n),也就是陈述“归纳假设”,建立归纳步骤。用n+!替换命题S(n)中的n来表示S(n+1)。
(5)假定归纳假设S(n)为真,证明S(n+1)。
(6)得出S(n)对所有n≥i0都(但对更小的n不一定)为真结论
完全归纳
目前为止所看到的例子,在证明 S ( n+1) 为真时,都只用到了 S ( n) 作为归纳假设。不过,由于要对参数从归纳依据开始增加的值证明命题S,我们可以对从归纳依据到n的所有i的值使用S ( i) ,这种形式的归纳叫作完全归纳(有时也称为完美归纳或强归纳)。而上面的简单归纳形式,也就是只用 S ( n) 来证明 S( n+1) ,有时被称为弱归纳。
先来考虑一下如何进行从归纳依据 n = 0 开始的完全归纳。要通过以下两个步骤来证明S (n ) 对所有 n≥0 为真。
(1) 先证明归纳依据, S(0) 。
(2) 假设 S(0) , S(1) , …, S (n ) 全为真,作为归纳假设。从这些命题来证明 S (n+1) 成立。
至于弱归纳,也可以在选择0之外再选择某个值a作为归纳依据,然后证明S (a )归纳依据。而且在归纳步骤中,可以只假定 S (a ) , S ( a+1) , … S (n) 为真。请注意,弱归纳是完全归纳的一个特例,应用弱归纳,我们在之前的命题中只选择 S (n )来证明 S ( n+1) 。