2、链式存储的双向循环链表

这篇博客详细介绍了如何使用C语言实现带头结点的双向循环链表,包括结构体定义、链表创建、插入、查找、删除等操作。通过变长结构体思想动态分配内存,实现了数据结构的高效利用。同时提供了遍历打印和销毁链表的函数,以及主函数示例,展示了完整的双向链表操作流程。
摘要由CSDN通过智能技术生成

本篇博客部分图片来自《黑马程序员数据结构资料》,如有侵权,请联系我。

双向链表

双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用 来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存 储数据,指向前驱结点的指针域和指向后继结点的指针域指向第一个真正存储数据的结点。
在这里插入图片描述

带头结点的双向循环链表

每个结构体都知道自己的前驱和后继
在这里插入图片描述

1、结构体以及链表的构建

在这里插入图片描述

struct llist_node_st
{
	void *data;
	struct llist_node_st *prev,*next;
};

typedef struct 
{
	int size;
	struct llist_node_st head;
}LLIST;
//main.c中学生结构体
struct score_st
{
    int id;
    char name[NAMESIZE];
    int math;
};
LLIST* llist_create(int size)
{
	LLIST *new;
	
	new = malloc(sizeof(*new));
	if(new == NULL)
		return NULL;

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

1.2 变长结构体思想的创建(malloc)

在这里插入图片描述

struct llist_node_st
{   
    struct llist_node_st *prev,*next;
    char data[1];  //地址对齐
};

struct llist_head_st
{   
    int size;   //主函数里学生结构体的大小
    struct llist_node_st head;
}LLIST;
LLIST* llist_create(int size)
{
    LLIST *new;

    new = malloc(sizeof(*new)); //struct llist_head_st
    if(new == NULL)
        return NULL;

    new->size = size;  //主函数里学生结构体的大小
    new->head.prev = new->head.next = &new->head; //new的前驱和后继都指向头结点

    return new; //返回一个LLIST * 类型的指针
}

1.3 双向链表的插入

用变长结构体思想:申请空间时多申请出一块学生结构体的空间
在这里插入图片描述

 int llist_insert(LLIST *ptr, const void *data,int mode)
 {   //ptr:头结点 data:要插入的数据 mode:插入模式(1->首部插入 2->尾部插入)
     struct llist_node_st *newnode;
     
     newnode = malloc(sizeof(*newnode) + ptr->size);
     if(newnode == NULL)
         return -1;
     //将连续的size字节的data数据复制到newnode->data指向的地址为起点的内存中去.
     memcpy(newnode->data, data, ptr->size);
     
     if(mode == LLIST_FORWARD) //首部插入
     {
         newnode->prev = &ptr->head; //新结点前驱指针指向头结点
         newnode->next = ptr->head.next; //新结点的后继指针指向头结点的后继
     }
     else
     {
         if(mode == LLIST_BACKWARD) //尾插法
         {
             newnode->prev = ptr->head.prev;//最后一个结点的前驱指向头结点的前驱
             newnode->next = &ptr->head; //最后一个结点的后继指向头结点的头
         }
         else
             return -3;
     }
     newnode->prev->next = newnode; //新结点前驱的后继指针指向新结点
     newnode->next->prev = newnode; //新结点后继的前驱指针指向新结点
 
     return 0;
 }

1.4 双向链表的查找

//main.c 功能:按id查找,如果找到了返回0;
int id_cmp(const void *key, const void *data)
{	//key:我要查找的id  data:学生结构体指针 data->id:表示该学生的id
    const int *k = key;  //类型转换 k 为int*型
    const struct score_st *d = data; //类型转换data为struct score_st *型

    return *k - d->id; //return 0 表示有这个id
}

find_函数功能:按id查找数据,如果找到返回 llist_node_st * 结构体指针

struct llist_node_st *find_(LLIST *ptr,const void *key,llist_cmp *cmp)
{   
    struct llist_node_st *cur;
    //遍历:当cur == &ptr->head时结束(head是变量名,需要取地址)
    for(cur = ptr->head.next; cur != &ptr->head ; cur = cur->next)
    {// cur = cur->next
        if(cmp(key, cur->data) == 0)
            break;
    }
    
    return cur;
}
//函数功能:按id查找数据,如果找到返回node->data指针
void *llist_find(LLIST *ptr,const void *key,llist_cmp *cmp)
{
    struct llist_node_st *node;

    node = find_(ptr,key,cmp);
    if(node == &ptr->head) //如果返回的是头结点就证明没找到
        return NULL;
    else
        return node->data;
}

1.5 双向链表的删除

操作时只需要获取待插入结点就可以进行操作。
在这里插入图片描述

//函数功能:按id进行查找,找到直接删除。
int llist_delete(LLIST *ptr, const void *key, llist_cmp *cmp)
{   //ptr:头结点  key:要删除的数据  cmp:比较函数
    struct llist_node_st *node;

    node = find_(ptr,key,cmp);  //先找这个结点,如果没有返回-1.
    if(node == &ptr->head)
        return -1;
	//node是要删除的结点
    node->prev->next = node->next; //待删除结点前驱的后继指针指向待删除结点的后继
    node->next->prev = node->prev; //待删除结点后继的前驱指针指向待删除结点的前驱
    free(node); //释放已删除节点
    return 0;
}
//函数功能:寻找这个结点,并且把这个结点的数据写到data域中,然后删除。
int llist_fetch(LLIST* ptr, const void *key, llist_cmp *cmp,void *data)
{   
    struct llist_node_st *node;

    node = find_(ptr,key,cmp);  //找到我要删除的结点
    if(node == &ptr->head)     //如果没找到就是返回的头结点
        return -1;

    node->prev->next = node->next; //待删除结点前驱的后继指针指向待删除结点的前驱
    node->next->prev = node->prev; //待删除结点后继的前驱指针指向待删除结点的前驱
    //将连续的size字节的node->data数据复制到新结点data指向的地址为起点的内存中去.
    memcpy(data, node->data, ptr->size);
    free(node);//释放已删除节点
    return 0;
}

1.6 遍历打印llist_travel(LLIST *,llist_op *)

void print_s(void *data)
{   
    struct score_st *d = data; // struct score_st:学生结构体
    printf("%d %s %d\n",d->id, d->name, d->math);
}
//typedef void llist_op(void *); print_s(void *data)
void llist_travel(LLIST *ptr, llist_op *op)
{
    struct llist_node_st *cur;

    for(cur = ptr->head.next; cur != &ptr->head ; cur = cur->next)
        op(cur->data); //print_s(cur->data)
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pEvkXL9v-1628908040970)(C:\Users\10101\AppData\Roaming\Typora\typora-user-images\image-20210813171643229.png)]

1.7 销毁_llist_destroy(LLIST *)

void llist_destroy(LLIST *ptr)
{
    struct llist_node_st *cur,*next;
	//从第一个有效结点开始遍历,也就是头结点所指向的下一个结点
    for(cur = ptr->head.next; cur != &ptr->head ; cur = next)
    {
        next = cur->next;
        free(cur);
    }
    free(ptr); //最后释放头结点
}

1.8 主函数_main.c()

int main()
{
    LLIST *handler;
    struct score_st tmp;
    int i;

    handler = llist_create(sizeof(struct score_st));
    if(handler == NULL)
    {
        printf("llist_create() failed.\n");
        exit(1);
    }
    for(i = 0 ; i < 7 ; i++)
    {
        tmp.id = i;
        tmp.math = 100-i;
        snprintf(tmp.name, NAMESIZE,"STU%d",i);

        llist_insert(handler, &tmp, LLIST_BACKWARD);
    }

    llist_travel(handler,print_s);
    printf("\n\n");

    int findid = 12;
    char *findname = "STU40";
    struct score_st *retp;

/*
    //retp = llist_find(handler,&findid,id_cmp);
    retp = llist_find(handler,findname,name_cmp);
    if(retp)
        print_s(retp);
    else
        printf("Can not find.\n");
*/
//  llist_delete(handler,&findid,id_cmp);
    if(llist_fetch(handler,&findid,id_cmp,&tmp) == 0)
        print_s(&tmp);
    printf("\n\n");

    llist_travel(handler,print_s);

    llist_destroy(handler);
    exit(0);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值