数据结构3-双向链表、循环链表

摘要:本文详细介绍了三种链表结构的实现:双向链表、循环链表和内核链表。主要内容包括:1. 双向链表的节点定义、创建、头插/尾插法、遍历、查找、修改、删除和销毁操作的C语言实现;2. 循环链表的特殊处理(首尾相连)及各操作的实现差异;3. 内核链表的特点(节点嵌入数据结构)及其与传统链表的区别。通过代码示例和操作步骤说明,系统讲解了每种链表的基本操作原理和实现要点,包括指针处理、内存管理等关键技术细节。

一、双向链表

(一)链表节点的定义

1、定义的代码实现:

/* 节点存放数据的类型 */
typedef int datatype;
/* 节点类型 */
typedef struct node
{
        datatype data;                      
 //存放数据空间
        struct node *ppre;                 //存放前一个节点地址
        struct node *pnext;               //存放下一个节点地址
}linknode;

(二)空白链表的创建

1、步骤:

  1. 申请节点空间
  2. 对pnext和ppre赋值为NULL
  3. 返回空白节点地址即可

图例

2、代码实现:

/* 创建一个空链表 */

linknode *create_empty_linklist(void)
{
        linknode *ptmpnode = NULL;
        ptmpnode = malloc(sizeof(linknode));
        if (NULL == ptmpnode)
        {
                perror("fail to malloc");
                return NULL;
        }
        ptmpnode->ppre = NULL;
        ptmpnode->pnext = NULL;
        return ptmpnode;
}

(三)链表的头插法

1、步骤:

  1. 申请节点
  2. 存放数据
  3. pnext赋值为phead->pnext
  4. ppre赋值为phead的地址
  5. phead->pnext赋值为新申请节点地址
  6. 如果有后一个节点,需要让后一个节点的ppre指向该节点

2、代码实现:

/* 链表头插法 */
int insert_head_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;

        ptmpnode = malloc(sizeof(linknode));


        if (NULL == ptmpnode)
        {
                perror("fail to malloc");
                return -1;
        }
        ptmpnode->data = tmpdata;
        ptmpnode->pnext = phead->pnext;
        ptmpnode->ppre = phead;
        phead->pnext = ptmpnode;
        if (ptmpnode->pnext != NULL)
        {
                ptmpnode->pnext->ppre = ptmpnode;
        }
        return 0;

}

3、注意事项:

        与单向链表思想上的不同是:要判断ptmpnode->pnext;后面还有没有节点。

(四)链表尾插法

1、步骤:

  1. 申请节点
  2. 将节点的pnext赋值为NULL
  3. 找到链表最后一个节点
  4. 将节点的ppre赋值为最后一个节点地址
  5. 将最后一个节点的pnext赋值为新申请节

2、代码实现:

/* 链表尾插法 */
int insert_tail_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        linknode *plastnode = NULL;
        ptmpnode = malloc(sizeof(linknode));
        if (NULL == ptmpnode)
        {
                perror("fail to malloc");
                return -1;
        }
        plastnode = phead;
        while (plastnode->pnext != NULL)
        {
                plastnode = plastnode->pnext;
        }
        ptmpnode->data = tmpdata;
        ptmpnode->pnext = NULL;
        ptmpnode->ppre = plastnode;
        plastnode->pnext = ptmpnode;


        return 0;
}

(五)链表的遍历

1、步骤:与单向链表类似

2、代码实现:

/* 链表的遍历 */
int show_linklist(linknode *phead)
{
        linknode *ptmpnode = NULL;
        ptmpnode = phead->pnext;
        while (ptmpnode != NULL)
        {
                printf("%d ", ptmpnode->data);
                ptmpnode = ptmpnode->pnext;
        }
        printf("\n");


        return 0;
}

(六)链表的查找

1、步骤:与单向链表类似

2、代码实现:

/* 链表的查询 */
linknode *find_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        ptmpnode = phead->pnext;

        while (ptmpnode != NULL)
        {
                if (ptmpnode->data == tmpdata)
                {
                        return ptmpnode;
                }
                ptmpnode = ptmpnode->pnext;
        }


        return NULL;
}

(七)链表的修改

1、步骤:与单向链表的类似

2、代码实现:

/* 链表的修改 */
int update_linklist(linknode *phead, datatype olddata, datatype newdata)
{
        linknode *ptmpnode = NULL;
        ptmpnode = phead->pnext;
        while (ptmpnode != NULL)
        {
                if (ptmpnode->data == olddata)
                {
                        ptmpnode->data = newdata;
                }
                ptmpnode = ptmpnode->pnext;
        }
        return 0;
}

(八)链表的删除

1、步骤:

  1. 找到要删除的节点
  2. 让删除节点的前一个节点的pnext指向要删除节点的后一个节点
  3. 让伤处节点的后一个节点的ppre指向要删除节点的前一个节点
  4. 将要删除的节点释掉

        <细节版>

  1. 定义两个指针ptmpnode、pfreenode,ptmpnode用来遍历寻找要删除的数据
  2. 将ptmpnode->pnext->ppre = ptmpnode->ppre;
  3. 将ptmpnode->ppre->pnext = ptmpnode->pnext;
  4. 令pfreenode = ptmpnode,ptmpnode往前进一步
  5. 释放pfreenode空间

图例

2、代码实现:

/* 链表的删除 */

int delete_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        linknode *pfreenode = NULL;
        ptmpnode = phead->pnext;
        while (ptmpnode != NULL)
        {
                if (ptmpnode->data == tmpdata)
                {
                        ptmpnode->ppre->pnext = ptmpnode->pnext;
                        if (ptmpnode->pnext != NULL)
                        {
                               ptmpnode->pnext->ppre = ptmpnode->ppre;
                        }
                        pfreenode = ptmpnode;
                        ptmpnode = ptmpnode->pnext;
                        free(pfreenode);

                }
                else
                {
                        ptmpnode = ptmpnode->pnext;
                }
        }
        return 0;
}
 

3、注意事项:

1)设置两个指针的作用:

① 删除链表元素完要释放空间,所以需要定于一个指针(pfreenode后续称为A)指向此时的位置(ptmpnode后续称为B)。

② 若没有A,直接释放掉B之后我们就找不到被删除的下一个节点位置。

③ 所以需要A指向B此时的位置,然后让B往前再走一个节点到达下一个节点。然后释放掉A指向节点的空间。

2)解析释放空间的操作:

       free(pfreenode);   释放的是该指针指向的空间,而不是指针本身。该操作之后此指针就为悬垂指针。pfreenode仍然可以重新指向别的空间。所以为了误用,规范使用最好释放后立即置空。

(九)链表的销毁

1、步骤:与单向链表类似

2、代码实现:

/* 链表的销毁 */
int destroy_linklist(linknode **pphead)
{
        linknode *ptmpnode = NULL;
        linknode *pfreenode = NULL;
        ptmpnode = *pphead;
        pfreenode = ptmpnode;
        while (ptmpnode != NULL)
        {
                ptmpnode = ptmpnode->pnext;
                free(pfreenode);
                pfreenode = ptmpnode;
        }
        *pphead = NULL;
        return 0;
}

二、循环链表

(一)节点定义

1、步骤:与双向链表类似

2、代码实现:

/* 节点存放数据的类型 */
typedef int datatype;


/* 节点类型 */
typedef struct node
{
        datatype data;                 //存放数据空间
        struct node *ppre;           //存放前一个节点地址
        struct node *pnext;         //存放下一个节点地址
}linknode

(二)链表创建

1、步骤:

  1. 申请节点
  2. pnext和ppre都指向自己

2、代码实现:

/* 创建空链表 */
linknode *create_empty_linklist(void)
{
        linknode *ptmpnode = NULL;
        ptmpnode = malloc(sizeof(linknode));
        if (NULL == ptmpnode)
        {
                perror("fail to malloc");
                return NULL;
        }
        ptmpnode->pnext = ptmpnode->ppre = ptmpnode;


        return ptmpnode;
}

(三)链表头插法

1、步骤:

  1. 申请节点空间
  2. 存放数据
  3. 将pnext指向空白节点的pnext
  4. 将ppre指向空白节点
  5. 将空白节点的pnext指向新申请节点
  6. 将后续节点的ppre指向新申请节点

2、代码实现:

/* 头插法 */
int insert_head_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        ptmpnode = malloc(sizeof(linknode));
        if (NULL == ptmpnode)
        {
                perror("fail to malloc");
                return -1;

        }
        ptmpnode->data = tmpdata;
        ptmpnode->pnext = phead->pnext;
        ptmpnode->ppre = phead;
        ptmpnode->pnext->ppre = ptmpnode;
        ptmpnode->ppre->pnext = ptmpnode;
        return 0;
}

(四)链表尾插法

1、步骤:

  1. 申请节点空间
  2. 将数据存放到节点中
  3. 将pnext赋值为空白节点
  4. 将ppre赋值为之前的最后一个节点地址
  5. 将之前最后一个节点的pnext指向新申请节点
  6. 将空白节点的ppre指向新申请节点

2、代码实现:

/* 尾插法 */
int insert_tail_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        ptmpnode = malloc(sizeof(linknode));
        if (NULL == ptmpnode)
        {
                perror("fail to malloc");
                return -1;
        }
        ptmpnode->data = tmpdata;
        ptmpnode->pnext = phead;
        ptmpnode->ppre = phead->ppre;
        ptmpnode->pnext->ppre = ptmpnode;
        ptmpnode->ppre->pnext = ptmpnode;
        return 0;
}

(六)链表的遍历

1、步骤:与双向链表类似,循环链表最终循环条件由 != NULL修改为 != phea

2、代码实现:

/* 链表节点遍历 */
int show_linklist(linknode *phead)
{
        linknode *ptmpnode = NULL;
        ptmpnode = phead->pnext;
        while (ptmpnode != phead)
        {
                printf("%d ", ptmpnode->data);
                ptmpnode = ptmpnode->pnext;
        }
        printf("\n");
        return 0;
}

(七)链表的查找

1、步骤:与双向链表类似

2、代码实现:

/* 链表的查找 */
linknode *find_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        ptmpnode = phead->pnext;
        while (ptmpnode != phead)
        {
                if (ptmpnode->data == tmpdata)
                {
                        return ptmpnode;
                }
                ptmpnode = ptmpnode->pnext;
        }


        return NULL;
}

(八)链表的修改

1、步骤:与双向链表类似

2、代码实现:

/* 链表的修改 */
int update_linklist(linknode *phead, datatype olddata, datatype newdata)
{
        linknode *ptmpnode = NULL;
        ptmpnode = phead->pnext;
        while (ptmpnode != phead)
        {
                if (ptmpnode->data == olddata)
                {
                        ptmpnode->data = newdata;
                }
                ptmpnode = ptmpnode->pnext;
        }
        return 0;
}

(九)链表的删除

1、步骤:与双向链表类似

2、代码实现:

/* 链表的删除 */
int delete_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        linknode *pfreenode = NULL;
        ptmpnode = phead->pnext;
        while (ptmpnode != phead)
        {
                if (ptmpnode->data == tmpdata)
                {
                        ptmpnode->ppre->pnext = ptmpnode->pnext;
                        ptmpnode->pnext->ppre = ptmpnode->ppre;
                        
pfreenode = ptmpnode;
                        ptmpnode = ptmpnode->pnext;
                        free(pfreenode);

                        pfreenode = NULL;
                }
                else
                {
                        ptmpnode = ptmpnode->pnext;
                }

        }
        return 0;
}

(十)链表的销毁

1、步骤:与双向链表类似

1、代码实现:

/* 链表的销毁 */
int destroy_linklist(linknode **pphead)
{
        linknode *ptmpnode = NULL;
        linknode *pfreenode = NULL;


        ptmpnode = (*pphead)->pnext;
        pfreenode = ptmpnode;
        while (ptmpnode != *pphead)
        {
                ptmpnode = ptmpnode->pnext;
                free(pfreenode);
                pfreenode = ptmpnode;
        }
        free(*pphead);
        *pphead = NULL;


        return 0;
}

三、内核链表

1、概念:

  1. Linux内核中所使用到的一种链表结构
  2. 使用同一种结构可以存储不同类型的链表
  3. 普通链表:链表节点中包含数据
  4. 内核链表:数据中包含链表节点

2、结构:

3、 内核链表、单向链表、双向链表和循环链表 区别与联系

  • 单向链表
    • 由一系列节点组成,每个节点包含数据域和一个指向下一个节点的指针。
    • 节点之间的连接是单向的,只能从链表头开始,沿着指针依次访问后续节点,无法反向遍历。
  • 双向链表
    • 同样由节点构成,每个节点除了包含数据域和指向下一个节点的指针外,还包含一个指向前一个节点的指针。
    • 可以从链表头向链表尾遍历,也可以从链表尾向链表头遍历,增加了遍历的灵活性。
  • 循环链表
    • 可以是单向循环链表或双向循环链表。在单向循环链表中,最后一个节点的指针指向链表头节点;在双向循环链表中,头节点的前驱指针指向尾节点,尾节点的后继指针指向头节点。
    • 形成一个封闭的环,使得遍历可以无限循环下去。
  • 内核链表
    • 是 Linux 内核中使用的一种链表实现方式,它将链表节点嵌入到其他数据结构中。节点只包含两个指针(前驱和后继),不包含数据域。
    • 通过这些指针将不同的数据结构连接成链表,使得链表操作与具体的数据结构解耦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值