1. 算法和数据结构
一种说法是算法是程序的灵魂;一种说法是算法+数据结构=程序。我更加同意第一种说法。数据结构是为算法服务的。程序的特性、需求决定了需要采用何种算法,也就决定了要采用何种数据结构。编译器要求速度尽可能快,要能够处理成百上千万行代码,因此不可能采用数组或者二叉搜索树来存储数据,而只能采用hash表。如果要不停的插入、删除数据,则只能选择链表而不能采用数组。
基本的算法和数据结构就那么几种,但不同的实现产生了许多变体。这些变体各有特性,体现了和具体业务的相关性,但本质上,它还没有改变。
基本的算法有:检索、排序,具体实现的数据结构有:数组、链表、树,哈希表。检索方法一种是线性检索,适用于任何数据结构,有的是时间,还有什么事情做不了呢?一种是二分检索,这种只适合在已排序好的数组上,在二叉搜索树上检索其实也是一种二分检索,不过如果树过于倾斜,效果不太好;还有一种就是hash了。Hash是最快的,几乎能够达到O(1)的复杂度。但是Hash值得计算也是不容易的。这还是一个很慢的过程。排序算法更多的使用在数组上,链表上的排序算法好象是没有的。堆积排序或者构造二叉排序树来排序也可以。
对于不确定类型的数据怎么来排序或者检索呢?我们不可能为每种数据类型都写一个方法来排序。单根的java充分利用了动态绑定的特性,只要类实现comparable接口就可以使用通用的排序算法,C++充分利用了模版,但是需要重载operator < 和operator =操作符。至于C语言,只能采用函数指针这个古老而又实用的方法。
hash表中,我们最常用的就是用来Hash字符串。编译器充分利用了Hash表,虽然不同编译器的Hash算法不一样,但都是Hash字符串。书中提到了一个很有效的字符串Hash算法:
unsigned int hash( char* str )
{
unsigned int h;
unsigned char* p;
h = 0;
for( p = (unsigned char* )str; *p != ‘/0’; ++p ){
h = MULTPLIER * h + *p;
return h % nhash;
}
}
MULITPLIER取31和37是个不错的选择,nhash也最好取一个质数,这样可以有效避免在数组大小、散列的乘数和可能的数据值之间存在公因子。
下面是一个快速排序算法,这真是个优雅的算法。
void quick_sort( int v[], int n ){
int last, int i,
if( n <= 1 )
return;
swap( v, 0, rand()%n );
last = 0;
for( i = 1; i < n; ++i ){
if( v[i] < v[0] )
swap( v, ++last, i );
swap( v, 0, last );
quick_sort( v, last );
quick_sort( v + last + 1, n – last -1 );
}