摘要
邻接表是标示图的标准方法。图论算法中通常需要找出与某个给定顶点v邻接的所有顶点。而这可以通过简单地扫描相应的邻接表来完成,所用时间与这些顶点的个数成正比。
C语言中我们无法通过名字作为数组的索引,因此我们必须提供从名字到数字的映射。完成这项工作最容易的方法是使用散列表(HashTable),在每条边被输入的时,我们通过该散列表检查它指向的定点是否已经存在,如果不存在我们生成并将该点加入到图中且set到散列表中,如果存在我们获>取其值,即图中的数组下标。
无权图最短路径的计算需要用到队列结构。我们先将起点的known和dist属性置为1和0,入列,然后出列,将该点的所有边指的距离未知(known属性为0)的点都计算dist(所在顶点的dist+1)并置known为1然后入列,如此反复。
我们先声明图的数据结构,和相关操作方法:
#define MAX_GRAPH_V_NODE_NUM 256
#define MAX_NAME_SIZE 32
typedef struct e_node_s {
int idx;
struct e_node_s *next;
} e_node_t;
typedef struct v_node_s {
char name[MAX_NAME_SIZE];
int known;
int dist;
e_node_t *first_e_node;
e_node_t *last_e_node;
} v_node_t;
typedef struct graph_s {
v_node_t *v_node_list[MAX_GRAPH_V_NODE_NUM];
int v_node_num;
int e_node_num;
} graph_t;
e_node_t *e_node_init(int idx);
v_node_t *v_node_init(char *name);
graph_t *graph_init();
int graph_add_e_node(e_node_t *e, int idx, graph_t *g);
int graph_add_v_node(v_node_t *v, graph_t *g);
v_node_t *graph_get_v_node(int idx, graph_t *g);
int graph_set_v_node(v_node_t *v, int idx, graph_t *g);
int graph_get_shortest_path(char *vname, htable_t *htable, graph_t *g);
int graph_print(graph_t *g);
具体方法的定义实现:
e_node_t *e_node_init(int idx) {
e_node_t *e = malloc(sizeof(e_node_t));
e->idx = idx;
e->next = NULL;
return e;
}
v_node_t *v_node_init(char *name) {
v_node_t *v = malloc(sizeof(v_node_t));
snprintf(v->name, MAX_NAME_SIZE, "%s", name);
v->known = 0;
v->dist = 0;
v->first_e_node = NULL;
v->last_e_node = NULL;
return v;
}
graph_t *graph_init() {
graph_t *g = malloc(sizeof(graph_t));
g->v_node_num = 0;
g->e_node_num = 1;
return g;
}
int graph_add_v_node(v_node_t *v, graph_t *g) {
g->v_node_list[g->v_node_num] = v;
g->v_node_num ++;
return g->v_node_num - 1;
}
v_node_t *graph_get_v_node(int idx, graph_t *g) {
return g->v_node_list[idx];
}
int graph_set_v_node(v_node_t *v, int idx, graph_t *g) {
g->v_node_list[idx] = v;
return 0;
}
int graph_add_e_node(e_node_t *e, int idx, graph_t *g) {
v_node_t *v = g->v_node_list[idx];
if (v == NULL) {
perror("vex is NULL!");
return 1;
}
if (v->first_e_node == NULL) {
v->first_e_node = e;
v->last_e_node = e;
} else {
v->last_e_node->next = e;
v->last_e_node = e;
}
g->e_node_num ++;
return 0;
}
int graph_print(graph_t *g) {
int j, i = 0;
v_node_t *v;
while (NULL != (v = g->v_node_list[i])) {
e_node_t *e = v->first_e_node;
j = 0;
while (e != NULL) {
printf("顶点[%d]%s的第%d个邻接点:[%d]%s\n", i+1, v->name, j+1, e->idx+1, g->v_node_list[e->idx]->name);
e = e->next;
j ++;
}
i ++;
}
return 0;
}
int graph_get_shortest_path(char *vname, htable_t *htable, graph_t *g) {
htable_node_t *node = htable_get(vname, htable);
int idx = node->val;
queue_t *q = queue_init();
g->v_node_list[idx]->known = 1;
g->v_node_list[idx]->dist = 0;
enqueue(ele_init(idx), q);
ele_t *ele;
while (NULL != (ele = dequeue(q))) {
v_node_t *v = g->v_node_list[ele->val];
e_node_t *e = v->first_e_node;
while (e != NULL) {
if (g->v_node_list[e->idx]->known == 0) {
g->v_node_list[e->idx]->known = 1;
g->v_node_list[e->idx]->dist = v->dist + 1;
enqueue(ele_init(e->idx), q);
printf("点[%d]%s距离点[%d]%s:%d\n", e->idx, g->v_node_list[e->idx]->name, idx, vname, g->v_node_list[e->idx]->dist);
}
e = e->next;
}
}
return 0;
}
声明散列表的结构和相关操作方法:
#define HASH_TABLE_SIZE 512
typedef struct htable_node_s {
unsigned int hash;
char key[MAX_NAME_SIZE];
int val;
struct htable_node_s *next;
} htable_node_t;
typedef struct htable_s {
htable_node_t *table[HASH_TABLE_SIZE];
int count;
} htable_t;
unsigned int BKDRHash(char *str); //BKDRHash
htable_node_t *htable_node_init(unsigned int hash, char *key, int val);
htable_t *htable_init();
htable_node_t *htable_get(char *key, htable_t *htable);
int htable_set(char *key, int val, htable_t *htable);
具体方法定义实现:
// BKDR Hash Function
unsigned int BKDRHash(char *str) {
unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
htable_node_t *htable_node_init(unsigned int hash, char *key, int val) {
htable_node_t *node = malloc(sizeof(htable_node_t));
node->hash = hash;
snprintf(node->key, MAX_NAME_SIZE, "%s", key);
node->val = val;
node->next = NULL;
return node;
}
htable_t *htable_init() {
htable_t *h = malloc(sizeof(htable_t));
h->count = 0;
return h;
}
int htable_set(char *key, int val, htable_t *htable) {
unsigned int hash = BKDRHash(key);
int idx = hash % HASH_TABLE_SIZE;
htable_node_t *prev;
htable_node_t *node = htable->table[idx];
if (node == NULL) {
htable->table[idx] = htable_node_init(hash, key, val);
htable->count ++;
} else {
while (strcmp(node->key, key) != 0) {
if (node->next != NULL) {
node = node->next;
} else {// scan to the end
node->next = htable_node_init(hash, key, val);
htable->count ++;
break;
}
}
}
return 0;
}
htable_node_t *htable_get(char *key, htable_t *htable) {
unsigned int hash = BKDRHash(key);
int idx = hash % HASH_TABLE_SIZE;
htable_node_t *node = htable->table[idx];
while (node != NULL && strcmp(node->key, key) != 0) {
node = node->next;
}
return node;
}
声明队列的数据结构和相关操作方法:
typedef struct ele_s {
int val;
struct ele_s *next;
} ele_t;
typedef struct queue_s {
ele_t *head;
ele_t *tail;
int count;
} queue_t;
ele_t *ele_init(int val);
queue_t *queue_init();
ele_t *dequeue(queue_t *q);
int enqueue(ele_t *e, queue_t *q);
具体方法的定义实现:
ele_t *ele_init(int val) {
ele_t *e = malloc(sizeof(ele_t));
e->val = val;
e->next = NULL;
return e;
}
queue_t *queue_init() {
queue_t *q = malloc(sizeof(queue_t));
q->head = q->tail = NULL;
q->count = 0;
return q;
}
int enqueue(ele_t *e, queue_t *q) {
if (q->tail == NULL) {
q->head = e;
q->tail = e;
q->count = 1;
} else {
q->tail->next = e;
q->tail = e;
q->count ++;
}
return 0;
}
ele_t *dequeue(queue_t *q) {
if (q->count <= 0) {
return NULL;
}
ele_t *e = q->head;
q->head = q->head->next;
q->count --;
if (q->count == 0) {
q->tail = NULL;
}
return e;
}
编译执行:
➜ exercises git:(master) ✗ gcc graph.c
➜ exercises git:(master) ✗ ./a.out
输入图的顶点数量:
7
输入第1个顶点名字:
v1
第1个顶点[0]v1已生成
输入第1个顶点[0]v1的邻接点数量:
2
输入第1个顶点[0]v1的第1个邻接点名字:
v2
第1个顶点[0]v1的第1个邻接点v2已生成
输入第1个顶点[0]v1的第2个邻接点名字:
v4
第1个顶点[0]v1的第2个邻接点v4已生成
输入第2个顶点名字:
v2
第2个顶点v2已存在,槽位:1
输入第2个顶点[1]v2的邻接点数量:
v4
输入第2个顶点[1]v2的第1个邻接点名字:
第2个顶点[1]v2的第1个邻接点v4已生成
输入第2个顶点[1]v2的第2个邻接点名字:
v5
第2个顶点[1]v2的第2个邻接点v5已生成
输入第3个顶点名字:
v3
第3个顶点[4]v3已生成
输入第3个顶点[4]v3的邻接点数量:
2
输入第3个顶点[4]v3的第1个邻接点名字:
v1
第3个顶点[4]v3的第1个邻接点v1已生成
输入第3个顶点[4]v3的第2个邻接点名字:
v6
第3个顶点[4]v3的第2个邻接点v6已生成
输入第4个顶点名字:
v4
第4个顶点v4已存在,槽位:2
输入第4个顶点[2]v4的邻接点数量:
4
输入第4个顶点[2]v4的第1个邻接点名字:
v3
第4个顶点[2]v4的第1个邻接点v3已生成
输入第4个顶点[2]v4的第2个邻接点名字:
v5
第4个顶点[2]v4的第2个邻接点v5已生成
输入第4个顶点[2]v4的第3个邻接点名字:
v6
第4个顶点[2]v4的第3个邻接点v6已生成
输入第4个顶点[2]v4的第4个邻接点名字:
v7
第4个顶点[2]v4的第4个邻接点v7已生成
输入第5个顶点名字:
v5
第5个顶点v5已存在,槽位:3
输入第5个顶点[3]v5的邻接点数量:
1
输入第5个顶点[3]v5的第1个邻接点名字:
v7
第5个顶点[3]v5的第1个邻接点v7已生成
输入第6个顶点名字:
v6
第6个顶点v6已存在,槽位:5
输入第6个顶点[5]v6的邻接点数量:
0
输入第7个顶点名字:
v7
第7个顶点v7已存在,槽位:6
输入第7个顶点[6]v7的邻接点数量:
1
输入第7个顶点[6]v7的第1个邻接点名字:
v6
第7个顶点[6]v7的第1个邻接点v6已生成
图生成完毕!
开始打印图——
顶点[1]v1的第1个邻接点:[2]v2
顶点[1]v1的第2个邻接点:[3]v4
顶点[2]v2的第1个邻接点:[3]v4
顶点[2]v2的第2个邻接点:[4]v5
顶点[3]v4的第1个邻接点:[5]v3
顶点[3]v4的第2个邻接点:[4]v5
顶点[3]v4的第3个邻接点:[6]v6
顶点[3]v4的第4个邻接点:[7]v7
顶点[4]v5的第1个邻接点:[7]v7
顶点[5]v3的第1个邻接点:[1]v1
顶点[5]v3的第2个邻接点:[6]v6
顶点[7]v7的第1个邻接点:[6]v6
准备计算最短路径,请输入起始点:
v3
开始计算最短路径——
点[0]v1距离点[4]v3:1
点[5]v6距离点[4]v3:1
点[1]v2距离点[4]v3:2
点[2]v4距离点[4]v3:2
点[3]v5距离点[4]v3:3
点[6]v7距离点[4]v3:3
参考文献
(美)Mark Allen Weiss 著 冯舜玺 译. 数据结构与算法分析. 机械工业出版社. 2004 各种字符串Hash函数比较
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。