标题: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.总结
学习链表要多画图。要构建怎样的数据结构。合理的存储链表的数据。才能正确的操作存储的数据。在不需要时才能正确的销毁数据不会内存泄漏。