数据结构学习之循环链表结构

注:本文的主要目的是为了记录自己的学习过程,也方便与大家做交流。转载请注明来自:

http://blog.csdn.net/ab198604


        循环链表在单向链表及双向链表的基础之上作了一进步的概念延伸,循环链表让链表操作变的更加灵活。这是因为,单向链表与双向链表都具有链表的头结点与尾结点,如果我们进一步思考:

        1单向链表的尾结点的next链域指向其头结点       -->   形成了单向循环链表

        2 双向链表的尾结点的next链域指向其头结点,头结点的prev链域指向其尾结点   --> 形成了双向循环链表

        无论其如何变化,概念如何扩展,只要抓住其"链表"的本质,就可以很容易的理解循环链表的要领。循环链表并没有明显的头结点和尾结点,因为链表上的任何一个元素均可以看成是头、尾结点,这主要取决于实际的应用。因为区分一个链表是否是循环链表的主要依据在于判别它是否含有完整的"环",即它不存在像单链表及双向链表的真正意义上的尾结点了。

        本文主要讲阐的是单向循环链表,形象示意图如下所示:


一、单向循环链表的结构及接口定义

/* 
 * clist.h 
 * Author: zhm
 * date: 2013-01-04
 */

#ifndef CLIST_H_
#define CLIST_H_

#include <stdlib.h>

/* define a structure for the element */
typedef struct CListElmt_
{
    void *data;
    struct CListElmt_ *next;
}CListElmt;
        上述结构体定义了单向循环链表的每个结点元素,一个data域及指向其后续结点的next指针域。

typedef struct CList_
{
    int size;
    int (*match)(const void *key1, const void *key2);
    void (*destroy)(void *data);
    CListElmt *head;
}CList;
        上面结构体定义了一个单向循环链表结构,用于表示一个完整的单向循环链表。

/* public interface */
void clist_init(CList *list, void (*destroy)(void *data));
void clist_destroy(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_data(element) ((element)->data)
#define clist_next(element) ((element)->next)

#endif
        上面代码定义了单向循环链表的一些基本的操作,主要有如下操作:

        1 单向循环链表的初始化

        2 单向循环链表的销毁

        3 插入操作

        4 删除操作

        5 通用的宏定义操作,用以快速获取链表中的元素个数,头结点,data域及next域。


        二、单向循环链表的接口细节实现

        1 单向循环链表的初始化

        这步比较简单,看下代码就明白了。

        

/* 
 * clist.c
 * Author: zhm
 * date: 2013-01-04
 */

#include <stdlib.h>
#include <string.h>

#include "clist.h"

/* clist_init */
void clist_init(CList *list, void (*destroy)(void *data))
{
    list->size = 0;
    list->head = NULL;
    list->destroy = destroy;
    return;
}
        主要对CList单向循环链表结构体的初始化设置。

        2 单向循环链表的销毁        

/* clist_destroy */
void clist_destroy(CList *list)
{
    void *data; 
    while( clist_size(list) > 0 )
    {
        if( clist_rem_next(list, list->head, (void **)&data) == 0 
                && list->destroy != NULL )
        {
            //destroy the memory where the data point to.
            list->destroy(data);
        }
    }
    
    memset(list, 0, sizeof(CList));
    return;
}
        代码实现的主要思想在于,迭代链表中的每个元素并将其从链表中移除,对每个元素中的data数据域调用自己的destroy函数进行内存资源的回收。

        3 插入操作

        代码如下:

/* clist_ins_next */
int clist_ins_next(CList *list, CListElmt *element, const void *data)
{
    CListElmt *new_element;

    /* allocate the memory */
    new_element = (CListElmt *)malloc(sizeof(CListElmt));
    if( new_element == NULL )
        return -1;

    /* insert the new element into the list */
    new_element->data = (void *)data;
    if( clist_size(list) == 0 )
    {
        //when the list is empty.  be care!
        new_element->next = new_element; //here, new_element is the head and the tail element;
        list->head = new_element;
    }
    else
    {
        //when the list is not empty.
        new_element->next = element->next;
        element->next = new_element;
    }
    
    /* Adjust the size */
    list->size++;
    
    return 0;
}
        上述代码中需要注意的地方如下:

        (1) 需要注意当链表为空时的情况

        (2)当插入第一个元素时,它是头结点,且它的next指针域指向其自身,形成一个闭环。


        4 删除操作

/* clist_rem_next */
int clist_rem_next(CList *list, CListElmt *element, void **data)
{
    CListElmt *old_element; //keep the element which to be removed.
    
    /* Do not remove from an empty list */
    if( clist_size(list) == 0 )
        return -1;

    /* Remove the element from the list */
    *data = (void *)element->next->data;
    if( element->next == element ) //be care the condition.
    {
        /* when the element is the last element */
        old_element = element;
        list->head = NULL;
    }
    else
    {
        /* remove the other element */
        old_element = element->next;
        element->next = element->next->next;
        if( old_element == clist_head(list) ) //if the head element will be removed.
            list->head = old_element->next;
    }
    
    free(old_element);
    list->size--;

    return 0;
}
        删除操作也很简单,整体思路为,获取要删除的元素的data数据域,调整被删除元素前后结点元素的next指针域,需要注意如下情况:

        (1) 由于是循环链表,当删除的元素为头结点时,需要维护链表的head指针域
        (2) 当被删除的元素是最后一个元素时,也需要维护head指针域


        三、单向循环链表的简单应用举例

        本例的应用代码从与《双向链表》那篇博文类似,代码很简单,主要是为了应用单向循环链表的接口及其宏定义操作,完成单向循环链表的初始化,插入元素操作,删除元素操作和销毁元素操作。在本例中,用一个结构体表示一个长方体,结构体维护了三个变量:长、宽、高,代码如下:

/* 
 * main.c 
 * author:zhm
 * date: 2013-01-04
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "clist.h"

typedef struct Cuboid_
{
    int length;
    int width;
    int height;
}Cuboid;
        这个结构体表示一个长方体的一些属性,其主要作用仅仅为了让单向链表中元素的data域来表征一个长方体。可以参照《双向链表》那篇博文的第三部分: http://blog.csdn.net/ab198604/article/details/8275020

        在给每个元素的data域赋值之前,需要实例化一个长方体对象,代码如下:

Cuboid *cube_instance(const int length, const int width, const int height)
{
    Cuboid *cb_ptr;
    cb_ptr = (Cuboid *)malloc(sizeof(Cuboid));
    if( cb_ptr == NULL )
        return NULL;

    cb_ptr->length = length;
    cb_ptr->width = width;
    cb_ptr->height = height;

    return cb_ptr;
}
        下面是main函数内容,具体代码不再说明了,非常简单,看了就明白。

/* main */
int main(int argc, char **argv)
{
    int i;
    int ret = 0;
    CList clist;
    CListElmt *p = NULL;

    Cuboid *cb1_ptr, *cb2_ptr, *cb3_ptr, *cb4_ptr, *cb5_ptr;
    Cuboid *cb_ptr;

    //cb1_ptr ~ cb5_ptr are the data of the 5 elements.
    cb1_ptr = cube_instance(1,2,3);
    cb2_ptr = cube_instance(6,10,8);
    cb3_ptr = cube_instance(5,20,30);
    cb4_ptr = cube_instance(17,100,25);
    cb5_ptr = cube_instance(3,6,9);

    //init the clist
    clist_init(&clist, destroy);

    //insert the above 5 element 
    /* cb1 -> cb1 */
    ret = clist_ins_next(&clist, NULL, (void *)cb1_ptr);
    if( ret != 0 )
    {
        printf("insert cb1 error\n");
        return -1;
    }
    
    /* cb1 -> cb2 -> cb1 */
    p = clist_head(&clist);
    ret = clist_ins_next(&clist, p, (void *)cb2_ptr);
    if( ret != 0 )
    {
        printf("insert cb2 error\n");
        return -1;
    }
    
    /* cb1 -> cb2 ->cb3 ->cb1*/
    p = clist_next(p);
    ret = clist_ins_next(&clist, p, (void *)cb3_ptr);
    if( ret != 0 )
    {
        printf("insert cb3 error\n");
        return -1;
    }
    
    /* cb1 -> cb2 -> cb3 -> cb4 ->cb1 */
    p = clist_next(p);
    ret = clist_ins_next(&clist, p, (void *)cb4_ptr);
    if( ret != 0 )
    {
        printf("insert cb4 error\n");
        return -1;
    }

    /* cb1 -> cb2 -> cb3 -> cb4 ->cb5 -> cb1 */
    p = clist_next(p);
    ret = clist_ins_next(&clist, p, (void *)cb5_ptr);
    if( ret != 0 )
    {
        printf("insert cb5 error\n");
        return -1;
    }

    p = clist_head(&clist); //get the head element
    for(i = 0; i < clist_size(&clist) + 1; i++ )
    {
        cb_ptr = (Cuboid *)clist_data(p); // get the element's data, every data is a Cuboid 's pointer.
        printf("i = %d: ",i);
        printf("length = %d, width = %d, height = %d\n",
                cb_ptr->length,
                cb_ptr->width,
                cb_ptr->height);
        p = clist_next(p); //pointer to next element.
    }

    //remove the head element 
    clist_rem_next(&clist, p, (void **)&cb_ptr);
    printf("the removed element: length = %d, width = %d, height = %d\n",
            cb_ptr->length,
            cb_ptr->width,
            cb_ptr->height);
    
    destroy(cb_ptr); //free the memeory

    //destroy the circle list 
    clist_destroy(&clist);
    printf("after destroy the list, its size = %d\n", clist_size(&clist));
    return 0;
}
        上述代码的主要逻辑思路为:先实例化5个长方体,然后初始化一个单向循环链表,紧接着将5个长方体分别作为data域插入到链表中,然后删除其中某个元素,最后销毁整个循环链表。经过编译后,运行程序,结果如下所示:

        


        注:有需要的朋友可以留下邮箱,有时间我会把完整的调试过的代码通过邮箱发给大家。






        



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值