Data Structure And Algorithm :C language(list)

实现来自算法精解:C语言描述(本人练习数据结构与基本算法记录,供回忆知识点使用)

代码上传至github:
https://github.com/G1enY0ung/DataStructure-and-Algorithm


链表

1. 单链表(list):

单链表由各个元素之间通过一个指针链接起来,每个元素包含两部分,一个是数据域,一个是next指针域。最后一个元素的指针指向NULL
可以把单链表想象为一系列连续的元素,但是由于元素是动态分配的(malloc),所以元素通常是分散在内存空间中的,彼此之间访问都是靠地址访问的。

typedef struct ListElmt_  //元素的定义
{
    void *data;
    struct ListElmt_ *next;
} ListElmt;

typedef struct List_  //单链表的定义
{
    int size;
    int (*match)(const void *key1, const void *key2);  //match不由链表使用,而是由链表派生而来的新类型使用
    void (*destroy)(void *data);  //想象为析构函数
    ListElmt *head;
    ListElmt *tail;
} List;

实现的接口如下:

void list_init(List *list, void (*destroy)(void *data));
void list_destory(List *list);
int list_ins_next(List *list, ListElmt *element, const void *data);
int list_rem_next(List *list, ListElmt *element, void **data);
#define list_size(list) ((list)->size)
#define list_head(list) ((list)->head)
#define list_tail(list) ((list)->tail)
#define list_is_head(list, element) ((element) == (list)->head ? 1 : 0)
#define list_is_tail(list, element) ((element) == (list)->tail ? 1 : 0)
#define list_data(element) ((element)->data)
#define list_next(element) ((element)->next)

在插入时,需要注意2点:

1.插入链表头部
2.插入其他位置

头部插入时,新元素之前没有别的结点,所以要重置头结点指针,令其指向新元素。
还有无论何时插入元素位于单链表末尾时,都要更新tail使其指向新的结点。

同理,移除时也跟插入的注意点类似。

2.双向链表(dlist):

可以想象为在单链表基础上加上prev指针域,指向前一个元素结点,head->prev 和 tail->next 都为 NULL。

typedef struct DListElmt_
{
    void *data;
    struct DListElmt_ *prev;
    struct DListElmt_ *next;
} DListElmt;

typedef struct DList_
{
    int size;
    int (*match)(const void *key1, const void *key2);
    void (*destroy)(void *data);
    DListElmt *head;
    DListElmt *tail;
} DList;

实现的接口如下:

void dlist_init(DList *list, void (*destroy)(void *data));
void dlist_destroy(DList *list);
int dlist_ins_next(DList *list, DListElmt *element, const void *data);
int dlist_ins_prev(DList *list, DListElmt *element, const void *data);
int dlist_remove(DList *list, DListElmt *element, void **data);
#define dlist_size(list) ((list)->size)
#define dlist_head(list) ((list)->head)
#define dlist_tail(list) ((list)->tail)
#define dlist_is_head(element) ((element)->prev == NULL ? 1 : 0)
#define dlist_is_tail(element) ((element)->next == NULL ? 1 : 0)
#define dlist_data(element) ((element)->data)
#define dlist_next(element) ((element)->next)
#define dlist_prev(element) ((element)->prev)

插入时需要多管理一个prev指针,注意点和单链表一样。
删除操作不同的是,可以删除指定元素而不是指定元素的下一个元素,因为每个结点都有一个prev指针。

3. 循环链表

这个可以是双向也可以是单项,判断是不是循环链表只要看有没有尾部元素即可。最后一个元素的next指针指向头,头的prev指向最后一个元素。
循环链表的遍历无需担心达到尾部,会从头部重新开始,所以这也是循环链表跟前面两个链表的不同之处。

typedef struct CListElmt_
{
    void *data;
    struct CListElmt_ *next;
} CListElmt;

typedef struct CList_
{
    int size;
    int (*match)(const void *key1, const void *key2);
    void (*destroy)(void *data);
    CListElmt *head;
} CList;

实现的接口如下:

void clist_init(CList *list, void (*destroy)(void *data));
void clist_destory(CList *list);
int clist_ins_next(CList *list, CListElmt *element, const void *data);
int clist_rem_next(CList *list, CListElmt *element, void **data);
#define clist_size(list) ((list)->size)
#define clist_head(list) ((list)->head)
#define clist_tail(list) ((list)->tail)
#define clist_data(element) ((element)->data)
#define clist_next(element) ((element)->next)

需要注意:

当链表为空时插入,需要讲插入元素的next指针指向自己,从而保证后续的插入。

推荐阅读更多精彩内容