Posts Tagged ‘笔记’

Notes of C Programming FAQs (5)

Wednesday, May 13th, 2009

下面是一些通常的检查要点:
² 未初始化的局部变量
² 整数上溢, 特别是在一些16 比特的机器上, 一些中间计算结果可能上溢, 象a * b / c
² 未定义的求值顺序
² 忽略了外部函数的说明, 特别是返回值不是int 的函数, 或是参数“缩小” 或可变的函数
² 复引用空指针
² malloc/free 的不适当使用: 假设malloc 的内存都被清零、已释放的内存还可用、再次释放已释放内存、malloc 的内部被破坏
² 指针类常规问题
² printf() 格式与参数不符, 特别是用%d 输出long int
² 试图分配的内存大小超出一个unsigned int 类型的范围, 特别是在内存有限的机器上
² 数组边界问题, 特别是暂时的小缓冲, 也许用于sprinf() 来构造一个字符串
² 错误的假设了typedef 的映射类型, 特别是size t
² 浮点问题
² 任何你自己认为聪明的在特定机器上的机器代码生成小技巧

“Segmentation violation”, “Bus error” 和“General protectionfault” 意味着什么?
通常, 这意味着你的程序试图访问不该访问的内存地址, 一般是由于堆栈出错或是不正确的使用指针。可能的原因有: 局部数组溢出(用堆栈分配的自动变量);不小心, 用了空指针、未初始化指针、地址未对齐的指针或其它没有适当分配的指针; malloc 内部被破坏; 函数调用参数不匹配, 特别是如果用了指针, 两个可能出错的函数是scanf()和fprintf() (确定他的第一个参数是FILE *)。

“印第安山风格指南” (Indian Hill Style Guide)

用gcc -Wall -pedantic替代lint

怎样显示一个百分比或“转动的短棒” 的进展表示器?
这个简单的事情, 你可以做到相当的可移植。输出字符’\r’ 通常可以得到一个回车而没有换行, 这样你就可以复写当前行。字符’\b’ 代表退格, 通常会使光标左移一格。记住要调用fflush()。

使用数据库来取代平坦文件

怎样抓获或忽略像control-C 这样的键盘中断?
基本步骤是调用signal():
#include <signal.h>
singal(SIGINT, SIG_IGN);
就可以忽略中断信号, 或者:
extern void func(int);
signal(SIGINT, func);
使程序在收到中断信号时, 调用函数func()。

怎样判断机器的字节顺序是高字节在前还是低字节在前?
有个使用指针的方法:


int x = 1;
if(*(char *)&x == 1)
    	printf("little-endian\n");
else
    	printf("big-endian\n");

另外一个可能是用联合。

从其他语言转换到C的工具:p2c, ptoc, f2c

由一个日期, 怎样知道是星期几?
这个由Tomohiko Sakamoto 提供的优雅的代码:


int dayofweek(int y, int m, int d) /* 0 = Sunday */
{
        static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
        y -= m < 3;
        return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}

自打印程序:


char*s="char*s=%c%s%c;main(){printf(s,34,s,34);}";main(){printf(s,34,s,34);}

还有一个有James Hu 发布的改进版:


#define q(k)main(){return!puts(#k"\nq("#k")");}
q(#define q(k)main(){return!puts(#k"\nq("#k")");})

什么是“达夫设备” (Duff’s Device)?
这是个很棒的迂回循环展开法, 由Tom Duff 在Lucasfilm 时所设计。它的“传统” 形态, 是用来复制多个字节:


register n = (count + 7) / 8; /* count > 0 assumed */
switch (count % 8)
{
case 0: do { *to = *from++;
case 7:      *to = *from++;
case 6:      *to = *from++;
case 5:      *to = *from++;
case 4:      *to = *from++;
case 3:      *to = *from++;
case 2:      *to = *from++;
case 1:      *to = *from++;
            	} while (--n > 0);
}

这里count 个字节从from 指向的数组复制到to 指向的内存地址(这是个内存映射的输出寄存器, 这也是为什么它没有被增加)。它把swtich 语句和复制8 个字节的循环交织在一起, 从而解决了剩余字节的处理问题(当count 不是8 的倍数时)。相信不相信, 象这样的把case 标志放在嵌套在swtich 语句内的模块中是合法的。当他公布这个技巧给C 的开发者和世界时, Duff 注意到C 的swtich 语法, 特别是“跌落” 行为, 一直是被争议的, 而“这段代码在争论中形成了某种论据, 但我不清楚是赞成还是反对”。

Notes of C Programming FAQs (4)

Wednesday, May 13th, 2009

\n 在scanf 格式串中不表示等待换行符, 而是读取并放弃所有的空白字符。

在scanf() 转换数字的时候, 它遇到的任何非数字字符都会终止转换并被保留在输入流中。因此, 除非采用了其它的步骤, 那么未预料到的非数字输入会不断“阻塞” scanf(): scanf() 永远都不能越过错误的非数字字符而处理后边的合法数字字符。

通常先用类似fgets() 的函数读入整行, 然后再用sscanf() 或其它技术解释。

要避免溢出问题, 你可以使用限制长度的sprintf() 版本, 即snprintf()。这样使用:snprintf(buf, bufsize, “You typed \”%s\”", answer);
snprintf() 在几个stdio 库中已经提供好几年了, 包括GNU 和4.4bsd。在C99中已经被标准化了。作为一个额外的好处, C99 的snprintf() 提供了预测任意sprintf() 调用所需的缓冲区大小的方法。C99 的snprintf() 返回它可能放到缓冲区的字符数, 而它又可以用0 作为缓冲区大小进行调用。因此nch = snprintf(NULL, 0, fmtstring, /* 其它参数*/ );这样的调用就可以预测出格式串扩展后所需要的字符数。

没有什么标准的办法可以丢弃标准输入流的未读取字符, 即使有, 那也不够,因为未读取字符也可能来自其它的操作系统级的输入缓冲区。

读取二进制数据文件的时候你应该用“rb” 调用fopen(), 确保不会发生文本文件的解释。类似的, 写二进制文件时, 使用“wb”。注意文本/二进制区别只是发生在文件打开时: 一旦文件打开之后, 在其上调用何种I/O 函数无关紧要。

怎样产生标准分布或高斯分布的随机数?
这里有一个由Marsaglia 首创Knuth 推荐的方法:


#include <stdlib.h>
#include <math.h>
double gaussrand()
{
    	static double V1, V2, S;
    	static int phase = 0;
    	double X;
    	if(phase == 0) {
    	    	do {
            	double U1 = (double)rand() / RAND_MAX;
            	double U2 = (double)rand() / RAND_MAX;
            	V1 = 2 * U1 - 1;
            	V2 = 2 * U2 - 1;
            	S = V1 * V1 + V2 * V2;
    	    	} while(S >= 1 || S == 0);
    	    	X = V1 * sqrt(-2 * log(S) / S);
    	} else
        	X = V2 * sqrt(-2 * log(S) / S);
    	phase = 1 - phase;
    	return X;
}

大多数电脑都是用二进制来表示浮点和整数的。在十进制里, 0.1 是个简单、精确的小数, 但是用二进制表示起来却是个循环小数0.0001100110011 . . . 。

“参数默认晋级” 规则适用于在可变参数中的可变动部分: 参数类型为float 的总是晋级(扩展) 到double, char 和short int 晋级到int。所以va arg(arpg, float)是错误的用法。应该总是用va arg(arpg, double)。

程序在执行用之前就崩溃了, 用调试器单步跟进, 在main() 之前就死了。
也许你定义了一个或多个非常大的局部数组(超过上千字节)。许多系统只有固定大小的堆栈, 即使那些自动动态堆栈分配的系统也会因为一次性要分配大段堆栈而失败。
一般对大规模数组, 定义为静态的数组会更好。如果由于递归的原因, 每次都需要一组新的数组, 可以用malloc() 动态申请内存。

Notes of C Programming FAQs (3)

Wednesday, May 13th, 2009

一般地说, 使用指针的时候, 你必须总是考虑内存分配, 除非明确知道编译器替你做了此事。

那么返回字符串或其它集合的争取方法是什么呢?
返回指针必须是静态分配的缓冲区, 或者调用者传入的缓冲区, 或者用malloc() 获得的内存, 但不能是局部(自动) 数组。

sizeof(char) 严格为1。

一个常见的bug 是用malloc(strlen(s)) 而不是strlen(s) + 1。

当你调用free() 的时候, 传入指针指向的内存被释放, 但调用函数的指针值可能保持不变, 因为C 的按值传参语义意味着被调函数永远不会永久改变参数的值。

calloc(m, n) 本质上等价于 p = malloc(m * n); memset(p, 0, m * n); 填充的零是全零, 因此不能确保生成有用的空指针值或浮点零值。free() 可以安全地用来释放calloc() 分配的内存。

C语言中的字符常数是int 型, 因此sizeof(’a’) 是sizeof(int),这是另一个与C++ 不同的地方。

如果你认为“if((a == b) == TRUE)” 比“if(a == b)” 好, 为什么就此打住呢?为什么不使用“if(((a == b) == TRUE) == TRUE)” 呢?

如果宏体内的语句都是简单语句, 没有声明或循环, 那么还有一种技术, 就是写一个单独的, 用一个或多个逗号操作符分隔的表达式。这种技术还可以“返回” 一个值。

作为一般规则, 你应该把这些东西放入头(.h) 文件中:
² 宏定义(预处理#defines)
² 结构、联合和枚举声明
² typedef 声明
² 外部函数声明
² 全局变量声明
当声明或定义需要在多个文件中共享时, 尤其需要把它们放入头文件中。特别是, 永远不要把外部函数原型放到.c 文件中。
另一方面, 如果定义或声明为一个.c 文件私有, 则最好留在.c 文件中。

我在文件的第一个声明就遇到奇怪的语法错误, 但是看上去没什么问题。可能你包含的最后一个头文件的最后一行缺一个分号。

C99 引入了对参数个数可变的函数式宏的正式支持。在宏“原型” 的末尾加上符号… (就像在参数可变的函数定义中), 宏定义中的伪宏__VA_ARGS__ 就会在调用时替换成可变参数。

“const char *p” (也可以写成“char const *p”) 声明了一个指向字符常量的指针, 因此不能改变它所指向的字符; “char * const p” 声明一个指向(可变) 字符的指针常量, 就是说, 你不能修改指针。

在使用符号粘接操作符## 连接两个宏的值(而不是名字) 时也要采用同样的“迂回战术”。

当你希望把宏参数转成字符串时,你可以使用新的预处理操作符# 和字符串常量连接(ANSI 的另一个新功能)

如果源和目的参数有重叠, memmove() 提供有保证的行为。而memcpy()则不能提供这样的保证, 因此可以实现得更加有效率。

保存getchar 的返回值的变量必须是int 型。getchar() 可能返回任何字符值, 包括EOF。如果把getchar 的返回值截为char 型, 则正常的字符可能会被错误的解释为EOF, 或者EOF 可能会被修改(尤其是char 型为无符号的时候), 从而永不出现。

在C 语言中, 只有输入例程试图读并失败以后才能得到文件结束符。换言之,C 的I/O 和Pascal 的不一样。通常你只需要检查输入例程的返回值, 例如, fgets()在遇到文件结束符的时候返回NULL。实际上, 在任何情况下, 都完全没有必要使用feof()。

我如何在printf 的格式串中输出一个’%’?我试过\%, 但是不行。
只需要重复百分号: %%。
\%不行, 因为反斜杠\ 是编译器的转义字符, 而这里我们的问题最终是printf的转义字符。

在printf 中使用%lf 不正确。scanf() 需要%lf,printf 的%f 标识符的确既可以输出浮点数又可以输出双精度数。printf() 的确接受%Lf, 用于输出长双精度数。

对于size t 那样的类型定义, 当我不知道它到底是long 还是其它类型的时候, 我应该使用什么样的printf 格式呢?
把那个值转换为一个已知的长度够大的类型, 然后使用与之对应的printf 格式。例如, 输出某种类型的长度, 你可以使用
printf(“%lu”, (unsigned long)sizeof(thetype));

我如何用printf 实现可变的域宽度?就是说, 我想在运行时确定宽度而不是使用%8d?
printf(“%*d”, width, x) 就能达到你的要求。

Notes of C Programming FAQs (2)

Wednesday, May 13th, 2009

*((condition) ? &a : &b) = complicated_expression;

*p++ 和*(p++) 等价,如果副作用的顺序无关紧要也可以使用++*p。

对函数extern int f(int *);用引用方式传入一个常数。在C99 中, 可以使用“复合常量”:f((int[]){5});

最初, 一个函数指针必须用* 操作符(和一对额外的括弧) “转换为” 一个“真正的” 函数才能调用:
int r, func(), (*fp)() = func;
r = (*fp)();
而函数总是通过指针进行调用的, 所有“真正的” 函数名总是隐式的退化为指针(在表达式中, 正如在初始化时一样。)。这个推论表明无论fp 是函数名和函数的指针
r = fp();
ANSI C 标准实际上接受后边的解释, 这意味着* 操作符不再需要, 尽管依然允许。

execl(“/bin/sh”, “sh”, “-c”, “date”, (char *)0);
如果省略最后一个参数的(char *) 转换, 则编译器无从知道这是一个空指针,从而当作一个0 传入。(注意很多Unix 手册在这个例子上都弄错了。)

有两条简单规则你必须遵循:
1. 当你在源码中需要空指针常数时, 用“0” 或“NULL”。
2. 如果在函数调用中“0” 或“NULL” 用作参数, 把它转换成被调函数需要的指针类型

一个图形胜过千言万语。声明
char a[] = “hello”;
char *p = “world”;
将会初始化下图所示的数据结果:


   +---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
   +---+---+---+---+---+---+
   +-----+     +---+---+---+---+---+---+
p: |  *======> | w | o | r | l | d |\0 |
   +-----+     +---+---+---+---+---+---+

说数组和指针“等价”不表示它们相同, 甚至也不能互换。它的意思是说数组和指针的算法定义可以用指针方便的访问数组或者模拟数组。

一个T 的数组类型的左值如果出现在表达式中会蜕变为一个指向数组第一个成员的指针(除了三种例外情况); 结果指针的类型是T 的指针。

一旦数组出现在表达式中, 编译器会隐式地生成一个指向数组第一个成员地指针, 就像程序员写出了&a[0] 一样。例外的情况是, 数组为sizeof 或&操作符的操作数, 或者为字符数组的字符串初始值。

C99引入了变长数组(VLA),局部数组的大小可以用变量或其它表达式设置, 可能也包括函数参数。

如果你向函数传递二位数组:
int array[NROWS][NCOLUMNS];
f(array);
那么函数的声明必须匹配:
void f(int a[][NCOLUMNS])
{ … }
或者
void f(int (*ap)[NCOLUMNS]) /* ap 是个数组指针*/
{ … }

Notes of C Programming FAQs (1)

Wednesday, May 13th, 2009

尽管字符类型(尤其是无符号字符型) 可以当成“小” 整型使用, 但由于不可预知的符号扩展和代码增大有时这样做可能得不偿失。

定义是分配空间并赋初值(如果有) 的声明。

永远不要把外部函数的原型放到.c 文件中: 通常它与定义的一致性不能得到检查, 而矛盾的原型比不用还糟糕。

在extern int f();和int f();之间并没有实质的区别。

使用cdecl 程序, 它可以把英文翻译成C 或者把C 翻译成英文

一本好的C 语言书都会解释如何“从内到外” 解释和理解这样复杂的C 语言声明(“模拟声明使用”)。

在范围内没有声明就调用(可能是第一次调用在函数的定义之前) 的函数被认为返回整型(int) (且没有任何参数类型信息), 如果函数在后边声明或定义成其它类型就会导致矛盾。所有函数(非整型函数一定要) 必须在调用之前声明。

calloc() 获得的内存为全零, 但这对指针和浮点值不一定有用

以下的初始化有什么区别?char a[] = “string literal”; char *p = “string literal”; 当我向p[i] 赋值的时候, 我的程序崩溃了。
字符串常量有两种稍有区别的用法。用作数组初始值(如同在char a[] 的声明中), 它指明该数组中字符的初始值。其它情况下, 它会转化为一个无名的静态字符数组, 可能会存储在只读内存中, 这就是造成它不一定能被修改。在表达式环境中, 数组通常被立即转化为一个指针, 因此第二个声明把p 初始化成指向无名数组的第一个元素。

int (*fp)() = func;

这样声明结构的代码: struct name { int namelen; charnamestr[1];}; 然后又使用一些内存分配技巧使namestr 数组用起来好像有多个元素。这种技术十分普遍。这些“亲密” 结构都必须小心使用, 因为只有程序员知道它的大小, 而编译器却一无所知。
C99 引入了“灵活数组域” 概念, 允许结构的最后一个域省略数组大小。这为类似问题提供了一个圆满的解决方案。

如何向接受结构参数的函数传入常数值?
传统的C 没有办法生成匿名结构值; 你必须使用临时结构变量或一个小的结构生成函数。
C99 标准引入了“复合常量” (compound literals); 复合常量的一种形式就可以允许结构常量。例如, 向假想plotpoint() 函数传入一个坐标对常数, 可以调用plotpoint((struct point){1, 2});
与“指定初始值” (designated initializers) (C99 的另一个功能) 结合, 也可以用成员名称确定成员值:plotpoint((struct point){.x=1, .y=2});

如何确定域在结构中的字节偏移?
ANSI C 在<stddef.h> 中定义了offsetof() 宏, 用offsetof(struct s, f) 可以计算出域f 在结构s 中的偏移量。如果出于某种原因, 你需要自己实现这个功能, 可以使用下边这样的代码:
#define offsetof(type, f) ((size_t)
((char *)&((type *)0)->f – (char *)(type *)0))
这种实现不是100% 的可移植; 某些编译器可能会合法地拒绝接受。

a[i] = i++; 不能工作

表达式: a ˆ= b ˆ= a ˆ= b不具有可移植性。它试图在序列点之间两次修改变量a, 而这是无定义的。

如果一个表达式和程序变得未定义, 则它的所有方面都会变成未定义。

Notes of UNIX SYSTEMS Programming (1)

Thursday, May 7th, 2009

(from Chapter 1 to Chapter 2, except for the Exercises)

(Example 1.5) The following code segment does not have a buffer overflow.

char buf[80];
printf(“Enter your first name:”);
scanf(“%79s”, buf);

(Program 1.1) print address of pointers using %p:

int x;
printf(“&x : %pn”, (void *) &x);

Static variables can make a program unsafe for threaded execution. In other words, they are not thread-safe.

External static variables also make code more difficult to debug because successive invocations of a function that references a static variable may behave in unexpected ways.

For these reasons, avoid using static variables except under controlled circumstances.

A common mapping divides the program image into equal-sized pieces, called pages.

Standard approaches to handling errors in UNIX programs include the following.

  • Print out an error message and exit the program (only in main).
  • Return -1 or NULL, and set an error indicator such as errno.
  • Return an error code.

Guidelines for implementing functions: (partial)

  • Do not exit from functions.
  • Do not use static variables or dynamic memory allocation if automatic allocation will do just as well.
  • Analyze the consequences of interruptions by signals.
  • Carefully consider how the entire program terminates.

In writing general library programs, you should avoid imposing unnecessary a priori limitations on sizes.

When using malloc or a related call, analyze whether to free the memory if an error occurs or when the function returns.

(using the C library function strtok to split a string into tokens) The first call to strtok is different from subsequent calls. On the first call, pass the address of the string to parse as the first argument. On subsequent calls for parsing the same string, pass a NULL.

strtok is not thread-safe
char *strtok_r(char *restrict s, const char *restrict sep, char **restrict lasts);

Static variables are commonly used in the C implementation of a data structure as an object. The data structure and all the functions that access it are placed in a single source file, and the data structure is defined outside any function. The data structure has the static attribute, giving it internal linkage: it is private to that source file. [...] You can often make an object thread-safe by placing locking mechanisms in its access functions without affecting outside callers.

(Program 2.8) use fgets instead of gets to prevent a buffer overrun on input.

[ISO C] extern char **environ;

Be careful about calling getenv more than once without copying the first return string into a buffer. Some implementations of getenv use a static buffer for the return strings and overwrite the buffer on each call.

[ISO C] int atexit(void (*func)(void));

The C exit function calls user-defined exit handlers that were registered by atexit in the reverse order of registration. After calling the user-defined handlers, exit flushes any open streams that have unwritten buffered data and then closes all open streams. Finally, exit removes all temporary files that were created by tmpfile() and then terminates control. Using the return statement from main has the same effect as calling exit with the corresponding status. Reaching the end of main has the same effect as calling exit(0).

An abnormal termination may produce a core dump, and user-installed exit handlers are not called.

《离散数学》6000字缩写笔记

Tuesday, April 14th, 2009

Discrete Mathematics (Fourth Edition)

离散数学(第四版)

6000字缩写笔记

1PERT图与关键路径

2、后续子集算法:找0/1串最右边的0,如果有,把它置为1,后面的都置为0,否则算法终止。

3Augusta Ada Byron 1815-1852

4union intersection disjoint difference complement

5、集合S上的关系R可以具备下列特殊性质中的任何一个:

1)如果对于S中的任意元素xx R x为真,则称R是自反的(reflexive

2)如果只要x R y为真,y R x就为真,则称R是对称的(symmetric

3)如果只要x R yy R z都为真,x R z都为真,则称R是传递的(transitive

6、自反、对称和传递的关系称为等价关系(equivalence relation

7、如果RS上的等价关系,x∈S,则S中与x相关联的元素的集合称为包含x的等价类(equivalence class),记为[x],所以[x] = {y∈S : y R x}

8n = q m + rq为商(quotient),r为余数(remainder

9、模m的同余关系 congruence modulo m

10Zm中:[x] + [y] = [x + y][x] [y] = [x y][x]y = [xy]

11、部分序,极大、极小元

12、哈斯图(Hasse Diagram),由下而上观察,图中所有的线段都看作是指向上方的

13、数学归纳法常用来验证算法

14、循环不变式:loop invariant

15、《算术》(Arithmetica

16、两图同构,对应顶的度数首先相等

17Leonhard Euler

18、多重图G中,包含G中所有的边恰好一次,并且第一个顶点和最后一个顶点不相同的通路称为欧拉通路(Euler path),起点和终点相同的称为欧拉回路(Euler circuit

19、求欧拉回路算法(O(n2)):算法运行在各顶度数均为偶数的连通多重图G

1)令EG的边集,选一个顶U,令通路C仅由U组成

2while E非空(扩充通路)

VC中的顶,它与E中某边关联,令通路P恰好包含V

W = V(扩充P使它成为一条从VV的通路)

while E中有邻接W的边e

E中删去e,用邻接e的另一顶替换W

将边e和顶W添加到通路P

endwhile

扩充C:用通路P替换C中出现的任意的某一个V

endwhile

3)通路C是一条欧拉回路

20Hamilton path是包含每个顶仅一次的通路,Hamilton cycle是包含每个顶仅一次的回路

21、定理(Ore性质):nn>2)阶图G若对任意不相邻顶UVdeg(U) + deg(V) ≥ nGH-回路

22、格雷码(Gray code)应用:确定圆盘旋转的位置(减少错误)

23BFS算法至多是n2阶的

24、定理(通路的数目):图G各顶标为V1V2,…,Vn,邻接矩阵为A,从ViVj的长度为m的通路的数目是Am(i, j)元素

25、定理24易用数学归纳法证明

26、着色一个图(color a graph)是指给每个顶点指定一种颜色,使得相邻的顶点有不同的颜色

27、当可用n种颜色,但不能用更少的颜色给一个图着色时,称该图具有色数(chromatic numbern

28Kn的色数是n

29、定理(下界):图G可用两种颜色着色当且仅当G不包含长度为奇数的回路

30、定理(上界):图G的色数不会超过G中顶点的最大度数加1

31、【练习】证明:“色数是3”是图的同构不变量

32、每条U-V有向通路都包含一条U-V简单有向通路

33、有向欧拉回路的应用(无线电通信中的旋转磁鼓的位置):序列01110100

34、有向H-回路(通路)与循环体育比赛

35、竞赛有向图(tournament directed graph);采用完全图Kn并给每边一个方向

36、寻找一个所有队的排名与为竞赛图寻找一条有向H-通路是等同的

37、欧拉公式(1752):f – e + v = 2

381847年,Gustav Kirchhoff在他的关于电网的工作中首次用到树。后来,Arthur Cayley在化学研究中使用了树

39、设UV是树中的顶点,那么从UV恰好有一条简单通路

40、在一棵顶点数超过一的树T中,至少有两个度为1的顶点

41Prufer算法:

本算法为一棵具有n个有标记的顶点的树构造一个数的序列a1a2,…,an-2,其中n≥3,且顶点上的标记分别为12,…,n

步骤1(初始化)

  1. T是给定的树

  2. k = k + 1

步骤2(选择ak

while T的顶点多于两个

步骤2.1(寻找一个度为1的顶点)

T中找出顶点X,其度为1且具有最小的标记

步骤2.2(构造一棵新的树)

  1. 找出X上的边e,令W表示e的另一个顶点

  2. akW上的标记

  3. T中删去边e和顶点X,形成一棵新的树

步骤2.3(改变Tk

  1. T = Tʹ

  2. k = k + 1

endwhile

42、可以如下从n-2个数的序列L构造出一棵树,序列L中的数来自N = {1, 2, …, n}(假定树中顶点标记为12,…,n)。

N中选出不在L中的最小的数k,在这个数和序列L中的第一个数之间构造一条边。

然后从L中删去第一个数,从N中山区k,并重复这个过程。

L中的数被用完时,构造一条边连接N中剩下的两个数。

43Prufer算法在有n个标记为12,…,n的顶点的树和产生的序列之间建立了一个一一对应。

44、(续43)具有顶点12,…,nn>1)的不同的树有nn-2

45、一个图可能会有多棵生成树

46BFS构造出的生成树称为最短通路树(shortest path tree

47Prim算法用来构造一棵最小生成树(minimum spanning tree):每次选择离已得到的树最近的顶点加入树中并相应地连一条边

48Kruskal算法每次选择一条权最小的边,如果不构成回路则加入树中,最终也得到一棵最小生成树

49PrimKruskal同样都可以求解最大生成树(maximum spanning tree),只需把“最小”改为“最大”即可

50DFS构造出的树T称为深度优先搜索树(depth-first search tree)。DFS的阶至多是n2的。

51、(续50T中的边称为树边(tree edge),其他边称为后向边(back edge)。对顶点所作的标记称为深度优先搜索编码。

52、根树(rooted tree)是一个有向图T,它满足两个条件:

一是当忽略T中的边的方向时,所得到的无向图是一棵树;

二是存在一个惟一的顶点RR的入度为零,其他任何顶点的入度都为一。顶点R称为根树的根(root)。

53、画根树时将遵循如下惯例:根画在顶端,并省略有向边上的箭头,把边的方向理解为都是向下的。

54Catalan数:有n个顶点的二叉树的数目是(2n)! / [n! (n+1)!]

55Fibonacci TreeT1T2都只有一个顶点;对于n≥3Tn是一棵树,其根的左子树是Tn-1,右子树是Tn-2

56、【练习】(续55)给出Tn的顶点数的公式并证明

57David A. Huffman最优二叉树(optimal binary tree)算法:每次选标记最小的两个根合并成一棵新的树,新树根的标记为这两个根标记之和

58、(续57)可利用此算法找到合并多个有序列表的最优模式,顶点的权是对应的列表的项数

59、一个列表的二叉搜索树是一棵这样的二叉树:二叉树的每个顶点都被列表的一个元素标记,使得:

1)没有两个顶点有相同的标记

2)如果顶点U属于顶点V的左子树,那么U≤V

3)如果顶点W属于顶点V的右子树,那么V≥W

60S1S2,…,Sn的一个相异代表系(system of distinct representatives)是指这样的一个序列:x1x2,…,xn,其中,xi∈Sii = 1, 2, …, n),并且各个元素xi都互不相同

61、霍尔定理(Phillip Hall):有限集合序列S1S2,…,Sn有相异代表系当且仅当对{1, 2, …, n}的任意子集ISii∈I)的并集所包含的元素至少与集合I所包含的元素个数相同

6261中所述条件称为霍尔条件(Hall’s condition

63、【练习】对于r≤nr×n的拉丁矩形(Latin rectangle)是这样的r×n的矩阵:这个矩阵的元素是数12,…,n中的数,而且任何一行或一列中都不会有一个数出现多次。

一个n×n的拉丁矩形称为一个拉丁方(Latin square)。证明:如果r<n,那么一定可以附加n-r行到一个r×n的拉丁矩形,以形成一个拉丁方。(提示:应用霍尔定理)

64、偶图(二分图):bipartite

65、图的一个匹配(matching)是这样的一个边的集合M,图中没有一个顶点与M中的多条边关联。

66、偶图的0-1矩阵;矩阵的一排(line)是指矩阵的一行或一列。

67、设A是一个矩阵,如果其中没有两个元素在同一排上,则称A的元素的集合是独立的(independent)。

68A1的独立集是1的最大独立集(maximum independent set),如果A中没有包含更多11的独立集。

69、在偶图中寻找一个最大匹配与在一个0-1矩阵中寻找一个1的最大独立集是等价的。

70、图的一个覆盖(coveringC是指一个顶点的集合,它使每条边至少与C中的一个顶点关联。

71、称C是一个最小覆盖(minimum covering),如果没有包含顶点更少的该图的覆盖。

72、定理:设一个图有一个匹配M和一个覆盖C,那么|M|≤|C|。此外,如果|M|=|C|,那么M是一个最大匹配,C是一个最小覆盖。

73Dénes König1931):在0-1矩阵中,1的最大独立集和最小覆盖包含相同个数的元素。等价地,在偶图中,最大匹配和最小覆盖包含相同个数的元素。

74、(书中关于匹配算法和网络流的内容讲述不清错误连篇所以没有作笔记,以后会单独写相关内容,敬请关注)

75Pascal’s triangle;西方称为帕斯卡三角形,国内原称杨辉三角形,现多称为贾宪三角形。

76C(n, r) = C(n-1, r-1) + C(n-1, r)

77r C(n, r) = n C(n-1, r-1)

78C(k, 0) + C(k+1, 1) + … + C(k+r, r) = C(k+r+1, r)

79、曲棍球棒公式:C(r, r) + C(r+1, r) + … + C(n, r) = C(n+1, r+1)

80p为素数,r为整数,1≤r≤p-1,则C(p, r)可以被p整除

81C(n, m)×C(m, k) = C(n, k)×C(n-k, m-k) (k≤m≤n)

82、有r个对象要选择,每个对象有n种选择方式:

条件

排列(有序列表)数

组合(无序列表)数

不允许重复

P(n, r)

C(n, r)

允许重复

nr

C(n+r-1, r)

83、容斥原理:对任意有限集合A1A2,…,Ar,对1sr,定义ns如下:

A1A2,…,Ar中不重复地选择s个集合并计算其交集的大小,对所有可能的选择,定义ns为这些值的和,那么

|A1∪A2∪…∪Ar| = n1 – n2 + n3 – n4 + … + (-1)r-1 nr

84、第二类Stirling数:把n个不同的球放到m个相同的盒中,没有空盒的放法总数记为S2(n, m)

85S2(n+1, m) = S2(n, m-1) + m×S2(n, m)

86、对于大正整数nStirling近似公式:n!(n/е)n (2πn)1/2

87、错排数递推式:Dn = (n-1) (Dn-2 + Dn-1)n≥3D1 = 0D2 = 1

88Eugene Charles Catalan (1814 – 1894)

89、在表达式x1x2…xn+1中插入n对圆括号,以把各因子组合成n对数的乘积,插入这n对括号的方法数是Cn,称作Catalan数。

90、(续89)另一种表述基于栈的排列生成。

91、离散动态系统(discrete dynamical systems)是微分方程的离散替代物,微分方程用于研究连续时间框架下的变化。

92、一阶常系数线性差分方程(first-order linear difference equation with constant coefficients):

初始值为S0的一阶常系数线性差分方程Sn = a Sn-1 + b的通项满足:

Sn = an (S0 + c) – c (当a ≠ 1

Sn = S0 + n b (当a = 1

其中

c = b / (a – 1)

93、考虑二阶常系数线性齐次差分方程(second-order homogeneous linear difference equation with constant coefficients):

Sn = a Sn-1 + b Sn-2 (n≥2)

初值为S0S1。设r1r2表示方程x2 = a x + b的根,那么,

1)如果r1 ≠ r2,则存在常数c1c2,使Sn = c1 r1n + c2 r2nn = 0, 1, 2, …

2)如果r1 = r2 = r,则存在常数c1c2,使Sn = (c1 + n c2) rnn = 0, 1, 2, …

方程x2 = a x + b称为递推关系Sn = a Sn-1 + b Sn-2的辅助方程(auxiliary equation)。

94、从信息论的观点说明比较排序算法的效率(一个关于下界的说明):

在任何比较排序算法中都要比较各对元素,有些比较不会提供新的信息(如已知a1≤a2a2≤a3那么比较a1a3是没有意义的)。

但不管怎样进行k次比较,可能得到的信息至多有2k种不同的模式。

结论是:通过k次比较,最多能区分2k种不同的顺序。

如果要对列表排序,就必须获得足够的信息,以从其元素n!种可能的不同排列序列中辨别出该列表来。

因此,要对该列表排序,至少要做k次比较,这里,2k≥n!。可以证明n!≥nn/2,于是,

2k≥nn/2

log2(2k)≥log2(nn/2)

k≥(n/2) log2n

由此,任何比较排序算法的复杂性都至少是c n log2n,其中,c为常数。

95、一般地考虑一个无限数列a0a1a2,…

其中,对某个整数n,有an+1 = an+2 = … = 0

我们称多项式a0 + a1 x + a2 x2 + a3 x3 + … + an xn是该序列的生成函数(generating function

96、形如a0 + a1 x + a2 x2 + a3 x3 + …的表达式称为形式幂级数(formal power series

其中,允许无限多个系数ar不为零。

97、假设G = a0 + a1 x + a2 x2 + a3 x3 + …,这里a0 ≠ 0。则存在惟一的生成函数H,使得G H = 1

98、卢卡斯序列(Lucas sequence):Ln = pn = 1)或Ln = qn = 2Ln = Ln-1 + Ln-2n ≥ 3)。

99、(关于逻辑电路和状态机的内容讲得很烂所以略过,以后有机会给大家写一篇好的~

100Collatz序列:根据正整数n是偶数还是奇数,分别用n/23n+1替换它,并重复这个过程。

人们猜测但尚未证明:无论从哪个整数n开始,最终都将到达1

(本文完;未经许可,不得转载)