vlambda博客
学习文章列表

c语言数据结构:单链表

各位朋友们,伙计们,大家好!

好久没有更新新内容了,我发现自己原本仅有的不到四十个订阅,竟然还掉了三个! Oh NO! 所以,在此我必须更新新内容了,当然,如标题看到,今天要说的就是单链表,我将用尽我所学,来帮助大家理解单链表的各种操作,创建链表、创建结点、插入结点、查询结点、修改结点数据、删除结点、输出链表销毁链表,我会尽量讲的生动有趣些,不会让大家感到枯燥乏味,那么话不多说,就让我们开始学习吧!


0 1

创建链表

链表链表,顾名思义,就是像链子一样连在一起的表,咳咳,虽说有些废话,但确实如此,它是结构体与结构体之间以指针互相连接着,上一个结构体的指针指向下一个结构体,这样就形成了一个链表,它相对于数组有什么好处呢,大家都知道,数组内的内存空间是预先分配好的,而且也不能在使用过程中动态的改变其空间大小,这样就会有一个很大的弊端,比如给一个int型数组分配了100个内存空间,那么它就只能最多存100个数据,超过了就完蛋,存少了还浪费内存,就很烦,然而链表就没有这个问题,你有多少数据就开辟多少内存空间,不要某个数据还可以释放掉其内存空间,而且支持多个不同类型的数据在一块,多好!所以,学会了链表,数组只能躲在一边默喊mmp。
咳咳,话不多说,上代码!

#include <stdio.h>#include <stdlib.h> //此头文件包函malloc动态申请内存函数//创建一个名为list的结构体typedef struct list{  //结构体数据,你想多整几个也行,我在这只放了一个,不是我想偷懒啊 int data;   //弄一个指向下一个结点的结构体的尾指针 struct list* next;}L; //用typedef让L来代替struct list,节约点按键盘次数
//创建链表//L* 为此函数的类型为L*也就是struct list*,返回结构体函数指针L* creatorList(){ //定义一个结构体指针并分配内存空间,分配大小为此结构体所占内存大小 L* headlist = (L*)malloc(sizeof(L)); //表头只是起到一个引导作用,所以不用给其赋值 //刚创建的链表为空,所以需要让表头的尾结点指向NULL headlist->next = NULL; //返回创建好的链表 return headlist;}

02

创建结点

既然链表已经创建好了,那么接下来就是创建结点,结点里存放我们数据,结点的创建跟上面的操作类似,如果这块有啥问题可以直接来问我就可以了。

话不多说,上代码!

//创建结点L* creatorNode(int data){  //跟之前创建链表的时候相同,结点也需要申请内存空间 L* newNode = (L*)malloc(sizeof(L));  //不过,之前创建的链表只有表头,只起到了引导作用  //所以,结点里面就可以放数据了 newNode->data = data;  //将结点的尾指针指向NULL newNode->next = NULL;  //返回结点 return newNode;}

03

插入结点

结点的插入方式好几种,在此我向大家展示三种结点插入方法:尾插法、头插法、排序插法。大家可要仔细看好理解了啊,如果有啥不清楚的可以直接给我发消息!
头插法:
//函数参数为要插入的链表和要插入其中的结点void insertNode(L* head,L* newNode){  //首先先让新结点的尾指针指向头结点的尾指针所指的结点 newNode->next = head->next;  //再让头结点的尾指针指向新结点  head->next = newNode;}
尾插法:
void insertNode(L* head,L* newNode){  //首先再定义一个指针指向链表的头结点  L* pMove = head; //此做法是让head一直是链表头结点  while(pMove->next!=NULL) //判断pMove是否移动到了链表的最后一个结点    pMove = pMove->next; //使pMove移动至下一个节点  //循环完毕之后表示pMove已经指向了链表的最后一个结点  pMove->next = newNode; //将pMove,即最后的结点的尾指针指向新结点}
排序插法:
void insertNode(L* head,L* newNode){  L* pMove = head; //此做法是让head一直是链表头结点  //判断pMove是否移动到了尾结点  while(pMove->next!=NULL) {    //判断pMove所指结点所指结点的数据的数是否小于新结点的数据,然后再判    //断pMove所指结点的尾指针所指的结点的数据是否大于新结点数据 if(pMove->data<newNode->data&&pMove->next->data>newNode->data) {      //条件成立,此下操作就跟头插法差不多      //让新结点的尾指针指向pMove所指结点的尾指针所指的结点 newNode->next = pMove->next;      //让pMove所指结点的尾指针指向新结点 pMove->next = newNode;      //插入完成,return返回 return; }    //让pMove指向下一个结点 pMove = pMove->next; }  //循环完毕后,此时pMove为链表尾结点,让pMove所指结点的尾指针指向新结点 pMove->next = newNode;}


0 4

查询结点

大家用过数组吧,其实链表的查询方法就和数组差不多,比如我想在数组里找一个数据,我会用循环来让数组里的元素和我要查找的数一一比较,直到找到为止,而链表也是如此,不过只是将数组元素换成了链表结点。

上代码!!!

void queryNode(L* head,int data){  //定义一个整形i的目的是显示其所查询的数据在第几个结点  int i=1;  //这里我就不讲了,跟上面解释一样 L* pMove = head->next; while(pMove) {    //判断数据是否匹配 if(pMove->data==data) {      //输出查找到的结点数据和它所在结点位置 printf("已找到数据:%d,它在第%d结点",pMove->data,i);      //已查找到,return返回,不让继续执行下面程序 return; }    i++; pMove = pMove->next; } //循环结束表示没有找到 printf("未找到匹配数据\n");}


0 5

修改结点数据

修改结点数据首先要确定所想要修改的结点,这里链表里只放了一个整型数据,所以只需要先在链表中找到这个数据所在的结点,然后就可以修改这个节点里的数据。

上代码!!!

void changeNode(L* head,int data){ L* pMove = head->next; while(pMove) {    //如果数据匹配 if(pMove->data==data) { printf("请输入修改后的数据:"); scanf("%d",&data);      //将此结点数据修改 pMove->data = data; printf("修改完成!\n"); return; } pMove = pMove->next; } //循环结束表示没有找到匹配结点 printf("未找到匹配结点\n");}


0 6

删除结点

话不多说,上代码!!!

void deteleNode(L* head,int data){ L* pMove = head;  //定义一个结构体指针,指向pMove所指结点的下一个节点 L* dpMove = pMove->next; while(dpMove) {    //如果结点数据匹配 if(dpMove->data==data) { //这里的操作就跟头插入差不多      //让pMove所指结点的尾指针指向dpMove所指结点的尾指针所指的结点 pMove->next = dpMove->next; //释放dpMove所指的结点内存 free(dpMove); printf("删除完成!\n"); return; } pMove = pMove->next; dpMove = pMove->next; } //循环结束表示没有找到匹配结点 printf("未找到匹配结点\n");}


0 7

输出链表

终于到输出了,赶紧弄完去打把LOL,哈哈哈哈!

上代码!!!

void printlist(L* head){  //让PMove指向头结点的下一个结点  L* pMove = head->next;  //如果PMove所指结点为NULL,则表示其链表为空 if(pMove==NULL) {    printf("链表为空\n"); return; }  //这里的头是表示头结点,它起一个引导作用 printf("头");  //当pMove不为NULL,同不为0时 while(pMove) {    //输出结点数据 printf("->%d",pMove->data);    //让pMove指向下一个结点 pMove = pMove->next; }  //结点输出完输出NULL的意义为,能更直观的看到链表的结构  printf("->NULL\n");}


0 8

销毁链表

可能有的读者会问了,为什么要销毁链表呢?我不销毁链表程序执行也没啥问题呀。
哼哼,年轻人,我只想说,你只是数据比较少而已,如果非常多的话,你的电脑非得卡死崩了不成,首先要知道啊,我们的运行内存分为代码区、全局区、栈区、堆区这四大区,代码区用来存放加载你所写的代码,全局区也称静态区,用来存放全局变量和静态变量,栈区用来存放局部变量,堆区就是malloc开辟内存的区域,前三区开辟的内存空间它们很懂事,知道自己收拾自己的烂摊子(自动释放内存),而堆区不一样,需要你自己手动去释放,如果你不去自己释放的话,它就会一直在那里,直到你电脑重启为止,这种类似的事件就比如:你手机或电脑突然卡得很,然后你重启一下,就又好了。

话不多说了,上代码!!!

尾删法:

void detelelist(L* head){ L* pMove; L* detele; while(1)  { pMove = head;    //如果下面的条件成立,表示链表为空,跳出循环 if(pMove->next==NULL) break;    //判断pMove所指结点的尾指针所指的尾指针所指的结点是否为NULL    //此做法是为了给detele留个位位好释放它所指的尾结点 while(pMove->next->next!=NULL) pMove = pMove->next;    //此时pMove所指的结点的尾指针所指的结点为尾结点    //让detele指向它!!! detele = pMove->next;    //让pMove所指的结点的尾结点指向detele所指结点的尾指针所指,即NULL pMove->next = detele->next;    //释放它!!! free(detele);    //每释放一次输出一次链表 printlist(head); }  //此时链表为空,将头结点指针置为NULL head = NULL;}

头删法:

void detelelist(L* head){ L* pMove = head->next; while(pMove) {    //让头结点的尾指针指向pMove所指结点的尾指针所指的结点 head->next = pMove->next;    //釈放する!!(释放它!!) free(pMove);    //让pMove指向下一个结点 pMove = head->next;    //每释放一个结点输出一次链表 printlist(head); } //此时链表为空,将头结点指针置为NULL head = NULL;}

好了,以上就是单链表操作的全部内容了,下面请欣赏完整代码。
#include <stdio.h>#include <stdlib.h>typedef struct list{ int data; struct list* next;}L;
L* creatorList(){ L* headlist = (L*)malloc(sizeof(L)); headlist->next = NULL; return headlist;}
L* creatorNode(int data){ L* newNode = (L*)malloc(sizeof(L)); newNode->data = data; newNode->next = NULL; return newNode;}
void insertNode(L* head,L* newNode){ L* pMove = head; while(pMove->next!=NULL) { if(pMove->data<newNode->data&&pMove->next->data>newNode->data) { newNode->next = pMove->next; pMove->next = newNode; return; } pMove = pMove->next; } pMove->next = newNode;}
void queryNode(L* head,int data){ int i=1; L* pMove = head->next; while(pMove) { if(pMove->data==data) { printf("已找到数据:%d,它在第%d结点",pMove->data,i); return; } i++; pMove = pMove->next; } printf("未找到匹配数据\n");}
void changeNode(L* head,int data){ L* pMove = head->next; while(pMove) { if(pMove->data==data) { printf("请输入修改后的数据:"); scanf("%d",&data); pMove->data = data; printf("修改完成!\n"); return; } pMove = pMove->next; } printf("未找到匹配结点\n");}
void deteleNode(L* head,int data){ L* pMove = head; L* dpMove = pMove->next; while(dpMove) { if(dpMove->data==data) { pMove->next = dpMove->next; free(dpMove); printf("删除完成!\n"); return; } pMove = pMove->next; dpMove = pMove->next; } printf("未找到匹配结点\n");}
void printlist(L* head){ L* pMove = head->next; if(pMove==NULL) { printf("链表无数据\n"); return; } printf("头"); while(pMove) { printf("->%d",pMove->data); pMove = pMove->next; } printf("->尾\n");}
void detelelist(L* head){ L* pMove = head->next; while(pMove) { head->next = pMove->next; free(pMove); pMove = head->next; printlist(head); } head = NULL;}
void main(){ L* list = creatorList(); L* newNode; int data; while(1) { printf("请输入结点数据(0为停止输入):"); scanf("%d",&data); if(data==0) break; newNode = creatorNode(data); insertNode(list,newNode); printf("输出链表:\n"); printlist(list); printf("\n"); } printf("请输入所要查找的数据:"); scanf("%d",&data); queryNode(list,data); printf("\n请输入所要修改的结点数据:"); scanf("%d",&data); changeNode(list,data); printlist(list); printf("\n请输入所要删除的结点:"); scanf("%d",&data); deteleNode(list,data); printlist(list); getchar();getchar(); printf("\n删除链表数据:\n"); detelelist(list);}


代码里插入结点函数我用了排序插入法,销毁链表函数我用了头删法,大家可以使用上面的其他方法,如果有什么问题可以问我,下次我将做一个双向链表,ok,希望大家看完之后能点一个在看、分享,作者在此多谢啦!!!


至此,所有单链表操作完结!!撒花!!