在学习C语言的指针和结构体,后面就会学习链表,而对于链表这个词,大家也是没有听说过,因为在大学的C语言书籍里面并没有这个东西,因为这属于数据与结构的内容,所以就没有在C语言书籍里面提过,但肯定是有相关的介绍,并不是很全面。
链表是一种这样的数据结果,其中的各对象按线性顺序排列,数组的线性顺序是由数组下标决定的,然而与数组不同的是,链表的顺序是由各对象的指针决定的,链表的动态集合决定了一种简单而灵活的表示方法。
链表分为单链表和双链表,我们对于单链表和双链表都进行简单的介绍。对于这里的链表更加注重由头节点的介绍,带有头节点的链表使用的更加广泛。
对于单链表就是一个单链接的链表,我们首先要创建一个结构体对象以及头节点。
typedef struct node
{
int data;
struct node* next;
}Node;
typedef struct list
{
Node* frist;
Node* last;
}List;
对于这个结构体的创建我们只考虑简单的数字插入,对于后面有什么需求,比如字符都是可以在里面继续定义你需要存放的类型。
然后我们需要对这个链表节点进行动态空间分配并进行返回。
/// @brief 创建一个链表节点
/// @param 无
/// @return 返回一个带头结点的链表
List* Creat_List(void)
{
List* l=(List*)malloc(sizeof(List));
l->frist=NULL;
l->last=NULL;
return l;
}
对于基本操作已经完成的话,我们就要进行链表的插入,对于插入又分为前插和后插,感觉用词乖乖的,前插和后插?可能带有颜色的网页看多了。
/// @brief 进行链表前插
/// @param h 输入的链表
/// @param d 插入的数据
void first_insert_list(List* h,int d)
{
if(h==NULL)
{
return ;
}
Node* p=(Node*)malloc(sizeof(Node));
p->data=d;
p->next=NULL;
if(h->frist==NULL)
{
h->frist=p;
h->last=p;
}
else
{
p->next=h->frist;
h->frist=p;
}
}
/// @brief 进行链表后插
/// @param h 输入链表
/// @param d 插入的数据
void last_insert_list(List* h,int d)
{
if(h==NULL)
{
return ;
}
Node* p=(Node*)malloc(sizeof(Node));
p->data=d;
p->next=NULL;
if(h->frist==NULL)
{
h->frist=p;
h->last=p;
}
else
{
h->last->next=p;
h->last=p;
}
}
对于按理来说,我们的插入已经完成了,感觉还是不够,我们应该让他更加有序起来,于是一个升序插入的版本诞生了。
/// @brief 对于输入的数据进行链表排序
/// @param h 输入的链表
/// @param d 输入的数据
void supper_insert_list(List* h,int d)
{
if(h==NULL)
{
return ;
}
Node* p=(Node*)malloc(sizeof(Node));
p->data=d;
p->next=NULL;
if(h->frist==NULL)
{
h->frist=p;
h->last=p;
}
else
{
Node* pr=NULL;
Node* pk=NULL;
pk=h->frist;
while (pk)
{
if(pk->data>d)
{
break;
}
pr=pk;
pk=pk->next;
}
if(pk==h->frist)
{
p->next=h->frist;
h->frist=p;
}else if(pk==NULL)
{
pr->next=p;
h->last=p;
}else
{
pr->next=p;
p->next=pk;
}
}
}
这样的话就能更加地加强了我们对链表的使用程度。但是对于‘增’我们是有了,是不是还少了一个删除操作,我们需要对链表的数据进行删除操作。
/// @brief 删除所有数据相同的节点
/// @param h 输入的链表
/// @param d 需要删除的数据
void delete_list_all(List* h,int d)
{
if(h==NULL)
{
return ;
}
Node* pk=NULL;
Node* pr=NULL;
Node* ps=h->frist;
while (1)
{
pk=ps;
while (pk)
{
if(pk->data==d)
{
break;
}
pr=pk;
pk=pk->next;
}
if(pk==NULL)
{
break;
}
ps=pk->next;
if(pk==h->frist)
{
h->frist=pk->next;
pk->next=NULL;
}else if(pk->next==NULL)
{
pr->next=NULL;
h->last=pr;
}else
{
pr->next=pk->next;
pk->next=NULL;
}
free(pk);
}
}
对于单链表我们进行了这么多的操作,是不是需要进行打印起来看看,这样才能知道我们有没有链表创建成功,以及对于链表数据的改变是否成功。
/// @brief 打印链表数据
/// @param l:头节点
void print_list(List* l)
{
if(l==NULL)
{
return ;
}
Node* p=l->frist;
while (p)
{
printf("%d ",p->data);
p=p->next;
}
printf("\r\n");
}
对于主函数我只为大家提供一个模板,上面提供的函数并不会全部调用。
int main(void)
{
int d;
List* l=Creat_List();
while (1)
{
scanf("%d",&d);
if(!d)
{
break;
}
supper_insert_list(l,d);
}
print_list(l);
free_all_list(l);
}
然而运行结果证明,以上程序可以使用。
对于双链表又是单链表的进阶版,代表链表里面有两个单链,就是双链。
与单链表极为相识,我们在这里直接附上我们的代码。
typedef struct node
{
int data;
struct node* next;
struct node* prev;
}Node;
typedef struct list
{
/* data */
Node* first;
Node* last;
}List;
/// @brief 创造一个头节点
/// @param 无
/// @return 返回一个已经创建的头节点
List* creat_list(void)
{
List* p;
p=malloc(sizeof(List));
p->first=NULL;
p->last=NULL;
return p;
}
/// @brief 尾插法
/// @param h 链表
/// @param d 值
void last_insert_list(List* h,int d)
{
if(h==NULL)
{
return ;
}
Node* p=(Node*)malloc(sizeof(Node));
p->data=d;p->next=NULL,p->prev=NULL;
if(h->first==NULL)
{
h->first=p;
h->last=p;
}
else
{
h->last->next=p;
p->prev=h->last;
h->last=p;
}
}
/// @brief 头插法
/// @param h 链表
/// @param d 值
void first_isert_list(List* h,int d)
{
if(h==NULL)
{
return ;
}
Node* p=(Node*)malloc(sizeof(List));
p->data=d;p->next=NULL;p->prev=NULL;
if(h->first==NULL)
{
h->first=p;
h->last=p;
}else
{
p->next=h->first;
h->first->prev=p;
h->first=p;
}
}
/// @brief 进行双链表的升序插入
/// @param h 需要插序的链表
/// @param d 插入的数据
void supper_isert_list(List* h,int d)
{
if(h==NULL)
{
return ;
}
Node* p=(Node*)malloc(sizeof(List));
p->data=d;p->next=NULL;p->prev=NULL;
if(h->first==NULL)
{
h->first=p;
h->last=p;
}else
{
Node* pk=h->first;
while (pk)
{
if(pk->data>d)
{
break;
}
pk=pk->next;
}
if(!pk)
{
h->last->next=p;
p->prev=h->last;
h->last=p;
}
else
{
if(pk==h->first)
{
p->next=h->first;
h->first->prev=p;
h->first=p;
}
else
{
p->next=pk;
pk->prev->next=p;
p->prev=pk->prev;
pk->prev=p;
}
}
}
}
/// @brief 删除单个值
/// @param h 链表
/// @param x 传入需要的值
void delete_x_list(List* h,int x)
{
Node* p=h->first;
while (p)
{
if(p->data==x)
{
break;
}
p=p->next;
}
if(!p)
{
return ;
}
else
{
if(p==h->first)
{
h->first=h->first->next;
p->next=NULL;
h->first->prev=NULL;
}else if(p==h->last)
{
h->last=p->prev;
h->last->next=NULL;
p->prev=NULL;
}
else
{
p->next->prev=p->prev;
p->prev->next=p->next;
p->next=NULL;
p->prev=NULL;
}
free(p);
}
}
/// @brief 删除值相同的节点
/// @param h 被执行的链表
/// @param x 一个传入需要被删除的相同值的链表
void delete_all_list(List* h,int x)
{
Node* p=NULL;
Node* ps=h->first;
while (1)
{
p=ps;
while (p)
{
if(p->data==x)
{
break;
}
p=p->next;
}
if(!p)
{
break;
}
ps=p->next;
if(p==h->first)
{
if(h->first==h->last)
{
h->first=NULL;
h->last=NULL;
}else
{
h->first=h->first->next;
p->next=NULL;
h->first->prev=NULL;
}
}
else if(p==h->last)
{
h->last=p->prev;
h->last->next=NULL;
p->prev=NULL;
}
else
{
p->next->prev=p->prev;
p->prev->next=p->next;
p->next=NULL;
p->prev=NULL;
}
free(p);
}
}
/// @brief 释放链表的空间
/// @param h 被释放的链表
void free_list(List* h)
{
Node* p=NULL;
while (h->first)
{
printf("ok");
p=h->first;
h->first=p->next;
p->next=NULL;
p->prev=NULL;
free(p);
}
h->last=NULL;
free(h);
}
/// @brief 打印正,反序列
/// @param h 被打印的链表
void print_list(List* h)
{
Node* p=h->first;
printf("************************");
printf("\r\n");
while (p)
{
printf("%d ",p->data);
p=p->next;
}
printf("\r\n");
p=h->last;
while (p)
{
printf("%d ",p->data);
p=p->prev;
}
printf("\r\n");
printf("************************");
printf("\r\n");
}
这样我们就完成了我们的双链表,这样我们链表的基本操作都已经完成了。
在这里我给大家附上一个宝藏网站:
唯一的缺陷就是有部分内容是需要付费的,正所谓人在江湖飘,哪能不挨刀,不付费就想白嫖,是没有免费的午餐吃的。