2021-03-10

标题:c语言双向循环链表

1.首先是带头的双向循环链表。需要两个指针域分别指向前一个结点prev和后一个结点next。因为是循环所以最后一个节点的next要指向头。头节点的prev要指向尾结点。下面就是一个节点void* date 是数据域void*是万能指针方便任意数据类型。

typedef struct NODE
{
    void *date;
    struct NODE *next;
    struct NODE *prev;
}Node;

1.创建头节点

/******************
函数功能:创建头结点
函数参数:无
函数返回值:申请成功返回头结点,否则返回NULL
******************/

```c
Node* createhead()
{
   Node* head=(Node*)malloc(sizeof(Node));
   if(head==NULL)
   {
       printf("头结点申请失败\n");
       return NULL;
   }
   head->next=head;
   head->prev=head;
   head->date=NULL;//头结点不存数据 数据没有动态申请
   return head;  //返回创建的头结点
}

当一开始只有头节点是时两个指针域读指向头节点如下图在这里插入图片描述
2.在尾部增加一个节点
在尾部插入一个节点最重要的是先找到尾结点。因为是循环的head->prev就是指向尾结点。用Node*temp_end保存。

/******************
函数功能:在尾部插入一个结点
函数参数:参数1:头接点   参数2:要插入的数据 参数3:插入的大小
函数返回值:成功返回0,否则返回-1
******************/
int inserttail(Node*head,void*date,int size)
{
    if(head==NULL||date==NULL)
    {
        printf("head==NULL 失败\n");
        return -1;
    }
    Node*now=my_malloc(date,size);
    //找到尾结点
    Node*temp_end=head->prev;   //找到尾结点

    //插入新结点
    temp_end->next=now;
    now->next=head;
    now->prev=temp_end;
    head->prev=now;
    return 0;
}

要结合上面的图看。如果在尾部插入第二节点可以根据
temp_end->next=now; now->next=head;
now->prev=temp_end; head->prev=now; 这四句自行画出第二个节点插入的图。my_malloc是申请一个节点。代码如下在这里插入图片描述
3.动态申请节点函数

 Node *my_malloc(void *date,int size)
{
    Node*now=(Node*)malloc(sizeof(Node)); //申请一个新结点
    if(now==NULL)
    {
         printf("新结点申请失败\n");
        return NULL;
    }
    now->date=(void*)malloc(sizeof(size)); //数据申请
    if(now->date==NULL)
    {
        printf("数据申请失败\n");
        free(now);
        return NULL;
    }
    memcpy(now->date,date,size);
    now->next=NULL;
    return now;
}

4.遍历链表
遍历链表可以从表头到表尾也可以从表尾到表头。这里是表头到表尾。
重点要记住遍历的退出条件不等于表头还有在这里用到了函数指针show
在主函数中定义一个show指向声明相同的函数。


```c

```c
/******************
函数功能:链表打印
函数参数:参数1:头接点   参数2:打印函数(是一个函数指针)
函数返回值:无
******************/
void print(Node*head,void (*show)(void*))
{
    if(head==NULL)
    {
         printf("head==NULL 失败\n");
         return ;
    }
   Node*temp_head=head; 
   while(temp_head->next!=NULL)
   {
         if(temp_head->date!=NULL)
         {
              show(temp_head->date); 
         }
         temp_head=temp_head->next; 
         if(temp_head==head)   
         {
             break;
         }
   } 
}

5.删除一个节点。
首先要查找到一个节点返回查找到的节点的地址。然后传入节点地址。这里是根据地址来判断是否找到。
temp_head->next=node->next; node->next->prev=temp_head;

/******************
函数功能:删除一个指定的结点
函数参数:参数1:头接点   参数2:删除的结点
函数返回值:成功返回0 否则返回-1
******************/
int delete_list(Node*head,Node*node)     //删除一个结点
{
    if(head==NULL||node==NULL||node->date==NULL||head->next==NULL)
    {
         printf("head==NULL 失败\n");
         return -1;
    }
    Node*temp_head=head;
    while(temp_head->next!=NULL)
    {
        if(temp_head->next==node)    //判断地址是否相同
        { 
            temp_head->next=node->next;
            node->next->prev=temp_head;
            free(node->date);      //先释放数据域
            free(node);
            node=NULL;
            return 0;   //删除成功
        }
        temp_head=temp_head->next;
        if(temp_head==head)
        {
            break;
        }
    }
    return -1;  //删除失败
}

temp_head要删除的前一个。在这里插入图片描述
6.查找一个节点
是根据节点的地址来查找的。cmp是一个函数指针。cmp指向的函数在主函数中声明。

/******************
函数功能:查找一个结点数据
函数参数:参数1:头接点   参数2:结点数据  参数3:查找的模式  参数4:比较的函数指针
函数返回值:成功返回查找的结点  否则返回NULL
******************/
Node *node_find(Node *head, const void *date, int mode, int (*cmp)(const void *, const void *, int))
{
    if(head==NULL||date==NULL||cmp==NULL||head->next==NULL)
    {
        printf("head==NULL 失败\n");
         return NULL;
    }
    Node*temp_head=head;
    while(temp_head->next!=NULL)
    {
        if(temp_head->date!=NULL)
        {
            if(cmp(temp_head->date,date,mode)==0)  //查找到了
            {  
                   return temp_head;   //返回查找到了      
            }
        }
        temp_head=temp_head->next;
        if(temp_head==head)
        {
            break;
        }
    }
    return NULL;   //没有找到
}

7.更新链表中的数据
首先要传入要更改的节点与要更改的数据。update是一个函数指针它所指向的函数在主函数声明。

/******************
函数功能:结点数据的更新
函数参数:参数1:要更新结点的数据   参数2:新数据   参数3:模式  参数4:更新的函数指针
函数返回值:无
******************/
int node_update(Node*node,const void* date,int mode,void (*update)(void*,const void*,int mode))
{
      if(node==NULL||date==NULL||update==NULL)
    {
        printf("更新失败\n");
         return -1;
    }
    update(node->date,date,mode);
    return 0;   //没有找到
}

8.排序
这里是一个冒泡排序。mode是模式。因为节点数据是void* 会传入一个结构体所以模式是可以根据学号、名字、身高。如 1按学号排序 2按名字排序。
node_cmp是一个函数指针指向一个比较函数。同样在主函数声明定义。

void node_sort(Node*head,int mode,int (*node_cmp)(const void *,const void*,int))
{
       if(head==NULL||node_cmp==NULL||head->next->date==NULL)
       {
           printf("排序失败\n");
           return ;
       }
       for(Node*temp_head=head->next;temp_head!=head;temp_head=temp_head->next)
       {
           for(Node*jemp=head->next;jemp->next!=head;jemp=jemp->next)
           {
                 if(node_cmp(jemp->date,jemp->next->date,mode)>0)
                 {
                           void *temp=jemp->date;
                           jemp->date=jemp->next->date;
                           jemp->next->date=temp;
                 }
           }
       }

9.销毁链表
销毁链表要Node**二级指针这样才能释放头节点(改变头结点的指向)

/******************
函数功能:链表释放
函数参数:参数1:头接点的地址 如果要释放头指针必须要拿到头指针的地址
函数返回值:无
******************/
void list_free(Node**head)
{
    if(*head==NULL)
    {
        printf("list_free head==NULL失败\n");
         return ;
    }
    Node*temp_head=*head;
    Node*temp=NULL;
    while(temp_head->next!=NULL)
    {
         temp=temp_head->next;
        if(temp_head->date!=NULL)
        {
            free(temp_head->date);
            free(temp_head);
            temp_head=NULL;
        }
        temp_head=temp;
        if(temp_head==*head)
        {
            break;
        }
    }
    free(*head);  //释放头结点 要拿到头接点的地址
    *head=NULL;
}

10.主函数

#include<stdio.h>
#include<string.h>
#include"student.h"
#include"list.h"
void m_show(void*v)
{
    int *p=v;
    printf("%d->",*p);
}
int Cmp(const void *date1, const void *date2, int mode)
{
      const Stu *s1=date1;
      const Stu *s2=date2;
     switch(mode)  
     {
         case 1:
                //姓名比较
                return strcmp(s1->m_name,s2->m_name)?1:-1;
              break;
        case  2://年龄比较
        {
               int *temp1=(int*)s1;
               int *temp2=(int*)s2;
               return (*temp1)>(*temp2)?1:-1;
        }
              break;
        case 3:
              return memcmp(s1->m_name,s2->m_name,sizeof(int));  //所以内容比较
              break;
     }
     return -1;   //模式错误
}
void UPdate (void*old ,const void* new,int mode)
{
    int *date1 =old;
    const int *date2 = new;
    switch(mode)
    {
        case 1://姓名修改
            break;
        case 2://年龄修改
            break;
        case 3://所有内容修改
            memcpy(date1,date2,sizeof(int));
            break;
    }
}
int main()
{
    Node *head=createhead();
    if(head==NULL)
    {
        printf("head(头接点创建成功)==NULL");
        return -1;
    }
    Stu s[3]={{"阿里",16,78.9},{"字节",46,90.9},{"深蓝",20,82}};
    int len=sizeof(s)/sizeof(s[0]);
    for(int i=0;i<len;i++)
    {
           inserthead(head,&s[i],sizeof(s[i]));
    }
    print(head,stu_show);
    printf("\n");
 }

11.运行的结果

student@student-machine:~/双向循环链表$ gcc main.c list.c student.c
student@student-machine:~/双向循环链表$ ./a.out
姓名:深蓝	年龄:20	成绩:82.000000
姓名:字节	年龄:46	成绩:90.900002
姓名:阿里	年龄:16	成绩:78.900002

12.总结
学习链表要多画图。要构建怎样的数据结构。合理的存储链表的数据。才能正确的操作存储的数据。在不需要时才能正确的销毁数据不会内存泄漏。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值