如果学习C语言数据结构,那么第一步接触的应该就是链表,
可是如果想要去真正弄懂并且熟练掌握它也并不是一件容易的事,而且在实现对链表的基本操作:创建、插入、遍历、搜索、删除的时候可能会迎来一个新的问题:头结点(Head Node),对头结点的不同处理方法也决定着后面实现基本操作的方法不同。
这个时候出现错误的频率相较以前就会有大幅度增加,但是我们唯一能做的就是静下心来多去分析,我们要相信自己,没有解决不了的问题。
****************************************************************************************************************************************
接触链表前,我们应该明白为什么会出现链表?到底什么是链表呢?
- 首先,我们应该都知道数组,数组作为存放同类数据的集合,在我们进行程序设计时带来了很多的方便。但是数组也同样存在一些问题,比如数组的大小在定义时要事先规定,不能在程序中进行调整,这样以来,在程序设计中针对不同问题有时需要30个大小的数组,有时需要300个数组的大小, 难以统一。我们只能够根据可能的最大需求来定义数组,这就会造成一定存储空间的浪费。
- 那么我们能不能构造一种动态的数组,然后我们就可以随时调整数组的大小,以满足不同问题的需要呢?于是链表这种数据结构便应运而生。链表简单理解就是动态数组,它在程序的执行过程中根据是否需要有数据存储然后再向系统申请存储空间,这就极大程度上的避免了空间的浪费 。
- 那么如何实现根据程序的需要动态分配存储空间呢?如果要实现根据程序的需要动态分配存储空间,就必须用到以下几个函数
- malloc函数:
malloc函数的原型为:
void *malloc (unsigned int size)
其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作,以免造成不必要的错误。下面给出一个动态分配存储空间的示例:#include <stdio.h> #include <stdlib.h> int main() { int count,*array; if((array=(int *) malloc(10*sizeof(int)))==NULL) { printf("不能成功分配存储空间。"); exit(0); } for (count=0;count<=10;count++) /*给数组赋值*/ { array[count]=count; } for(count=0;count<=10;count++) /*打印数组元素*/ { printf("%2d",array[count]); } }
这里如果将" if((array=(int *) malloc(10*sizeof(int)))==NULL)" 一句中的10改为100000000000000,就会出现内存分配失败的情况,那么这句代码有什么意思呢?下面给出解释:
1)分配10个整型的连续存储空间(省掉数字就默认分配一个哦),并返回一个指向其起始地址的整型指针
2)把此整型指针地址赋给array
3)检测返回值是否为NULL -
free函数:
由于内存区域总是有限的,不能不限制地分配下去,而且一个程序要尽量节省资源,所以当所分配的内存区域不用时,就要释放它,以便其它的变量或者程序使用。这时我们就要用到free函数。
其函数原型是:
void free(void *p):
作用是释放指针p所指向的内存区。
但是这里需要注意的是这里的参数p必须是先前调用malloc函数或calloc函数(另一个动态分配存储区域的函数,这里就不再做过多说明了,以免说多了不知道用哪个了~)时返回的指针。给free函数传递其它的值很可能造成不可预料的眼中后果。
- malloc函数:
单链表的建立:
- 首先说明一下链表中的节点,节点实际上就是一个结构体,这个结构体包含了两部分:
1、数据域:用来存储本身数据
2、链域或称为指针域:用来存储下一个结点地址或者说指向其直接连接的指针。
3、下面举个栗子(这里就简单包含了节点的两部分):typedef struct node { char name[20]; struct node *link; }stu;
- 接下来直接由程序进行分析:
- 首先是新建一个链表:
stu * creat(int n) //n是要创建的节点的个数 { stu *h,*p,*s; //h指向头节点,s指向当前节点,p指向当前节点的前一个节点 int i; if(!(h=(stu *)malloc(sizeof(stu)))) //在头节点的后方申请一个结构体大小的动态空间 { printf("Memory allocate error!"); exit(0); //直接退出程序,返回0 } h->name[0]='\0'; h->link=NULL; //这两句是将头结点空置,程序外解释 p=h; //p指针也指向该头结点 printf("Please input the student's name and score (seprated by blank):\n"); for(i=0;i<N;i++) { if(!(s=(stu *)malloc(sizeof(stu)))) { printf("Memory allocate error!"); exit(0); } p->link=s; //像刚刚第一的意义一样,将二者前后的关系连接起来 scanf("%s %d",s->name,&s->score); s->link=NULL; //一次循环后将最后一个节点的指针域指空 p=s; //程序外解释 } return (h); }
- 首先在程序中为什么要将链表的头结点空置呢?
- 我们可以先想象一下链表删除时的操作,如果头结点没有空置,而是直接将头指针直接指向第一个存放数据的地方,那么如果当删除第一个节点时,我们可能也会首先想到这样不就可以只把头指针指向第二个节点就可以了嘛,似乎更加简单了,但是事实不然,这样做便会产生第一个数据没法删除(操作)的情况(具体我还没有解决怎样处理这个问题),不相信的小伙伴们可以自己写一遍,我的前几篇博客中也写到这个问题(Q点我即达Q,但不提供复制功能,哈哈~~),你也会尝试到那种无奈的感觉。但是如果第一个节点不存放数据这个问题就会迎刃而解。
- 然后就是"p=s"这个地方,下面这张图应该可以很好地帮助理解(s相当于p1,p相当于图中的p2,然后p1就会在循环后再次指向新开辟的空间),通过指针的移动可以不断地在后方生成新空间添加新数据:
- 首先是新建一个链表:
- 然后为了方便可以首先写一个输出函数,方便检查每一步是否有错:
void print(stu *head) { stu *p; p=head->link; printf("\nName\t\tScore\t\t\n"); while(p) { printf("%s\t\t%d\n",p->name,p->score); p=p->link; } }
-
接下来是搜索部分,这里写了三个函数,第一个的作用是简单的按照姓名查找,然后返回这点的指针;第三个函数的功能是按照分数来查询学生信息;第二个函数功能与第一个类似,在链表的删除操作中可以用它来寻找一个节点的前一个节点指针:
stu * search_name(stu *head,char *x) /*两个参数一个是头结点指针一个是姓名,因为姓名使用的是字符串数组,所以这里传入的数组的首地址,下面参数同理*/ { stu *s; char *y; s=head->link; while(s) { y=s->name; if(!strcmp(x,y)) { return s; } else s=s->link; } if(!s) { printf("Search error!\n"); } } stu * search_name2(stu *head,char *x) { stu *s,*p; char *y; s=head->link; p=head; while(s) { y=s->name; if(!strcmp(x,y)) { return p; } else { p=p->link; s=s->link; } } if(!s) { printf("Search Error!\n"); } } stu * search_score(stu *head,int x) { stu *s; int y; s=head->link; while(s) { y=s->score; if(y==x) { return s; } else s=s->link; } if(!s) { printf("Search error!\n"); } }
-
然后是链表的插入,放上一张过程图辅助理解(函数包含三个,分别是头插、尾插与指定位置插入(这里需要借助上面的搜索函数来起到位置定位功能)):
void insert_head(stu *head)
{
char stuname[20];
int score;
stu *s,*p;
p=head->link;
if(!(s=(stu *)malloc(sizeof(stu))))
{
printf("Memory allocate error!");
exit(0);
}
printf("Please input student's name and score that you want to add:\n");
scanf("%s %d",stuname,&s->score);
strcpy(s->name,stuname);
s->link=p;
head->link=s;
}
void insert_tail(stu *head)
{
char stuname[20];
int score;
stu *s,*p;
p=head->link;
do
{
p=p->link;
}while(!p);
printf("Please input student's name and score that you want to add:\n");
scanf("%s %d",stuname,&s->score);
strcpy(s->name,stuname);
s->link=p->link;
p->link=s;
}
void insert_appoint(stu *p) //Ensuring this parameter p can by Search function
{
char stuname[20];
int score;
stu *s;
if(!(s=(stu *)malloc(sizeof(stu))))
{
printf("Memory allocate error!");
exit(0);
}
printf("Please input student's name and score that you want to add:\n");
scanf("%s %d",stuname,&s->score);
strcpy(s->name,stuname);
s->link=p->link;
p->link=s;
}
- 最后是删除功能,这里将要额外定义一个新指针用于存放将要删除的节点指针,然后通过free函数将其在该进程释放掉,否则将一直占用着内存空间,这里因为只有一个可能影响效果不明显,但是要养成一个及时释放的好习惯。
void del(stu *x,stu *y) //y是需要删除的节点指针,x是其前一个节点指针 { stu *s; //新建 s=y; //赋值 x->link=y->link; //重定向 free(s); //释放 }
-
链表的基本操作创建、插入、删除、查询、遍历(输出)到这里就全部介绍完了。
-
然后还可以将其封装为静态库,但是就要再次对这些代码进行修改以优化它的通用性,具体可以参考我的前几篇博客,我在那里介绍了Linux下静态库的封装。Q点我即达Q
-
最后附上整个程序,因为没有添加菜单功能,所以可能更适合分析使用(注释已经省掉,因为在不同的编译器下可能会因为汉字的编码不同而产生问题):
#include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <string.h> #define N 3 typedef struct node { char name[20]; int score; struct node * link; }stu; stu * creat(int n) { stu *h,*p,*s; int i; if(!(h=(stu *)malloc(sizeof(stu)))) { printf("Memory allocate error!"); exit(0); } h->name[0]='\0'; h->link=NULL; p=h; printf("Please input the student's name and score (seprated by blank):\n"); for(i=0;i<N;i++) { if(!(s=(stu *)malloc(sizeof(stu)))) { printf("Memory allocate error!"); exit(0); } p->link=s; scanf("%s %d",s->name,&s->score); s->link=NULL; p=s; } return (h); } stu * search_name(stu *head,char *x) { stu *s; char *y; s=head->link; while(s) { y=s->name; if(!strcmp(x,y)) { return s; } else s=s->link; } if(!s) { printf("Search error!\n"); } } stu * search_name2(stu *head,char *x) { stu *s,*p; char *y; s=head->link; p=head; while(s) { y=s->name; if(!strcmp(x,y)) { return p; } else { p=p->link; s=s->link; } } if(!s) { printf("Search Error!\n"); } } stu * search_score(stu *head,int x) { stu *s; int y; s=head->link; while(s) { y=s->score; if(y==x) { return s; } else s=s->link; } if(!s) { printf("Search error!\n"); } } void insert_head(stu *head) { char stuname[20]; int score; stu *s,*p; p=head->link; if(!(s=(stu *)malloc(sizeof(stu)))) { printf("Memory allocate error!"); exit(0); } printf("Please input student's name and score that you want to add:\n"); scanf("%s %d",stuname,&s->score); strcpy(s->name,stuname); s->link=p; head->link=s; } void insert_tail(stu *head) { char stuname[20]; int score; stu *s,*p; p=head->link; do { p=p->link; }while(!p); printf("Please input student's name and score that you want to add:\n"); scanf("%s %d",stuname,&s->score); strcpy(s->name,stuname); s->link=p->link; p->link=s; } void insert_appoint(stu *p) //Ensuring this parameter p can by Search function { char stuname[20]; int score; stu *s; if(!(s=(stu *)malloc(sizeof(stu)))) { printf("Memory allocate error!"); exit(0); } printf("Please input student's name and score that you want to add:\n"); scanf("%s %d",stuname,&s->score); strcpy(s->name,stuname); s->link=p->link; p->link=s; } void print(stu *head) { stu *p; p=head->link; printf("\nName\t\tScore\t\t\n"); while(p) { printf("%s\t\t%d\n",p->name,p->score); p=p->link; } } void del(stu *x,stu *y) { stu *s; s=y; x->link=y->link; free(s); } int main() { stu *head,*p,*p1; int number=N; char name[20]; int score; system("title Linked list"); head=creat(number); printf("\nPlease input the name for search:"); scanf("%s",name); p=search_name(head,name); printf("Name\tScore\t\n"); printf("%s\t%d\n",p->name,p->score); printf("\nPlease input the score for search:"); scanf("%d",&score); p=search_score(head,score); printf("Name\tScore\t\n"); printf("%s\t%d\n",p->name,p->score); printf("\nAdd(head):\n"); insert_head(head); print(head); printf("\nAdd(tail):\n"); insert_tail(head); print(head); printf("\nAdd(Appoint):\n"); printf("Please input a name , new data will be after it:\n"); scanf("%s",name); p=search_name(head,name); insert_appoint(p); print(head); printf("\nPlease input the name to delete:\n"); scanf("%s",name); p=search_name(head,name); p1=search_name2(head,name); del(p1,p); print(head); }
****************************************************************************************************************************************
这并不是就结束了哟,链表可并不仅仅就是单链表,还有循环链表、双向链表等一系列的数据结构在后面等着我们呢。
当然首先必须先掌握单链表吧~
没看懂的小伙伴可以多看几遍或留言疑惑哟,我会尽力解答的~
****************************************************************************************************************************************
最快的脚步不是跨越,而是继续,最慢的步伐不是小步,而是徘徊。
****************************************************************************************************************************************