链表

一、概述

            什么是链表?链表是一种线性数据结构,即除末尾元素外,每个元素都仅有一个后继元素。对于这种一对一的逻辑关系,数组是通过物理内存地址连续来保持,而链表则通过指针域来保存后继元素的内存地址来保持,因此链表能将散落在内存中的各个元素一一串联起来.

            在此,我们用C语言实现一个单链表,其拥有遍历、插入、删除、构造、析构这5种操作.写在LinkList.h,BaseType.h,Iterate.h,LinkList.c,Basic.c这五个文件中,这些文件下面会一一阐述它们的作用.

二、公共定义

         定义一些后面会用的公共数据类型与宏,这些东西写在BaseType.h与Basic.c中.

BaseType.h

 1 #ifndef BASETYPE_H
 2 #define DEBUG 
 3 #define x86
 4 
 5 /***************基本类型*************/
 6 #define BASETYPE_H
 7 #define True 1
 8 #define False 0
 9 typedef char int8;
10 typedef unsigned char uint8;
11 typedef short int16;
12 typedef unsigned short uint16;
13 typedef long int32;
14 typedef unsigned long uint32;
15 typedef long long int64;
16 typedef unsigned long long uint64;
17 /**********断言**************/
18 #ifdef DEBUG
19 void _assert(char *info, char *file, uint32 line);
20 #define e_assert(f,s) \
21 if (f) NULL; \
22 else \
23     _assert(s,__FILE__,__LINE__);
24 #else
25 #define e_assert(f,s) NULL
26 #endif
27 /***********指针大小*****************/
28 #ifdef x86
29 typedef int32 intptr;
30 #endif
31 #ifdef x64
32 typedef int64 intptr;
33 #endif
34 /*****************常用宏**********************/
35 /*********************************
36 *@summary 交换ab的值
37 *@param a a元素的地址
38 *@param b b元素的地址
39 *@remark 例如:int a=100,b=101;swap(&a,&b);
40 **********************************/
41 #define swap(a,b) *a=*a^*b;*b=*a^*b;*a=*a^*b
42 #endif

Basic.c

#include "BaseType.h"
#include <stdio.h>
#include <stdlib.h>
void _assert(char *info,char *file,uint32 line)
{
    fflush(stdout);
    fprintf(stderr, "exception: file:%s line:%d \ninfo:%s", file, line, info);
    fflush(stderr);
    abort();
}

      主要为基本数值类型起了别名以便于使用,定义了断言宏.

三、接口设计

        链表实现后,如何给用户一组直观简洁的接口?我的定义如下:

LinkList.h

/********************************************************
*@summary 单链表
*@author 易水寒
*@date 2015/3/18
*
*********************************************************/

#ifndef LINKLIST_H
#define LINKLIST_H
#include "BaseType.h"
#include "Iterate.h"

 struct TageLinkList
{
    iterate_begin iterate_begin;
    iterate_end iterate_end;
    iterate_destory iterate_destory;
    iterate_before iterate_before;
    iterate_equal iterate_equal;
    iterate_read iterate_read;
    iterate_write iterate_write;
};

 typedef struct TageLinkList* LinkList;

/*****************************************************
*@summary 销毁子项的析构函数
*@param item 子项指针
******************************************************/
typedef void(*link_destory_item)(void *item);

/*****************************************************
*@summary 创建一个单链表
*@return >0为链表句柄,否则为失败
******************************************************/
LinkList link_new(link_destory_item freeItem);
/*****************************************************
*@summary 销毁链表
*@param handle 链表句柄
*@remark 将会释放所有元素内存
******************************************************/
void link_destory(LinkList handle);

/*****************************************************
*@summary 在尾部加入一个节点
*@param handle 链表句柄
*@param item 元素指针
*@remark true or false
******************************************************/
int32 link_push(void *item, LinkList handle);

/*****************************************************
*@summary 删除尾部节点
*@param handle 链表句柄
*@return 元素指针
******************************************************/
void link_pop(LinkList handle);

/*****************************************************
*@summary 获取链表元素数量
*@param handle 链表句柄
*@return 元素数量
******************************************************/
int32 link_count(LinkList handle);
/*****************************************************
*@summary 将一个元素插入迭代器所指位置之后
*@param handle 链表句柄
*@param iter 迭代器
*@param item 要插入的元素
*@return true or false
******************************************************/
int32 link_insert_after(void *item, Iterate iter, LinkList handle);

/*****************************************************

*@summary 删除迭代器所指位置的后继元素
*@param handle 链表句柄
*@param iter 迭代器
******************************************************/
void link_remove_after(Iterate iter, LinkList handle);



#endif

方法名加link前缀,表示它们同属一个对象,有遍历、插入、删除、构造、析构。我们希望这组函数能根据不同的链表句柄操作不同的链表,而不是一次只能操作一个链表,如何达到这个效果?其不同点只是内部的状态信息不同,我们只需要将每个链表的内部状态信息用不同的内存分割开来,这部分内存由构造函数来分配,并把块区域的入口地址给用户,由用户来保存,然后用户在调用函数时将其传入。这其实就是面向对象的思维了,将操作与属性结合在一起,只是这种结合在C里没有显示表达出来,而是由程序员隐性关联。定义的数据结构如下:

LinkList.c

typedef struct TageNode
{
    void *data;
    struct TageNode *next;
} Node;

typedef struct _TageLinkList
{
    struct TageLinkList func;
    link_destory_item free_item;
    /******头节点数据区存储连接元素数量***************/
    Node *head;
    Node *end;
} _LinkList;

Node结构描述了节点的信息,data用来存储元素数据,next用来存储后继节点的地址._LinkList描述了整个链表对象的属性,func是迭代器操作,用来实现遍历的,这部分会在下面讲遍历时专门阐述,这里其实可以说_LinkList继承了TageLinkList;free_item是由用户传入的释放元素数据的函数指针;head是头指针,end是尾指针,作用会在将插入的时候阐述.这两个结构用户不需要知道,因此放在实现文件里.

 

四、构造与析构

          构造由构造函数link_new来完成,其作用是分配一块内存用来存储链表的内部状态信息,并且初始化,返回这块内存的入口地址,称为句柄,用来表示一个链表对象.构造函数如下:

LinkList link_new(link_destory_item freeItem)
{
    _LinkList *link;
    link = (_LinkList*)calloc(1, sizeof(_LinkList));
    if (link > 0)
    {
        link->free_item = freeItem;
        /***********注册迭代器操作************/
        link->func.iterate_begin = ll_iterate_begin;
        link->func.iterate_destory = ll_iterate_destory;
        link->func.iterate_end = ll_iterate_end;
        link->func.iterate_equal = ll_iterate_equal;
        link->func.iterate_read = ll_iterate_read;
        link->func.iterate_write = ll_iterate_write;
        link->func.iterate_before = ll_iterate_before;
        link->head = calloc(1, sizeof(Node));
        if (link->head == 0) return 0;
        link->end = link->head;
    }
    return (LinkList)link;
}


freeItem是释放元素数据的析构函数,因为我们的链表支持所有数据类型,而这些数据只有用户自己知道如何释放,所以要有用户传入。函数里分配一块_LinkList大小的内存,并初始化各个字段.因为_LinkList的开始处与TageLinkList一样,所以可以将其安全转为TageLinkList,这样做是为了封装,也为了用户方便使用遍历操作.

         析构函数link_destory用来释放各个元素与链表对象用来存储状态信息的内存.实现如下:

 1 #define invalide_handle(h,l) e_assert(h>0,"集合句柄无效");l=(_LinkList*)h
 2 #define delete_item(link,item) if(link->free_item>0) link->free_item(item)
 3 
 4 void link_destory(LinkList handle)
 5 {
 6     _LinkList *link;
 7     Node *temp_node, *t2_node;
 8     invalide_handle(handle, link);
 9     /*******释放所有元素**********/
10     temp_node = link->head->next;
11     while (temp_node> 0)
12     {
13         t2_node = temp_node;
14         temp_node = temp_node->next;
15         //if (link->free_item > 0)link->free_item(t2_node->data);
16         delete_item(link, t2_node->data);
17         free(t2_node);
18     }
19     free(link);
20 }

因为经常要写验证链表句柄是否为空,将句柄转为_LinkList,所以定义invalide_handle来简化这些代码;在释放元素数据,又要经常写若元素析构函数不为空就调用它是否数据,所以定义delete_item宏来简化.

四、插入

            链表元素的插入就好比穿针引线,就是把要插入位的前一个元素的指针域指向新元素就行了,有头插法与尾插法两种。在_LinkList里有一个head(Node*)字段,它的作用是用来指向第一个元素,作为遍历的入口点,称为头指针,是不是非得用它呢,直接用第一个元素做入口不行吗?不是非得用它,用它是为了统一插入操作,而不用做第一个元素的特殊处理,这个特殊处理就是,如果是第一个元素,就把数据存到头节点的数据区里,而不用新分配一个Node,用了头指针后就统一为分配一个Node,把数据写在该Node的数据区,再把该Node加入链表,用代码来表示就是:

 1 /**************************
 2 *这里采用尾插法,假设item为void *,
 3 *表示要插入的元素数据
 4 ***************************/
 5 _LinkList *link;
 6 Node *new_node;
 7 invalide_handle(handle, link);
 8 /**********不用头指针*************/
 9 if (link->head->next == 0)
10       link->head->data = item;
11 else
12 {
13       new_node = (Node*)calloc(1, sizeof(Node));
14       new_node->data = item;
15       link->end->next = new_node;
16       link->end = new_node;
17 }
18 /**********用头指针***************/
19 new_node = (Node*)calloc(1, sizeof(Node));
20 new_node->data = item;
21  link->end->next = new_node;
22  link->end = new_node;

头插法好比让新加入的元素用根线来串链表的末尾,即它指向链表末尾,这种串法导致逆序,但同时只需要一个工作指针即可完成(可以就用头指针),该串法代码表示如下:

 

_LinkList *link;
Node *new_node;
invalide_handle(handle, link);
new_node = (Node*)calloc(1, sizeof(Node));
if (new_node == 0) return;
new_node->next=link->head->next;
linke->head->next=new_node;

尾插法好比让链表尾部元素用根线去串新加入的元素,即末尾元素指向新元素,这种串法不会逆序,但由于需要保存尾元素信息,所以需要额外一个指针,不可能用头指针,头指针要保存入口点,这也就是为什么_LinkList有head与end.

         插入的操作实现link_push与link_insert_after就行了,前者是将元素插入尾部,后者是将元素作为指定位置的元素的后继,为什么是后继而不是该位置,因为这里是单链表,链表新加元素需要知道其前驱,而单链表没法直接通过元素找到其前驱,所以就是插入到后继.代码如下:

int32 link_push(void *item, LinkList handle)
{
    _LinkList *link;
    Node *new_node;
    intptr count;
    invalide_handle(handle, link);
    new_node = (Node*)calloc(1, sizeof(Node));
    if (new_node == 0) return False;
    new_node->data = item;
    /*****将新节点加入尾部,并将新节点作为尾指针******/
    link->end->next = new_node;
    link->end = new_node;
    count = (intptr)link->head->data;
    count++;
    link->head->data = count;
    return True;
}

int32 link_insert_after(void *item, Iterate iter_handle, LinkList handle)
{
    _LinkList *link;
    Node *new_node;
    IterateInfo *iter;
    intptr count;
    invalide_handle(handle, link);
    e_assert(iter_handle > 0, "迭代器句柄无效");
    iter = (IterateInfo *)iter_handle;
    new_node = (Node*)malloc(sizeof(Node));
    if (new_node == 0) return False;
    new_node->data = item;
    new_node->next = iter->location->next;
    iter->location->next = new_node;
    count = iter->handle->head->data;
    /***********处理迭代器指向尾部时***************/
    if (iter->handle->end == iter->location)
        iter->handle->end = new_node;
    count++;
    iter->handle->head->data = count;
    return True;
}

五、遍历

          所谓遍历,就是指提供顺序读写集合(容器)每个元素的能力,因为不同容器的遍历都是一样的操作,所以做一步抽象,统一接口。这里,我们抽象出迭代器的概念,一个迭代器标识容器的某个位置,两个迭代器组成一个迭代范围,该迭代范围要在容器的范围内,首迭代器标识容器第一个元素位置,尾迭代器标识容器尾元素的后面,这段迭代范围囊括容器所有元素.迭代器的操作我们定义如下:

Iterate.h

/*****************************************************************
*@summary 迭代器,用来遍历容器,每两个迭代器组成一个迭代范围
*@author 易水寒
*@date 2014/3/18
*
******************************************************************/

#ifndef ITERATE_H
#define ITERATE_H
#include "BaseType.h"
typedef intptr Iterate;
#define end_behind -1//尾后

/*****************************************************
*@summary 获取一个首迭代器
*@param handle 容器句柄
******************************************************/
typedef Iterate (*iterate_begin)(intptr handle);

/*****************************************************
*@summary 获取一个尾后(最后一个元素的后面)迭代器
*@param handle 容器句柄
******************************************************/
typedef Iterate (*iterate_end)(intptr handle);

/*****************************************************
*@summary 销毁迭代器
*@param iterate 迭代器句柄
******************************************************/
typedef void (*iterate_destory)(Iterate iter_handle);

/*****************************************************
*@summary 迭代器前移
*@param iter_handle 迭代器句柄
******************************************************/
typedef void(*iterate_before)(Iterate iter_handle);

/*****************************************************
*@summary 迭代器后移
*@param iter_handle 迭代器句柄
******************************************************/
typedef void(*iterate_back)(Iterate iter_handle);

/*****************************************************
*@summary 读取迭代器当前位置的元素
*@param iterate 迭代器句柄
******************************************************/
typedef void *(*iterate_read)(Iterate iterate);

/*****************************************************
*@summary 将一个元素写入迭代器当前位置
*@param iterate 迭代器句柄
*@remark 会删除旧元素
******************************************************/
typedef void (*iterate_write)(void *value, Iterate iterate);

/*****************************************************
*@summary 两个迭代器的位置是否相等
*@param a/b 迭代器句柄
*@remark true or false
******************************************************/
typedef int32(*iterate_equal)(Iterate a, Iterate b);

#endif

这套接口由容器实现即可.在链表中,我们采用TageLinkList结构来暴露迭代接口的形式,主要是为了解决统一函数名,解决不同容器命名冲突的问题.链表的实现如下:

LinkList.c

/*********************************迭代器操作**************************************/
typedef struct TagIterateInfo
{
    _LinkList *handle;
    Node *location;
}IterateInfo;

 Iterate ll_iterate_begin(intptr handle)
{
    _LinkList *link;
    IterateInfo *iterate = (IterateInfo*)calloc(1, sizeof(IterateInfo));
    invalide_handle(handle,link);
    iterate->handle = link;
    iterate->location = link->head->next == 0 ? end_behind : link->head->next;
    return (Iterate)iterate;
}
static Iterate ll_iterate_end(intptr handle)
{
    _LinkList *link;
    IterateInfo *iterate = (IterateInfo*)calloc(1, sizeof(IterateInfo));
    invalide_handle(handle, link);
    iterate->handle = link;
    iterate->location = end_behind;
    return (Iterate)iterate;
}

static void ll_iterate_destory(Iterate iter_handle)
{
    e_assert(iter_handle > 0, "迭代器句柄无效");
    free(iter_handle);
}

static void *ll_iterate_read(Iterate iter_handle)
{
    IterateInfo *iter;
    e_assert(iter_handle > 0, "迭代器句柄无效");
    iter = (IterateInfo *)iter_handle;
    return iter->location==0 ? 0 : iter->location->data;
}

static void ll_iterate_write(void*value, Iterate iter_handle)
{
    IterateInfo *iter;
    e_assert(iter_handle > 0, "迭代器句柄无效");
    iter = (IterateInfo *)iter_handle;
    if (iter->location == iter->handle->head) return;
    //if (iter->handle->free_item > 0 && iter->location->data > 0)iter->handle->free_item(iter->location->data);
    delete_item(iter->handle, iter->location->next->data);
    if (iter->location == 0)return;
    iter->location->data = value;
}

static void ll_iterate_before(Iterate iter_handle)
{
    IterateInfo *iter;
    e_assert(iter_handle > 0, "迭代器句柄无效");
    iter = iter_handle;
    if (iter->location==end_behind) return;
    iter->location = iter->location->next==0? end_behind:iter->location->next;
}

static int32 ll_iterate_equal(Iterate a, Iterate b)
{
    IterateInfo *iter, *iter2;
    e_assert(a > 0 && b>0, "迭代器句柄无效");
    iter = a;
    iter2 = b;
    return iter->location == iter2->location;
}
/************************************************************************/

在我们的链表中,用户用如下代码即可遍历:

#include <stdio.h>
#include "LinkList.h"

Iterate begin, end;
ll = link_new(0);
end = ll->iterate_end(ll);
begin = ll->iterate_begin(ll);
while (!ll->iterate_equal(begin, end))
{
      printf("%d\n", ll->iterate_read(begin));
      ll->iterate_before(begin);
}

六、删除

            单链表的删除与插入一样,只能删除指定位置的后继,而不能直接删除该位置,在这里的实现中,用迭代器来表示位置.链表节点的删除,就是将节点的指针指向其后继节点的后继,再释放该节点的后继,核心代码如下:

delete_item(iter->handle, iter->location->next->data);
iter->location->next = iter->location->next->next;

同样,我们定义了两种删除方式link_pop与link_remove_after,前者删除尾部元素,后者删除指定位置节点的后继,代码如下:

 1 void link_remove_after(Iterate iter_handle, LinkList handle)
 2 {
 3     _LinkList *link;
 4     IterateInfo *iter;
 5     intptr count;
 6     invalide_handle(handle, link);
 7     e_assert(iter_handle > 0, "迭代器句柄无效");
 8     iter = (IterateInfo *)iter_handle;
 9     if ( iter->location==0||iter->location->next==0) return;
10     count = iter->handle->head->data;
11     //if (iter->handle->free_item > 0) iter->handle->free_item(iter->location->next->data);
12     delete_item(iter->handle, iter->location->next->data);
13     iter->location->next = iter->location->next->next;
14     count--;
15     iter->handle->head->data = count;
16 }
17 
18 void link_pop(LinkList handle)
19 {
20     _LinkList *link;
21     uint32 k;
22     Iterate begin;
23     invalide_handle(handle, link);
24     if (link->head == link->end) return;
25     k = (uint32)link->head->data-1;
26     begin = link->func.iterate_begin((intptr)handle);
27     while (--k> 0)
28     {
29         link->func.iterate_before(begin);
30     }
31     link_remove_after(begin, handle);
32 }

七、获取元素数量

            头指针的数据区域是空的,所以可用来保存元素数量,在有新增与删除,维护该字段即可.

 

posted @ 2015-03-20 10:35  逝水流风  阅读(148)  评论(0编辑  收藏  举报