也没想象中那么神秘的数据结构-一环扣一环的“链表”(循环双链表)

概念

循环双向链表是一种更复杂的数据结构类型,是双向链表的一种特殊形式,主要用来解决一些搜索问题,特别是搜索尾节点、以及链表翻转输出等问题和双向链表不同的是,链表中任何节点均不含NULL,所以也不再通过p_node == NULL这样的条件作为链表遍历的结束条件。

注意

1、循环双链表的结构:循环双链表本质还是双链表,所以结构同双链表一样由节点构成,每个节点包含数据域(存储元素内容)和前驱指针域(指向直接前驱元素)+后继指针域(指向直接后继元素)三部分。

2、头指针:永远指向链表第一个节点的位置。用于指明链表的位置,便于后期找到链表并使用表中的数据。

3、头节点:通常作为链表的第一个节点。并非必须的,只是为了方便解决某些实际问题而引入的,另外头节点数据域可以用来存储链表元素个数。

4、首元节点:头节点的下一个节点,第一个真正意义上存储了数据的节点。

5、最后一个节点的后继节点,指向头结点,这是链表结束条件的唯一判据。

核心

循环双链表由前驱指针域、后继指针域和数据域构成,并且声明头指针指明链表的位置,节点与节点之间通过指针链接,最后一个节点的后继指针指向头结点,从而形成一个环。如下图代码和图形所示。

/* 链表元素定义 */
struct t_node{
    int data;                   /* 元素值 */
    struct t_node *p_prev;      /* 指向上一个元素 */
    struct t_node *p_next;      /* 指向下一个元素 */
};

/* 链表定义 */
struct t_link{
    struct t_node *p_node;      /* 指向链表头结点 */
};

循环双链表的插入同双链表一致分为在链表头插入和链表尾插入两种,链表头的位置可以通过头结点的后继指针得到,而链表尾的位置可以通过头结点的前驱指针得到

链表头插入:整个过程分为三步,a、创建节点;b、找到链表头位置;c、插入节点,具体执行如下图代码和图形所示。

/**
 * @在链表头增加元素
 * @p_link:链表       data:元素值
 * @返回1表示成功,0表示失败
**/
int link_add_front(struct t_link *p_link, int data)
{
    struct t_node *p_node = NULL, *p_head = p_link->p_node;

    if (link_is_full(p_link))
        return 0;

    if ((p_node = creat_node(data)) == NULL)
        return 0;

    p_node->p_next = p_head->p_next;
    p_node->p_prev = p_head;
    p_head->p_next->p_prev = p_node;
    p_head->p_next = p_node;
    p_head->data++;

    return 1;
}

链表尾插入:过程同链表头插入一样,链表尾的位置可以通过头结点的前驱指针直接得到,具体执行如下图代码和图形所示

/**
 * @在链表尾增加元素
 * @p_link:链表       data:元素值
 * @返回1表示成功,0表示失败
**/
int link_add_rear(struct t_link *p_link, int data)
{
    struct t_node *p_head = p_link->p_node;
    struct t_node *p_node = NULL;

    if (link_is_full(p_link))
        return 0;

    /* 1. 创建节点 */
    if ((p_node = creat_node(data)) == NULL)
        return 0;

    /* 2. 插入元素 */
    p_head->p_prev->p_next = p_node;
    p_node->p_prev = p_head->p_prev;
    p_node->p_next = p_head;
    p_head->p_prev = p_node;
    p_link->p_node->data++;

    return 1;
}

循环双链表的删除同样包括删除链表头和删除链表尾,删除的核心在于让目标节点的前驱节点和后继节点直接相连,然后删除目标节点即可

删除链表头:代码和示意图如下所示

/**
 * @删除链表头元素
 * @p_link:链表
 * @返回1表示成功,0表示失败
**/
int link_del_head(struct t_link *p_link)
{
    struct t_node *p_node = NULL, *p_head = p_link->p_node;

    if (link_is_empty(p_link))
        return 0;

    p_node = p_head->p_next;
    p_head->p_next = p_node->p_next;
    p_node->p_next->p_prev = p_head;
    free(p_node);
    p_head->data--;

    return 1;
}

删除链表尾:代码和示意图如下所示。

/**
 * @删除链表尾元素
 * @p_link:链表
 * @返回1表示成功,0表示失败
**/
int link_del_rear(struct t_link *p_link)
{
    struct t_node *p_head = p_link->p_node, *p_rear = p_head->p_prev;

    if (link_is_empty(p_link))
        return 0;

    p_rear->p_prev->p_next = p_head;
    p_head->p_prev = p_rear->p_prev;
    free(p_rear);
    p_head->data--;

    return 1;
}

其余操作相对比较简单,这里就不再一一阐述了,具体参见示例代码,相信大家应该能看懂。

示例

★包含头文件dcll.h和源文件dcll.c(均已验证通过),dcll = Doubly Circular Linked List。

 dcll.h

/**
 * @Filename : dcll.h
 * @Revision : $Revision: 1.0 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 循环双链表示例(dcll = Doubly Circular Linked List)
**/

#ifndef __DCLL_H__
#define __DCLL_H__

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

#define MAX_SIZE    15          /* 链表最大元素个数 */

/* 链表元素定义 */
struct t_node{
    int data;                   /* 元素值 */
    struct t_node *p_prev;      /* 指向上一个元素 */
    struct t_node *p_next;      /* 指向下一个元素 */
};

/* 链表定义 */
struct t_link{
    struct t_node *p_node;      /* 指向链表头结点 */
};

/**
 * @初始化链表
 * @p_link:链表
 * @创建头结点,让链表头指针指向头结点,头结点数据存储链表数据长度
**/
void link_init(struct t_link *p_link);

/**
 * @清除链表
 * @p_link:链表
**/
void link_deinit(struct t_link *p_link);

/**
 * @判断链表是否已满
 * @p_link:链表
 * @返回1表示已满,0表示未满
**/
int link_is_full(const struct t_link *p_link);

/**
 * @判断链表是否已空
 * @p_link:链表
 * @返回1表示已空,0表示未空
**/
int link_is_empty(const struct t_link *p_link);

/**
 * @获取链表元素个数
 * @p_link:链表
 * @返回元素个数
**/
int link_get_size(const struct t_link *p_link);

/**
 * @在链表头增加元素
 * @p_link:链表       data:元素值
 * @返回1表示成功,0表示失败
**/
int link_add_front(struct t_link *p_link, int data);

/**
 * @在链表尾增加元素
 * @p_link:链表       data:元素值
 * @返回1表示成功,0表示失败
**/
int link_add_rear(struct t_link *p_link, int data);

/**
 * @删除链表头元素
 * @p_link:链表
 * @返回1表示成功,0表示失败
**/
int link_del_head(struct t_link *p_link);

/**
 * @删除链表尾元素
 * @p_link:链表
 * @返回1表示成功,0表示失败
**/
int link_del_rear(struct t_link *p_link);

/**
 * @获取链表头元素
 * @p_link:链表      p_data:保存链表头元素值
 * @返回1表示成功,0表示失败
**/
int link_get_front(const struct t_link *p_link, int *p_data);

/**
 * @获取链表尾元素
 * @p_link:链表      p_data:保存链表尾元素值
 * @返回1表示成功,0表示失败
**/
int link_get_rear(const struct t_link *p_link, int *p_data);

/**
 * @获取链表第n个元素
 * @p_link:链表      p_data:保存元素值     n:第n个元素
 * @返回1表示成功,0表示失败
**/
int link_get_nth(const struct t_link *p_link, int *p_data, int n);

/**
 * @打印链表元素内容
 * @p_link:链表
**/
void link_print(const struct t_link *p_link);

#endif

 dcll.c

/**
 * @Filename : dcll.c
 * @Revision : $Revision: 1.0 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 循环双链表示例(dcll = Doubly Circular Linked List)
**/

#include "dcll.h"

/**
 * @创建节点
 * @data:元素值
 * @返回节点地址,NULL表示失败
**/
static struct t_node *creat_node(int data)
{
    struct t_node *p_node = (struct t_node *)malloc(sizeof(struct t_node));

    if (p_node == NULL)
        return NULL;

    p_node->data = data;
    p_node->p_prev = NULL;
    p_node->p_next = NULL;

    return  p_node;  
}

/**
 * @初始化链表
 * @p_link:链表
 * @创建头结点,让链表头指针指向头结点,头结点数据存储链表数据长度
**/
void link_init(struct t_link *p_link)
{
    struct t_node *p_head = creat_node(0);  /* 头结点 */

    if (p_head == NULL)
        return;

    p_link->p_node = p_head;
    p_head->p_next = p_head;
    p_head->p_prev = p_head;
}

/**
 * @清除链表
 * @p_link:链表
**/
void link_deinit(struct t_link *p_link)
{
    struct t_node *p_tmp = NULL, *p_node = p_link->p_node->p_next;

    while(p_node != p_link->p_node) {
        p_tmp = p_node;
        p_node = p_node->p_next;
        free(p_tmp);    
    }
    p_link->p_node->p_next = NULL;
    p_link->p_node->data = 0;
}

/**
 * @判断链表是否已满
 * @p_link:链表
 * @返回1表示已满,0表示未满
**/
int link_is_full(const struct t_link *p_link)
{
    return (p_link->p_node->data >= MAX_SIZE);
}

/**
 * @判断链表是否已空
 * @p_link:链表
 * @返回1表示已空,0表示未空
**/
int link_is_empty(const struct t_link *p_link)
{
    return (p_link->p_node->data == 0);
}

/**
 * @获取链表元素个数
 * @p_link:链表
 * @返回元素个数
**/
int link_get_size(const struct t_link *p_link)
{
    return (p_link->p_node->data);
}

/**
 * @在链表头增加元素
 * @p_link:链表       data:元素值
 * @返回1表示成功,0表示失败
**/
int link_add_front(struct t_link *p_link, int data)
{
    struct t_node *p_node = NULL, *p_head = p_link->p_node;

    if (link_is_full(p_link))
        return 0;

    if ((p_node = creat_node(data)) == NULL)
        return 0;

    p_node->p_next = p_head->p_next;
    p_node->p_prev = p_head;
    p_head->p_next->p_prev = p_node;
    p_head->p_next = p_node;
    p_head->data++;

    return 1;
}

/**
 * @在链表尾增加元素
 * @p_link:链表       data:元素值
 * @返回1表示成功,0表示失败
**/
int link_add_rear(struct t_link *p_link, int data)
{
    struct t_node *p_head = p_link->p_node;
    struct t_node *p_node = NULL;

    if (link_is_full(p_link))
        return 0;

    /* 1. 创建节点 */
    if ((p_node = creat_node(data)) == NULL)
        return 0;

    /* 2. 插入元素 */
    p_head->p_prev->p_next = p_node;
    p_node->p_prev = p_head->p_prev;
    p_node->p_next = p_head;
    p_head->p_prev = p_node;
    p_link->p_node->data++;

    return 1;
}

/**
 * @删除链表头元素
 * @p_link:链表
 * @返回1表示成功,0表示失败
**/
int link_del_head(struct t_link *p_link)
{
    struct t_node *p_node = NULL, *p_head = p_link->p_node;

    if (link_is_empty(p_link))
        return 0;

    p_node = p_head->p_next;
    p_head->p_next = p_node->p_next;
    p_node->p_next->p_prev = p_head;
    free(p_node);
    p_head->data--;

    return 1;
}

/**
 * @删除链表尾元素
 * @p_link:链表
 * @返回1表示成功,0表示失败
**/
int link_del_rear(struct t_link *p_link)
{
    struct t_node *p_head = p_link->p_node, *p_rear = p_head->p_prev;

    if (link_is_empty(p_link))
        return 0;

    p_rear->p_prev->p_next = p_head;
    p_head->p_prev = p_rear->p_prev;
    free(p_rear);
    p_head->data--;

    return 1;
}

/**
 * @获取链表头元素
 * @p_link:链表      p_data:保存链表头元素值
 * @返回1表示成功,0表示失败
**/
int link_get_front(const struct t_link *p_link, int *p_data)
{

    if (link_is_empty(p_link))
        return 0;

    *p_data = p_link->p_node->p_next->data;

    return 1;
}

/**
 * @获取链表尾元素
 * @p_link:链表      p_data:保存链表尾元素值
 * @返回1表示成功,0表示失败
**/
int link_get_rear(const struct t_link *p_link, int *p_data)
{
    if (link_is_empty(p_link))
        return 0;

    *p_data = p_link->p_node->p_prev->data;

    return 1;
}

/**
 * @获取链表第n个元素
 * @p_link:链表      p_data:保存元素值     n:第n个元素
 * @返回1表示成功,0表示失败
**/
int link_get_nth(const struct t_link *p_link, int *p_data, int n)
{
    int cnt = 0;
    const struct t_node *p_tmp = p_link->p_node;

    if (link_get_size(p_link) < n)
        return 0;

    while (p_tmp->p_next !=  p_link->p_node) {
        p_tmp = p_tmp->p_next;
        if (++cnt == n) {
            *p_data = p_tmp->data;
            return 1;
        }
    }
    return 0;
}
/**
 * @打印链表元素内容
 * @p_link:链表
**/
void link_print(const struct t_link *p_link)
{
    struct t_node *p_tmp = NULL;

    for (p_tmp=p_link->p_node->p_next; p_tmp!=p_link->p_node; p_tmp=p_tmp->p_next)
        printf("%d ", p_tmp->data);

    printf("\n");
}

/**
 * @反向打印链表元素内容
 * @p_link:链表
**/
void link_reverse_print(const struct t_link *p_link)
{
    struct t_node *p_tmp = NULL;

    for (p_tmp=p_link->p_node->p_prev; p_tmp!=p_link->p_node; p_tmp=p_tmp->p_prev)
        printf("%d ", p_tmp->data);

    printf("\n");
}

/**
 * @单链表测试代码
**/
int main(void)
{
    int val;
    struct t_link mylink;

    link_init(&mylink);

    link_add_front(&mylink, 25);
    link_add_front(&mylink, 41);
    link_add_front(&mylink, 16);
    link_add_front(&mylink, 7);
    link_add_rear(&mylink, 8);
    link_add_rear(&mylink, 19);
    link_add_front(&mylink, 22);
    link_add_front(&mylink, 16);
    link_add_front(&mylink, 7);
    link_add_rear(&mylink, 8);
    link_add_rear(&mylink, 19);
    link_add_front(&mylink, 22);

    /* 插入元素,链表:22 7 16 22 7 16 41 25 8 19 8 19,元素个数为12 */     
    printf("the size of link is: %d\n", link_get_size(&mylink));
    printf("link:    ");
    link_print(&mylink);
    printf("reverse: ");
    link_reverse_print(&mylink);
    printf("---------------------------------------\n");

    /* 删除首尾元素,链表:7 16 22 7 16 41 25 8 19 8,元素个数为10 */     
    link_del_head(&mylink);
    link_del_rear(&mylink); 
    printf("the size of link is: %d\n", link_get_size(&mylink));
    printf("link:    ");
    link_print(&mylink);
    printf("reverse: ");
    link_reverse_print(&mylink);     
    printf("---------------------------------------\n");

    /* 第一个元素7,最后一个元素8,第三个元素22 */   
    if (link_get_front(&mylink, &val))
        printf("the front link data is: %d\n", val);

    if (link_get_rear(&mylink, &val))
        printf("the tail link data is: %d\n", val);

    if (link_get_nth(&mylink, &val, 3))
        printf("the 3th link data is: %d\n", val);

    return 0;
}

结论

feng@feng-vm:~/share/link$ gcc -o dcll dcll.c
feng@feng-vm:~/share/link$ ./dcll
the size of link is: 12
link:    22 7 16 22 7 16 41 25 8 19 8 19 
reverse: 19 8 19 8 25 41 16 7 22 16 7 22 
---------------------------------------
the size of link is: 10
link:    7 16 22 7 16 41 25 8 19 8 
reverse: 8 19 8 25 41 16 7 22 16 7 
---------------------------------------
the front link data is: 7
the tail link data is: 8
the 3th link data is: 22
feng@feng-vm:~/share/link$ 

本示例仅为循环双链表示例,公众号也提供单链表示例《也没想象中那么神秘的数据结构-一环扣一环的“链表”(单链表)》、双链表示例《也没想象中那么神秘的数据结构-一环扣一环的“链表”(双链表)》以及链表的典型应用示例。

往期 · 推荐

也没想象中那么神秘的数据结构-一环扣一环的“链表”(双链表)

也没想象中那么神秘的数据结构-先来后到的“队列”(链式队列)

也没想象中那么神秘的数据结构-后来居上的“栈”

我用C语言玩对象,观察者模式应用2-热水的用途

我用C语言玩对象,状态模式应用1-水的三态

关注

更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:数据结构源码,也可点击此处下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不只会拍照的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值