1 前言: 前几天写过一篇单链表逆序的文章点击打开链接, 本来准备第二天就把双向链表方便的东西补全的,
奈何公(懒)务(癌)缠(发)身(作) , 拖到现在。
2 什么是链表
老规矩 , 先上一副数据结构的图 , 希望大家加深理解
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序通过链表中的指针链接次序实现。链表由一系列存储结点组成,结点可在运行时动态生成。每个结点均由两部分组成,即存储数据元素的数据域和存储相邻结点地址的指针域。当进行插入或删除操作时,链表只需修改相关结点的指针域即可,因此相比线性表顺序结构更加方便省时。
链表可分为单链表(Singly Linked List)、双向链表(Doubly Linked List)和循环链表(Circular List)等。一个单链表结点仅包含一个指向其直接后继结点的指针,因此当需要操作某个结点的直接前驱结点时,必须从单链表表头开始查找。
双向链表和循环链表均为单链表的变体。通常创建双向循环链表以综合利用两者的优点。
2.1 双向链表
双向链表的节点 , 除了有数据域之外 ,还有两个指针域, 分别指向该节点的前驱节点和后继节点,因此, 从双向链表中任意位置开始 , 都能很方便得访问其前驱和后继节点 , 节点如图1
图1 双向链表节点
如图1所示 , Data为该节点的数据域,prev指向直接前驱节点, 而next指向直接后继节点,通常, 双向链表会含有一个表头(也称作哨兵节点), 它的作用是能够迅速定位到链表头的位置, 简化我们对链表的任何操作包括但不限于增加,删除,遍历,查找等等操作, 双向有头非空链表结构如下图2
图2 双向有头非空链表
图中表头节点dhead的prev指向空, C节点的next指向空, 除表头节点和尾节点外, 链表中任意节点都满足以下关系
p = p->next->prev = p->prev->next
也就是 , 当前节点的前驱的后继是当前节点 , 当前节点的后继的前驱也是当前节点, 虽然有点拗口 , 但是请大家要记住。
3 双向循环链表实现:
3.1插入
假设指针p和q指向双向链表中的两个前后相邻结点,将某个新结点(指针为s)插到p和q之间,其过程及C语言描述如下图所示图3:
图3 双向链表插入过程
请大家注意 , 插入顺序不具备唯一性 , 但是为了保证链接的完整 , 第四步操作一定要是p->next 或 q->prev,不然会丢失掉P的后继或者是q的前驱 , 这里要一定要注意,总结下伪代码如下:
s->prev = p; s->next = q; q->prev = s; p->next = s;
3.2 删除
删除也就是插入的逆向操作 , 如上图, s节点成功插入到链表中, 我们删除也只需改变s节点的前驱和后继指针的指向 , 再释放掉s节点申请的内存就行了, 伪代码如下:
p->next = s->next; q->prev = s->prev; free(s->data); free(s);
3.3 查找
查找也就是从头节点开始, 遍历整张表, 遍历的时候逐一比对数据域的值就行了
代码如下:
llist.h
#ifndef ___LLIST_H__ #define ___LLIST_H__ #include <stdio.h> #include <string.h> #include <stdlib.h> //定义函数指针 typedef void (llist_op_t)(void *); typedef int (llist_cmp_t)(void *, void *); //获取字符串 #define GETLINES(string, buf) do{ \ printf(string); \ fgets(buf, sizeof(buf), stdin); \ if (buf[strlen(buf) - 1] == '\n') \ buf[strlen(buf) - 1] = '\0'; \ }while(0) //容错宏 #define ERRP(con, func, ret) do{ \ if (con) \ { \ printf(#func" failed!\n"); \ ret; \ } \ }while(0) //有头 循环 双向链表 //定义链表节点信息结构体 struct node_t{ void *data;//外部用户传递数据 struct node_t *next;//指向下一个节点地址 struct node_t *prev;//指向上一个节点地址 }; //抽象数据类型 typedef struct llist_t{ struct node_t head;//数据 int size;//类型 数据类型 int num;//大小 数据个数 }LLIST; //函数声明 LLIST *llist_create(int size);//初始化 int llist_append(void *data, LLIST *handle);//后插 int llist_prevend(void *data, LLIST *handle);//头查 int llist_index_insert(int index, void *data, LLIST *handle);//指定位置插入 //按关键字删除 int llist_del(void *key, llist_cmp_t *cmp, LLIST *handle); //删除所有 int llist_all_del(void *key, llist_cmp_t *cmp, LLIST *handle); //按位置删除 int llist_index_del(int index, LLIST *handle); //查找 void *llist_ind(void *key, llist_cmp_t *cmp, LLIST *handle); LLIST *llist_find(void *key, llist_cmp_t *cmp, LLIST *handle); void *llist_index_ind(int index, LLIST *handle); int llist_num(LLIST *handle); //排序 void llist_sort(llist_cmp_t *cmp, LLIST *handle); //存储 void llist_store(const char *path, LLIST *handle); //加载 LLIST *llist_load(const char *path); //flag = 0 -> next //flag = 1 -> prev void llist_travel(LLIST *handle, llist_op_t *op, int flag);//遍历 void llist_destroy(LLIST *handle);//销毁 void llist_clean(LLIST *handle);//清空 #endif /*LLIST_H*/
llist.c
#include "llist.h" LLIST *llist_create(int size) { LLIST *handle = NULL; handle = (LLIST *)malloc(sizeof(LLIST)); ERRP(NULL == handle, malloc handle, goto ERR1); handle->head.data = NULL; handle->head.next = &handle->head; handle->head.prev = &handle->head;//循环双向链表 handle->size = size;//struct cls_t new->data handle->num = 0; return handle; ERR1: return NULL; } //实现后插 int llist_append(void *data, LLIST *handle) { struct node_t *new = NULL; new = (struct node_t *)malloc(sizeof(struct node_t)); ERRP(NULL == new, malloc new, goto ERR1); new->data = (void *)malloc(handle->size); ERRP(new->data == NULL, malloc new->data, goto ERR2); memcpy(new->data, data, handle->size); //用户传递数据保存到节点信息中 //==================== //把new节点信息插入到链表handle->head中 new->next = &handle->head; new->prev = handle->head.prev; handle->head.prev->next = new; handle->head.prev = new; handle->num++; return 0; ERR2: free(new); ERR1: return -1; } //实现头插 int llist_prevend(void *data, LLIST *handle) { //1 接受用户数据data -> int char struct => data 要保存到链表节点信息中。 //2 定义一个链表节点,用来保存数据 new //3 申请空间 new //4 new->data //5 数据保存到new->data //6 新的节点连接到head(handle) struct node_t *new = NULL; ERRP(data == NULL, llist_prevend option, goto ERR1); new = (struct node_t *)malloc(sizeof(struct node_t)); ERRP(NULL == new, malloc new, goto ERR1); new->data = (void *)malloc(handle->size); ERRP(new->data == NULL, malloc new->data, goto ERR2); memcpy(new->data, data, handle->size); new->next = handle->head.next; new->prev = &handle->head; handle->head.next->prev = new; handle->head.next = new; handle->num++; return 0; ERR2: free(new); ERR1: return -1; } //指定位置插入 int llist_index_insert(int index, void *data, LLIST *handle) { struct node_t *tail = NULL; ERRP(index < 0 || index > handle->num, llist_index_insert option_1, goto ERR1); ERRP(data == NULL, llist_index_insert option_2, goto ERR1); struct node_t *new = NULL; new = (struct node_t *)malloc(sizeof(struct node_t)); ERRP(NULL == new, malloc new, goto ERR1); new->data = (void *)malloc(handle->size); ERRP(new->data == NULL, malloc new->data, goto ERR2); memcpy(new->data, data, handle->size); for (tail = &handle->head; tail->next != &handle->head && index--; tail = tail->next) ; //新的节点添加到tail后面 new->next = tail->next; new->prev = tail; tail->next->prev = new; tail->next = new; handle->num++; return 0; ERR2: free(new); ERR1: return -1; } //删除第一个满足条件 int llist_del(void *key, llist_cmp_t *cmp, LLIST *handle) { struct node_t *tail = NULL; for (tail = handle->head.next; tail != &handle->head; tail = tail->next) { if (!cmp(key, tail->data)) { tail->next->prev = tail->prev; tail->prev->next = tail->next; free(tail->data); free(tail); handle->num--; return 0; } } return -1; } //指定位置删除 int llist_index_del(int index, LLIST *handle) { struct node_t *tail = NULL; ERRP(index < 0 || index >= handle->num, llist_index_insert option_1, goto ERR1); for (tail = handle->head.next; tail != &handle->head && index--; tail = tail->next) ; tail->next->prev = tail->prev; tail->prev->next = tail->next; free(tail->data); free(tail); handle->num--; return 0; ERR1: return -1; } //删除所有 int llist_all_del(void *key, llist_cmp_t *cmp, LLIST *handle) { struct node_t *tail = NULL; struct node_t *save = NULL; for (tail = handle->head.next; tail != &handle->head; tail = save) { save = tail->next; if (!cmp(key, tail->data)) { //tail tail->next->prev = tail->prev; tail->prev->next = tail->next; free(tail->data); free(tail); handle->num--; } } return 0; } //按关键字查找 void *llist_ind(void *key, llist_cmp_t *cmp, LLIST *handle) { struct node_t *tail = NULL; for (tail = handle->head.next; tail != &handle->head; tail = tail->next) { if (!cmp(key, tail->data)) { return tail->data; } } return NULL; } //按位置查找 void *llist_index_ind(int index, LLIST *handle) { struct node_t *tail = NULL; ERRP(index < 0 || index >= handle->num, llist_index_insert option_1, goto ERR1); for (tail = handle->head.next; tail != &handle->head && index--; tail = tail->next) ; return tail->data; ERR1: return NULL; } //int llist_num(LLIST *handle) //{ // return handle->num; //} //查找所有匹配信息 LLIST *llist_find(void *key, llist_cmp_t *cmp, LLIST *handle) { LLIST *ind = NULL; struct node_t *tail = NULL; ind = llist_create(handle->size); ERRP(ind == NULL, llist_create, goto ERR1); for (tail = handle->head.next; tail != &handle->head; tail = tail->next) { if (cmp(key, tail->data) == 0) { llist_append(tail->data, ind); } } return ind; ERR1: return NULL; } //排序 void llist_sort(llist_cmp_t *cmp, LLIST *handle) { struct node_t *val1 = NULL, *val2 = NULL; void *tmp = NULL; tmp = (void *)malloc(handle->size); for (val1 = handle->head.next; val1 != &handle->head; val1 = val1->next) { for (val2 = val1->next; val2 != &handle->head; val2 = val2->next) { if (cmp(val1->data, val2->data) > 0) { #if 0 tmp = val1->data; val1->data = val2->data; val2->data = tmp; #else memmove(tmp, val1->data, handle->size); memmove(val1->data, val2->data, handle->size); memmove(val2->data, tmp, handle->size); #endif } } } free(tmp); } //存储 void llist_store(const char *path, LLIST *handle) { FILE *fp = NULL; int ret; struct node_t *tail = NULL; fp = fopen(path, "w"); ERRP(NULL == fp, fopen, goto ERR1); //保存数据类型 ret = fwrite(&handle->size, sizeof(handle->size), 1, fp); ERRP(ret != 1, fwrite size, goto ERR2); //保存数据大小 ret = fwrite(&handle->num, sizeof(handle->num), 1, fp); ERRP(ret != 1, fwrite num, goto ERR2); for (tail = handle->head.next; tail != &handle->head; tail = tail->next) { fwrite(tail->data, handle->size, 1, fp); } fclose(fp); return ; ERR2: fclose(fp); ERR1: return ; } //加载 LLIST *llist_load(const char *path) { LLIST *handle = NULL; FILE *fp = NULL; int size, num; int ret, i; void *new = NULL; fp = fopen(path, "r"); ERRP(fp == NULL, fopen, goto ERR1); ret = fread(&size, sizeof(size), 1, fp); ERRP(ret != 1, fread size, goto ERR2); ret = fread(&num, sizeof(num), 1, fp); ERRP(ret != 1, fread num, goto ERR2); handle = llist_create(size); ERRP(handle == NULL, llist_create, goto ERR2); new = (void *)malloc(size); ERRP(new == NULL, malloc new, goto ERR3); for (i = 0; i < num; i++) { ret = fread(new, size, 1, fp); ERRP(ret != 1, fread data, goto ERR4); ERRP(llist_append(new, handle) == -1, llist_aapend, goto ERR4); } fclose(fp); return handle; ERR4: free(new); ERR3: free(handle); ERR2: fclose(fp); ERR1: return NULL; } void llist_travel(LLIST *handle, llist_op_t *op, int flag) //遍历整个链表 { struct node_t *tail = NULL; if (!flag) //逆序 { //next for (tail = handle->head.next; tail != &handle->head; tail = tail->next) { op(tail->data);//打印 } } else { for (tail = handle->head.prev; tail != &handle->head; tail = tail->prev) { op(tail->data);//打印 } } } void llist_destroy(LLIST *handle) //销毁链表 { struct node_t *tail = NULL; struct node_t *save = NULL; for (tail = handle->head.next; tail != &handle->head; tail = save) { save = tail->next; free(tail->data); free(tail); } free(handle); } void llist_clean(LLIST *handle) //置空链表 { struct node_t *tail = NULL, *save = NULL; for (tail = handle->head.next; tail = &handle->head; tail = save) { save = tail->next; memset(tail->data, 0, handle->size); free(tail->data); free(tail); } memset(handle, 0, sizeof(handle)); handle->head.next = NULL; handle->head.prev = NULL; free(handle); }
main.c
#include "llist.h" #define MAX 10 struct cls_t{ char name[128]; int id; int age; char sex; }; int sort_id(void *d1, void *d2) { return ((struct cls_t *)d1)->id - ((struct cls_t *)d2)->id; } int sort_age(void *d1, void *d2) { return ((struct cls_t *)d1)->age - ((struct cls_t *)d2)->age; } int cmp_name(void *d1, void *d2) { return strcmp((char *)d1, ((struct cls_t *)d2)->name); } int cmp_age(void *d1, void *d2) { return *(int *)d1 - ((struct cls_t *)d2)->age; } int cmp_sex(void *d1, void *d2) { return *(char *)d1 - ((struct cls_t *)d2)->sex; } void ls(void *data) { struct cls_t *stu = (struct cls_t *)data; printf("name : %6s | id : %d | age : %d | sex : %c\n",stu->name, stu->id, stu->age, stu->sex); } int main(int argc, char **argv) { int i; struct cls_t cls, stu = {"tom", 1001111, 20, 'M'}; int age = 21, index, bit; char sex = 'M'; char name[128]; LLIST *handle = NULL; struct cls_t *p = NULL; if (argc >= 2) { printf("llist_load!\n"); handle = llist_load("./stu.db"); llist_travel(handle, ls, 0); } else { handle = llist_create(sizeof(struct cls_t)); ERRP(handle == NULL, llist_create, goto ERR1); for (i = 0; i < MAX; i++) { snprintf(cls.name, sizeof(cls.name), "stu_%c%c", rand() % 26 + 'A', rand() % 26 + 'a'); cls.id = rand() % 100 + 1001000; cls.age = rand() % 43 + 19; cls.sex = "MF"[rand() % 2]; /*llist_append(&cls, handle);*/ llist_prevend(&cls, handle); } llist_travel(handle, ls, 0); printf("=======after sort =========\n"); llist_sort(sort_age, handle); printf("sizeof(struct cls_t) = %ld\n", sizeof(struct cls_t)); llist_store("./stu.db", handle); //printf("please input bit : "); //scanf("%d", &bit); //p = llist_index_ind(bit, handle); //GETLINES("please input ind name : ", name); //llist_del(name, cmp_name, handle); //p = llist_find(name, cmp_name, handle); //if (p == NULL) //{ // printf("no find!\n"); //} //else //{ // ls(p); //} // //printf("===============\n"); /*llist_index_insert(10, &stu, handle);*/ llist_travel(handle, ls, 0); /* *printf("please input del age : "); *scanf("%d", &age); */ /*llist_all_del(&sex, cmp_sex, handle);*/ /*llist_travel(llist_find(&sex, cmp_sex, handle), ls, 0);*/ /*llist_travel(llist_find(&age, cmp_age, handle), ls, 0);*/ /* *printf("============\n"); *llist_travel(handle, ls, 1); */ } llist_destroy(handle); return 0; ERR1: return -1; }
Makefile
CC = gcc list : llist.o main.o $(CC) -o $@ $^ .PHONY:clean clean: @rm -fr *.o list
运行结果(仅打印age排序结果, 但是所有函数尽可能检查安全性的前提下测试OK)
刚才我测试在另外一台机器上复制代码下来编译有点小问题, llist.h的两个测试宏 注意 '\' 后不能有空格,手动删除一下就完全OK了