伪代码
在《算法导论》中,主要用伪代码书写的程序形式来表达算法,这种伪代码在很多方面都与 C、Pascal 或 Java等语言比较相似。如果熟悉这几种语言的话,阅读书中的算法时应该不会有什么困难。伪代码与真实代码的不同之处在于,在伪代码中,可以采用最具表达力的、最简明扼要的方法,来表达一个给定的算法。有时,最清晰的方法就是英语,因此,当遇到在一段“真正的”代码中嵌入了一个英语短语或者句子的时候,不要感到惊讶。在伪代码和真正的代码之间还有一点区别,就是伪代码一般不关系软件工程方面的问题。亦即,数据抽象、模块化和错误处理等问题往往都被忽略掉了,以便更简练的表达算法的核心内容。
插入排序伪代码
INSERTION - SORT( A )
1 for j = 2 to Length[A]
2 key = A[j]
3 ▷Insert A[j] into the sorted sequence A[1..j-1]
4 i = j - 1
5 while( i > 0 && A[j] > key )
6 A[i+1] = A[i]
7 i = i-1;
8 A[i+1] = key
伪代码约定
1)书写上的“缩进”表示程序中的分程序(程序块)结构。
2)while,for,repeat 等循环结构和 if,then,else 条件结构域 Pascal相同。然后,对for循环来说有一点小小的不同:在Pascal中,循环计数器变量在退出循环时是未定义的,但在本书中,在退出循环后,循环计数器的值仍然保持。于是,紧接着一个for循环之后,循环计数器的值就是第一个超出for循环终值的那个数字。
3)符号“▷ ”表示后面部分是个注释。
4)变量(如i,j,key等)是局部于给定过程的。在没有显式说明的情况下,我们不使用全局变量。
5)数组元素通过“数组名[下标]”这样的形式来访问的。
7)复合数据一般组织为对象,他们是由属性和域组成的。域的访问是由域名后跟由方括号扩朱的对相应形式来表示。例如,数组可以被看做一个对象,其属性有length,表示数组中元素的个数,如length[A] 就表示数组A中的元素个数。在表示数组元素和对象属性时,都要用到方括号,一般来说,通过上下文就可以看出其含义。
用于表示一个数组或对象的变量被看做是指向表示数组或对象的数据的一个指针。对于某个对象 x 的所有于 f ,赋值 y = x就使得 f[y] = f[x] .更进一步,如果有 f[x] = 3,则不仅有 f[x] = 3,同时 f[y] = 3。换言之,在赋值 y = x 后,x 和 y指向同一对象。
有时,一个指针不指向任何对象。这时,我们赋给它 NIL。
8)参数采用按值传递方式:被调用的过程会收到参数的一份副本。
9)布尔运算符 “and” 和 “or” 都具有短路能力。亦即,当我们求表达式“x and y”的值时,首先计算 x 的值。如果 x 的值 为FALSE,那么整个表达式的值就不肯那个为 TRUE了,因此,就无需再对 y 求值了。or的情况类似。
短路运算符,允许我们写出如“ x != NIL and f[x] = y”这样的布尔表达式,而不用担心当我们在试图 x 为 NIL 时计算f[x],会发生怎样的情况。
算法分析
对一个算法所需要的资源进行预测。内存、通信带宽或计算机硬件等资源偶尔是我们主要关心的,但通常,资源是指我们希望测度的计算时间。
在分析一个算法之前,要建立有关实现技术的模型,包括描述所用资源的及代价的模型。本书主要采用一种通用的单处理器、随机存取机(random access machine,RAM)计算模型来作为我们得实现技术,算法可以用计算机程序来实现。在RAM模型中,指令一条接一条地执行,没有并发操作。
输入规模
与具体问题有关。对许多问题来说(如排序或计算离散傅里叶变换),最自然的度量标准是输入中的元素个数,例如,待排序数组的大小 n。对另一些问题(如两个整数相乘),其输入规模的最佳度量是输入数在二进制表示下的位数。有时,用两个数(而不是一个)来表示输入可能更合适。例如,某一算法的输入是个图,则输入规模可以由图中定点数和
边数来表示。在下面讨论的每一个问题中,我们都将指明所用的度量标准。
算法的运行时间
在待定输入时,所执行的基本操作数(或步数)。可以很方便地定义独立于具体机器的“步骤”概念。目前,采用以下观点,每执行一行伪代码都要花一定量的时间。虽然每一行所花的时间可能不同,但我们假定每次执行第 i 行所化的时间都是常量 ci。这种观点与 RAM 模型是一致的,同时也反映出了 伪代码在多数真实计算机上是如何实现的。
增长的量级
运行时间的增长率,或称为增长的量级。我们只考虑公式中的最高次项,例如,插入排序的最坏时间情况代价为O(n^2)。
在输入的规模较小的时候,由于常数项和低次项的影响,这种看法有时可能是不对的,但是对规模足够大的输入来说,一个具有O(n^2)的算法再最坏情况下,比O(n^3)的算法运行得更快。
分治法
有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次或多次地递归调用其自身来解决相关的子问题。这些算法通常采用分治策略:将原问题划分成 n 个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
分治模式在每一层递归上都有三个步骤:
分解(Divide):将原问题分解成一系列问题;
解决(Conquer):递归地解决各子问题。若子问题足够小,则直接求解(递归出口)。
合并(Combine):将子问题的结果合并成原问题的解。
合并排序
直观操作如下:
分解:将 n 个元素分成各含 n/2 个元素的子序列。
解决:用合并排序法对两个子序列递归地排序。
合并:合并两个已排序的子序列以得到排序结果。
在对子序列排序时,其长度为 1 时递归结束。单个元素被视为是已排序好的。
伪代码:
Merge(A, p, q, r)
1 n1 = q - p + 1
2 n2 = r - q
3 create L【1..n1+1】 and R【1...n2+1
】
4 for i = 1 to n1
5 do L【i】= A【p + i -1】
6 for j = 1 to n2
7 do R【j】 = A【p + j】
8 L【n1 + 1】 =
9 R【n2 + 1】 = ∞
10 i = 1
11 j = 1
12
for k = p to r
13
do if L【i】<= R【j】
14
then A【k】 = L【i】
15 i = i + 1
16
else A【k】 = R【j】
17 j = j + 1
Merge-Sort(A,p,r)
1 if p < r
2 then q = (p+r) / 2
3 Merge-Sort(A,p,q)
4 Merge-Sort(A,q+1,r)
5 Merge(A,p,q,r)
以上算法易出错的点是 范围的确定,要仔细对应斟酌。递归思想(分治法)是理解重点。
当一个算法含有其对自身的调用时,其运行时间可以用一个递归方程表示。
c语言代码实现
/*复习折半查找、简单顺序查找、插值查找*/
#include<stdio.h>
#include<stdlib.h>
typedef struct
{
int r[10]; /* 用于存储要排序数组,r[0]用作哨兵或者临时变量 */
int length;
} SqList;
/* 对顺序表 L 作直接插入排序 */
void InsertSort( SqList *L )
{
int key, i, j;
for( i = 1; i < L->length; i++ )
{
key = L->r[i];
for( j = i-1; j >=0 && L->r[j] > key; j-- )
L->r[j+1] = L->r[j];
L->r[j+1] = key;
}
}
void Merge(SqList *S, int p, int q, int r)
{
int n1 = q - p + 1;
int n2 = r - q ;
int i, j, k, *L, *R;
L = (int *)malloc(sizeof(n1+2));
R = (int *)malloc(sizeof(n2+2));
for( i = 1; i <= n1; i++)
L[i] = S->r[p+i-1];
for( j = 1; j <= n2; j++)
R[j] = S->r[q+j];
L[n1+1] = 55555;
R[n2+1] = 55555;
i = 1;
j = 1;
for( k = p; k <= r; k++)
{
if( L[i] <= R[j] )
{
S->r[k] = L[i];
++i;
}
else
{
S->r[k] = R[j];
++j;
}
}
}
void Merge_Sort(SqList *L, int p, int r)
{
int q;
int i;
if( p < r)
{
q = ( p + r)/2;
Merge_Sort(L, p, q);
for(i = p; i <= q ; i++)
printf("%d\t",L->r[i]);
printf("\n");
Merge_Sort(L, q+1, r);
for( i = q+1; i <= r ; i++)
printf("%d\t",L->r[i]);
printf("\n");
Merge(L, p, q, r);
for( i = p; i <= r; i++)
printf("%d\t",L->r[i]);
printf("\n");
}
}
void main()
{
SqList L = {{1000,3,8,15,2,2,35,23,3,10},5};
int i;
Merge_Sort( &L, 1, 9 );
}