您的位置:首页 > 理论基础 > 数据结构算法

【程序设计实践】第2章 算法和数据结构

2013-08-07 08:31 190 查看
第2章 算法和数据结构
即使是很复杂的程序,也是由简单的数组、表、散列表等简单东西堆砌起来的。

检索

顺序检索,二分检索

排序

快速排序

C函数库中的qsort,调用时必须提供一个比较函数。ANSI C的二分检索函数:bsearch,它也要求一个指向比较函数的指针。标准C++库中有个名为sort的类属算法。
某一种快速排序:
/* quicksort: sort v[0]..v[n-1] into increasing order */
void quicksort(int v[], int n)
{
int i, last;
if (n <= 1) /* nothing to do */
       return;
swap(v, 0, rand() % n); /* move pivot elem to v[0] */
last = 0;
for (i = 1; i < n; i++) /* partition */
       if (v[i] < v[0])
              swap(v, ++last, i);
swap(v, 0, last); /* restore pivot */
quicksort(v, last); /* recursively sort */
quicksort(v+last+1, n-last-1); /* each part */
}
/* swap: interchange v[i] and v[j] */
void swap(int v[], int i, int j)
{
       int temp;
       temp = v[i];
       v[i] = v[j];
       v[j] = temp;
}
排序算法花费的时间正比于nlogn。

可增长数组

定义一个可增长数组,新元素被加到有关数组的最后。在必要时将自动增大以提供新的空间。删除名字需要一点诀窍,如果元素顺序不重要,最简单的方法就是把位于数组的最后元素复制到这里。
typedef struct Nameval Nameval;
struct Nameval {
       char        *name;
       int          value;
};
struct Nvtab {
       int          nval;              /* current number of values */
       int          max;              /* allocated number of values */
       Nameval *nameval;      /* array of name-value pairs */
};
enum { NVINIT = 1, NVGROW = 2 };
/* addname: add new name and value to nvtab */
int addname(Nameval newname)
{
       Nameval *nvp;
       if (nvtab.nameval == NULL) {    /* first time */
              nvtab.nameval = (Nameval *) malloc(NVINIT * sizeof(Nameval));
              if (nvtab.nameval == NULL)
                     return -1;
              nvtab.max = NVINIT;
              nvtab.nval = 0;
       } else if (nvtab.nval >= nvtab.max) {       /* grow */
              nvp = (Nameval *) realloc(nvtab.nameval,
                            (NVGROW*nvtab.max) * sizeof(Nameval));
              if (nvp == NULL)
                     return -1;
              nvtab.max *= NVGROW;
              nvtab.nameval = nvp;
       }
       nvtab.nameval[nvtab.nval] = newname;
       return nvtab.nval++;
}
/* delname: remove first matching nameval from nvtab */
int delname(char *name)
{
       int i;
       for (i = 0; i < nvtab.nval; i++)
              if (strcmp(nvtab.nameval[i].name, name) == 0) {
                     memmove(nvtab.nameval+i, nvtab.nameval+i+1,
                                   (nvtab.nval-(i+1)) * sizeof(Nameval));
                     nvtab.nval--;
                     return 1;
              }
}
在ANSI C的标准库里定义了两个相关的函数:memcpy的速度快,但是如果源位置和目标位置重叠,它有可能覆盖掉存储区中的某些部分;memmove函数的速度可能慢些,但总能保证复制的正确完成。

单链表,构造表的最简单的办法就是把每个元素加在最前面。
我们无法在编译时初始化一个非空的表,这点也与数组不同。表应该完全是动态构造起来的。
二分检索完全不能适用于表。
/* apply: execute fn for each element of listp */
void apply(Nameval *listp, void (*fn)(Nameval*, void*), void *arg)
{
       for ( ; listp != NULL; listp = listp->next)
              (*fn)(listp, arg);    /* call the function */
}
要使用apply,例如打印一个表的元素,我们可以写一个简单的函数,其参数包括一个格式描述串:
/* printnv: print name and value using format in arg */
void printnv(Nameval *p, void *arg)
{
       char *fmt;
       fmt = (char *) arg;
       printf(fmt, p->name, p->value);
}
它的调用形式是:
apply(nvlist, printnv, “%s: %x"n”);
要销毁一个表就必须特别小心:
/* freeall: free all elements of listp */
void freeall(Nameval *listp)
{
       Nameval *next;
       for ( ; listp != NULL; listp = next) {
              next = listp->next;
              /* assumes name is freed elsewhere */
              free(listp);
       }
}

二叉树,树的构造,树的遍历。

散列表

常见做法是为每个散列值(或称桶)关联一个项的链表,即数组的每个元素是个链表,链接起具有该散列值的所有数据项。散列函数hash应该计算出什么东西。这个函数必须是确定性的,应该能算得很快,应该把数据均匀地散布到数组里。对于字符串,最常见的散列算法之一就是:逐个把字节加到已经构造的部分散列值的一个倍数上。乘法能把新字节在已有的值中散开来。这样,最后结果将是所有输入字节的一种彻底混合。根据经验,在对ASCII串的散列函数中,选择31和37作为乘数是很好的。

小结

选择算法有几个步骤。首先,应参考所有可能的算法和数据结构,考虑程序将要处理的数据大概有多少。如果被处理数据的量不大,那么就选择最简单的技术。如果数据可能增长,请删掉那些不能对付大数据集合的东西。然后,如果有库或者语言本身的特征可以使用,就应该使用。如果没有,那么就写或者借用一个短的、简单的和容易理解的实现。如果实际测试说明它太慢,那么就需要改用某种更高级的技术。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  程序设计