数据结构:双向循环链表的c语言实现方法一

本文详细介绍了如何设计和实现一个双向循环链表,包括链表节点结构、创建与销毁链表、插入节点、删除节点、查找节点等功能。此外,还提供了通用的打印和查找方法,允许用户自定义数据类型和比较函数。示例代码展示了链表操作的完整流程,如插入学生数据、遍历显示、查找和删除特定学生记录。
摘要由CSDN通过智能技术生成

双向环链

首先双向循环链表每一个节点需要有一个指向上一个节点地址的指针与指向下一个节点地址的指针,并且还有一个需要保存的数据。保存数据的类型可以这样写:

#define data_t int
struct llist_node
{
	data_t *data;
	struct llist_node *prev;
	struct llist_node *next;
}

链表中需要保存 什么类型的数据就更改这个宏定义就可以了,但是这样其实很麻烦,有一个百搭的数据类型void,用下面这种写法更通用,需要什么样的数据类型自己进行强转就行了。

struct llist_node
{
	void *data;
	struct llist_node *prev;
	struct llist_node *next;
}

但是使用void *会有个问题,这个指针指向的地址需要申请一个多大的内存去保存数据?所以需要用户指定数据类型的大小,这时候就可以添加一个头节点来保存这个信息,并且头节点可以作为链表的起点,用户每次对链表进行操作只需要有这个头节点地址就行。

typedef struct llist_head
{
	int size;
	struct llist_node head;
}llist;

链表中每个节点会保存上一个节点和下一个节点的地址,并且链表的入口为头节点,结构图就如下:
在这里插入图片描述
其中data需要根据头节点中的size信息开辟内存保存数据。
首先写链表的创建与销毁函数

// .h文件中
struct llist_node
{
	void *data;
	struct llist_node *prev;
	struct llist_node *next;
}

typedef struct llist_head
{
	int size;
	struct llist_node head;
}llist;

// 创建链表 
// 第一个参数为节点中数据的大小
llist *llist_create(int size);
// 销毁链表
// 第一个参数为需要销毁的链表
void llist_destroy(llist *);

// .c文件中实现
llist *llist_create(int size)
{
    llist *new = NULL;
    // 给链表头分配空间
    new = malloc(sizeof(*new));
    if(new == NULL) 
        return NULL;
	// 保存data的大小在头部size中
    new->size = size;
    // 将头节点的数据指向NULL,头节点中data不需要保存数据
    new->head.data = NULL;
    // 头节点的上一个和下一个节点指向自身
    new->head.prev = &new->head;
    new->head.next = &new->head;

    return new;
}

void llist_destroy(llist *list)
{
    struct llist_node *cur,*next;
    for(cur = list->head.next;cur != &list->head;cur = next)
    {
    	// 因为要销毁cur所以在销毁之前将下一个节点的地址保存在next变量中
        next = cur->next;
        // 释放掉节点中数据的空间
        free(cur->data);
        // 释放节点
        free(cur);
    }
    // 释放链表头
    free(list);
}

目前通过llist_create函数获取到了链表头节点,下面的增删改查都通过操作这个头节点操作就行。
首先添加插入方法,插入分为前插与后插,前插指在头节点与第一个节点之间插入,后插指的是在最后一个节点的后面插入。前插的话需要将当前节点的下个节点指向头节点的下个节点,当前节点的上一个节点指向头节点的head地址,然后分别再让当前节点的上一个节点的下一个节点指向自己,下一个节点的上一个节点指向自己。后插则是当前节点的上一个节点指向头节点的上一个节点,当前节点的下一个节点指向头节点的head地址。

 int llist_insert(llist *list,int mode,const void *data)
{
    // 如果操作的链表为空就返回
    if(list == NULL)
        return -1;

    struct llist_node *new = NULL;
	// 为当前节点申请内存
    new = malloc(sizeof(*new));
    if(new == NULL)
        return -2;
    // 给当前节点下的data申请内存 
    new->data = malloc(list->size);
    if(new->data == NULL)
    {
        free(new);
        return -3;
    }
    // 知道数据的大小,用这个方法拷贝数据最合适
    // 将传入数据保存到当前节点的data中
    memcpy(new->data,data,list->size);

	// 前插或者后插
    if(mode == INSERT_FORWARD)
    {
        new->next = list->head.next;
        new->prev = &list->head;
    }
    else if(mode == INSERT_BACKWARD)
    {
        new->prev = list->head.prev;
        new->next = &list->head;
    }
    new->prev->next = new;
    new->next->prev = new;

    return 0;
}

接着写一个 show方法来遍历链表里面的数据。首先明确如何才能将 链表里面 的数据打印出来,以为我们封装的方法要通用,所以不能直接写数据的类型,不知道数据的具体类型打印的方法当然就不能实现,所以这里让用户 自己 实现自己的打印函数,我们给他定义一个函数指针,我们 只负责遍历 的时候调用用户给的 函数 。相关代码如下:

// .h中
typedef void (*llist_print)(const void *data);
// 打印链表中存放的数据
// 第一个参数为要操作的链表,第二个参数为打印函数实现,提供给用户了接口
// 该函数实际只是遍历函数,通过用户提供的函数打印数据
void llist_show(llist *,llist_print );

// .c中
void llist_show(llist *list,llist_print func)
{
    struct llist_node *cur = NULL;
    for(cur = list->head.next;cur != &list->head;cur = cur->next)
    {
        func(cur->data);
    }
}

//  调用部分示例
struct student
{
    char name[20];
    int age;
    int id;
};

void student_print(const void *data)
{
    struct student std = *((struct student *)data);
    printf("name : %s\r\n",std.name);
    printf("age : %d\r\n",std.age);
    printf("id : %d\r\n",std.id);
}

int main()
{
	...
	llist_show(head,student_print);
	...
}

接下来实现find函数,find函数有两种方法,一种是通过返回值将查找到 的 结果返回给用户 ,失败 返回空。另一种方法是回填数据到用户指定的变量地址中,成功返回0,失败返回非0.这里我们选用第二种方式。
find函数在实现过程中可以拆分出共有 的 部分 给其他函数使用,比如删除某个节点,删除并获取某个节点数据都会用到通过关键词查找数据的方法,所以将这个部分 封装成一个小函数供其他函数使用。

static struct llist_node *__find(llist *list,const void *key,llist_cmp cmp)
{
    struct llist_node *cur = NULL;
    for(cur = list->head.next;cur != &list->head;cur = cur->next)
    {
        if(cmp(key,cur->data))
            return cur;
    }
    return cur;
}

通过条件查找封装的这个函数的第一个参数为要操作的链表的头,第二个参数为查找节点内容的关键词,第三个参数为用户自己实现的内容,因为我们并不知道数据是什么类型的,里面又有 什么内容,所以这部分让用户 自己实现,我们只负责在遍历时调用用户给的函数。返回值为查找到的节点。这个用户自己写的比较函数,当找到了与key匹配的值后返回1,没找到返回0。
接着就可以 实现find、delete、fetch函数了,区别时find只是找到数据回填给用户,delete是找到数据并且将其删除,fetch是找到数据将其删除,并将删除的内容回填给用户,delete和fetch都涉及脱链操作,脱链是将当前节点的上一个节点的下一个节点指向当前节点的下一个节点,当前节点的下一个节点 的上一个节点指向当前节点的上一个节点,然后再将当前节点中的data释放,再释放当前节点。实现如下:

// .h
typedef int (*llist_cmp)(const void *key,const void *data);

// 删除链表中的一个节点
// 第一个参数为要操作的链表,第二个参数为删除内容的关键词
// 第三个为用户需要自己实现的比较函数
int llist_delete(llist *,const void *key,llist_cmp cmp);

// 删除链表中的一个节点并返回删除节点内的数据
// 第一个参数为要操作的链表,第二个参数为要删除并获取的节点内容的关键词 
// 第三个参数为用户需要自己实现的比较函数,第四个参数用于保存获取的数据
int llist_fetch(llist *,const void *key,llist_cmp cmp,void *data);

// 按条件查找链表中的数据
// 第一个参数为要操作的链表,第二个参数为要查询内容的关键词
// 第三个参数为需要用户自己实现的比较函数
int llist_find(llist *,const void *key,llist_cmp cmp,void *data);

// .c实现
int llist_fetch(llist *list,const void *key,llist_cmp cmp,void *data)
{
    struct llist_node *ret = __find(list,key,cmp);
    if(data == NULL)
        return -1;
    if(ret == &list->head)
        return -2;
    memcpy(data,ret->data,list->size);

    ret->prev->next = ret->next;
    ret->next->prev = ret->prev;

    free(ret->data);
    free(ret);

    return 0;
}

int llist_delete(llist *list,const void *key,llist_cmp cmp)
{
    struct llist_node *ret = __find(list,key,cmp);
    if(ret == &list->head)
        return -1;
    ret->prev->next = ret->next;
    ret->next->prev = ret->prev;

    free(ret->data);
    free(ret);

    return 0;
}

int llist_find(llist *list,const void *key,llist_cmp cmp,void *data)
{
    struct llist_node *ret = __find(list,key,cmp);
    if(ret == &list->head)
        return -1;
    if(data == NULL)
        return -2;
    memcpy(data,ret->data,list->size);

    return 0;
}

// 使用示例
struct student
{
    char name[20];
    int age;
    int id;
};

int id_cmp(const void *key,const void *data)
{
    int id = *((int *)key);
    struct student std = *((struct student *)data);
    return !(id - std.id);
}

int main()
{
	...
	int id=1,id1=2;
	struct student temp;
	llist_find(head,&id,id_cmp,&temp);
	llist_fetch(head,&id1,id_cmp,&temp);
	llist_delete(head,&id,id_cmp);
	...
}

下面为整个工程的代码
llist.h

#ifndef LLIST_H__
#define LLIST_H__

#define INSERT_FORWARD      0
#define INSERT_BACKWARD     1

typedef void (*llist_print)(const void *data);
typedef int (*llist_cmp)(const void *key,const void *data);


struct llist_node
{
    void *data;
    struct llist_node *prev;
    struct llist_node *next;
};

typedef struct llist_head
{
    int size;
    struct llist_node head;
}llist;

// 创建一个链表,输入参数为链表内要存储的数据类型大小
// 返回值为初始化好的链表头指针,使用完后需要通过llist_destroy函数释放
llist *llist_create(int size);

// 向链表内增加一个节点
// 第一个参数为要操作的链表,第二个参数为插入方式,前插或者后插
// 第三个参数为要插入的数据
int llist_insert(llist *,int mode,const void *data);

// 删除链表中的一个节点
// 第一个参数为要操作的链表,第二个参数为删除内容的关键词
// 第三个为用户需要自己实现的比较函数
int llist_delete(llist *,const void *key,llist_cmp cmp);

// 删除链表中的一个节点并返回删除节点内的数据
// 第一个参数为要操作的链表,第二个参数为要删除并获取的节点内容的关键词 
// 第三个参数为用户需要自己实现的比较函数,第四个参数用于保存获取的数据
int llist_fetch(llist *,const void *key,llist_cmp cmp,void *data);

// 按条件查找链表中的数据
// 第一个参数为要操作的链表,第二个参数为要查询内容的关键词
// 第三个参数为需要用户自己实现的比较函数
int llist_find(llist *,const void *key,llist_cmp cmp,void *data);

// 打印链表中存放的数据
// 第一个参数为要操作的链表,第二个参数为打印函数实现,提供给用户了接口
// 该函数实际只是遍历函数,通过用户提供的函数打印数据
void llist_show(llist *,llist_print );


// 释放链表所占用的内存空间
void llist_destroy(llist *);
#endif

llist.c

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

llist *llist_create(int size)
{
    llist *new = NULL;
    // 给链表头分配空间
    new = malloc(sizeof(*new));
    if(new == NULL)
        return NULL;

    new->size = size;
    new->head.data = NULL;
    new->head.prev = &new->head;
    new->head.next = &new->head;

    return new;
}

int llist_insert(llist *list,int mode,const void *data)
{
    // 如果操作的链表为空就返回
    if(list == NULL)
        return -1;

    struct llist_node *new = NULL;

    new = malloc(sizeof(*new));
    if(new == NULL)
        return -2;
    new->data = malloc(list->size);
    if(new->data == NULL)
    {
        free(new);
        return -3;
    }
    memcpy(new->data,data,list->size);

    if(mode == INSERT_FORWARD)
    {
        new->next = list->head.next;
        new->prev = &list->head;
    }
    else if(mode == INSERT_BACKWARD)
    {
        new->prev = list->head.prev;
        new->next = &list->head;
    }
    new->prev->next = new;
    new->next->prev = new;

    return 0;
}

static struct llist_node *__find(llist *list,const void *key,llist_cmp cmp)
{
    struct llist_node *cur = NULL;
    for(cur = list->head.next;cur != &list->head;cur = cur->next)
    {
        if(cmp(key,cur->data))
            return cur;
    }
    return cur;
}


int llist_fetch(llist *list,const void *key,llist_cmp cmp,void *data)
{
    struct llist_node *ret = __find(list,key,cmp);
    if(data == NULL)
        return -1;
    if(ret == &list->head)
        return -2;
    memcpy(data,ret->data,list->size);

    ret->prev->next = ret->next;
    ret->next->prev = ret->prev;

    free(ret->data);
    free(ret);

    return 0;
}


int llist_delete(llist *list,const void *key,llist_cmp cmp)
{
    struct llist_node *ret = __find(list,key,cmp);
    if(ret == &list->head)
        return -1;
    ret->prev->next = ret->next;
    ret->next->prev = ret->prev;

    free(ret->data);
    free(ret);

    return 0;
}


int llist_find(llist *list,const void *key,llist_cmp cmp,void *data)
{
    struct llist_node *ret = __find(list,key,cmp);
    if(ret == &list->head)
        return -1;
    if(data == NULL)
        return -2;
    memcpy(data,ret->data,list->size);

    return 0;
}

void llist_show(llist *list,llist_print func)
{
    struct llist_node *cur = NULL;
    for(cur = list->head.next;cur != &list->head;cur = cur->next)
    {
        func(cur->data);
    }
}

void llist_destroy(llist *list)
{
    struct llist_node *cur,*next;
    for(cur = list->head.next;cur != &list->head;cur = next)
    {
        next = cur->next;
        free(cur->data);
        free(cur);
    }
    free(list);
}

Makefile

out:main.o llist.o
    $(CC) $^ -o $@

.PHONY:clean

clean:
    -rm -rf *.o out

main.c

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

struct student
{
    char name[20];
    int age;
    int id;
};


void student_print(const void *data)
{
    struct student std = *((struct student *)data);
    printf("name : %s\r\n",std.name);
    printf("age : %d\r\n",std.age);
    printf("id : %d\r\n",std.id);
}

int id_cmp(const void *key,const void *data)
{
    int id = *((int *)key);
    struct student std = *((struct student *)data);
    return !(id - std.id);
}

int main(int argc,char **argv)
{
    llist *head = NULL;
    head = llist_create(sizeof(struct student));
    if(head ==  NULL)
        exit(1);
    for(int i=0;i<10;i++)
    {
        struct student std;
        snprintf(std.name,sizeof(std.name),"student%d",i);
        std.age = i;
        std.id = i;
        llist_insert(head,INSERT_BACKWARD,&std);
    }


    llist_show(head,student_print);

    int id = 1;
    int id1 = 9;
    int id2 = 2;
    llist_delete(head,&id,id_cmp);
    llist_delete(head,&id1,id_cmp);
    llist_delete(head,&id2,id_cmp);

    llist_show(head,student_print);

    int id3 = 4;
    struct student temp={0};
    int err = llist_fetch(head,&id3,id_cmp,&temp);
    if(err != 0)
        printf("没有找到\r\n");
    else
    {
        printf("name : %s\r\n",temp.name);
        printf("age : %d\r\n",temp.age);
        printf("id : %d\r\n",temp.id);
    }

    err = llist_find(head,&id,id_cmp,&temp);
    if(err != 0)
        printf("没有找到\r\n");
    else
    {
        printf("name : %s\r\n",temp.name);
        printf("age : %d\r\n",temp.age);
        printf("id : %d\r\n",temp.id);
    }

    llist_show(head,student_print);

    llist_destroy(head);
    exit(0);
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值