zoukankan      html  css  js  c++  java
  • 链表

    一、概述

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

                在此,我们用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 
    info:%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
    ", 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 }

    七、获取元素数量

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

  • 相关阅读:
    HDU-1702-ACboy needs your help again!(Stack)
    HDU1276-士兵队列训练问题 (Queue)
    HDU1285-确定比赛名次(拓扑+优先队列)
    The Preliminary Contest for ICPC Asia Nanjing 2019
    拓扑排序板子 hihocoder-1174
    BZOJ1066 [SCOI2007]蜥蜴
    BZOJ3888 [Usaco2015 Jan]Stampede
    BZOJ1718 [Usaco2006 Jan] Redundant Paths 分离的路径
    BZOJ1112 [POI2008]砖块Klo
    BZOJ1031 [JSOI2007]字符加密Cipher
  • 原文地址:https://www.cnblogs.com/yshuihan/p/4353020.html
Copyright © 2011-2022 走看看