写在最前
阅读这篇文章需要什么知识储备:
结构体、指针的一些知识、malloc
涉及开发环境:CodeBlocks 纯C
1、什么是链表
首先聊聊数组,数组是同类型的一组数据的集合,但首先要定义数组的大小,比如:
int num[10];
这就定义了一个int类型的数组num,里面可以存放10个int的数据,根据数组下标进行操作。
这时会出现一个问题,数组大小不够或者是浪费?这时就出现了一种新的数据存储方式,链表,链表的一个个节点是一个结构体,可以把结构体想象成一个一个点,里面有一根线引出来,为了和下一个点建立联系。因为这个点是一个单独的东西,所以各个点之间的地址不一定是连续的,而数组的地址一定是连续的,这个充分利用了内存资源,没有必要提前划分出一大块区域给数组用。这是链表的一大优势。
下面看结构体定义,举一个简单例子:
typedef struct Node{
int year;
int month;
int day;
struct Node *next;
}Node,*pNode;
Node相当于给这个结构体取了名字,同样的 *pNode 这个整体也是给结构体取了个名字,但注意,pNode就是一个指向结构体的指针了。结构体里面的那根线就是 *struct Node next,next也是一个指向 struct Node 的指针,也就是指向结构体的指针。好,点 和 线 都有了,下面开始串起来。
2、第一个链表
直接上最难的串法:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node{
int year;
int month;
int day;
struct Node *next;
}Node,*pNode;
void create(pNode phead){
pNode ptail = phead;//跟踪整个链表的最后一个节点
while(1){
pNode pnew = (pNode)malloc(sizeof(Node));//新申请一块内存,也就是一个新的“点”
scanf("%d",&pnew->year);//这里输入数据
if(pnew->year == -1)
break;
scanf("%d",&pnew->month);
scanf("%d",&pnew->day);
//开始串起来
ptail->next = pnew;
ptail = pnew;
ptail->next = NULL;
}
}
void traverse(pNode phead){
pNode p = phead->next;
while(p!=NULL){
printf("%d-%d-%d\n",p->year,p->month,p->day);
p = p->next;
}
}
int main()
{
pNode phead = (pNode)malloc(sizeof(Node));
phead->next = NULL;
create(phead);
traverse(phead);
return 0;
}
我们约定链表的头节点不存储数据,只做标识作用,用来找到整个链表。
所以这个头节点一定不能改变,这里输入三个数代表年月日,如果年输入-1表示不继续创建节点。
每次准备输入新数据时,一定要重新malloc一块新的Node地址出来。
然后把数据输入进去,输入完成之后,这个点已经只做好了,因为ptail一直是最后一个点(先这个认为,后面会解释怎么做到的),所以链的时候只需ptail->next=pnew,即可,注意,这里还没完,说好的ptail是最后一个节点,所以这里还要ptail=pnew,这样ptail才真的是最后一个,那一开始的时候呢?ptail=phead,这里ptail也是最后一个节点啦!所以,这里的ptail始终指向的是最后一个节点。这里还需要注意!!patil->next=NULL;这一步操作是必须的,后面比如遍历的时候是根据当前节点是否是NULL来决定的,如果不手动把patil的next赋值NULL,会出现一个意想不到的错误!!这样整个流程其实已经走完了,phead始终没有变,只是后面多链了一些东西。
3、链表的其他操作
3.1增加链表(头插、尾插)
尾插:其实前面的创建链表的方式就是尾插,就是新的节点一直插在最后面。
头插:就是新的节点在最前面,这种插法简单,但是顺序反了,根据实际需要选择头插和尾插。
void create1(pNode phead){
while(1){
pNode pnew = (pNode)malloc(sizeof(Node));
scanf("%d",&pnew->year);//这里输入数据
if(pnew->year == -1)
break;
scanf("%d",&pnew->month);
scanf("%d",&pnew->day);
//开始串起来
pnew->next = phead->next;
phead->next = pnew;
}
}
注意这里的插法不一样了,来的新节点应该插在phead的后面(因为phead不存储数据,所以它的下一个就是第一个节点了)。
首先pnew->next = phead->next
找到第一个然后赋值给pnew的下一个,这样pnew和大部队已经建立联系,但还没和phead联系上。再加上phead->next = pnew,注意这两步赋值操作顺序不能反,可以仔细思考如果反过来还能和它们建立联系吗?
这里需要注意的是生成phead的时候要手动phead->next = NULL,给末尾一个NULL的标识。
3.2删除链表
void delete(pNode phead){
//简化操作,根据 年 来删除节点
pNode p = phead->next;
pNode pre = phead;
int year;
printf("输入年来删除\n");
scanf("%d",&year);
while(p != NULL){
if(p->year == year){
pre->next = p->next;
break;
}
pre = p;
p = p->next;
}
}
这里需要注意的是,我们一直判断当前的这个节点的年份是不是我们想要删除的那个(删除条件)如果是就删除,可是找不到前面的节点了(哭!如果你说从头遍历,判断下一个是不是要删除的节点,那。。。很“聪明”!)
所以我们需要随时记录前一个节点是什么,所有有pre临时变量。当然这里要规范,因为有数据的是phead->next,所以肯定从它开始找,这时它的前一个是phead,所以pre赋初始值是phead,如果删除的是第一个就有作用了!!!
最后附上完整代码吧:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node{
int year;
int month;
int day;
struct Node *next;
}Node,*pNode;
void create(pNode phead){
pNode ptail = phead;//跟踪整个链表的最后一个节点
while(1){
pNode pnew = (pNode)malloc(sizeof(Node));//新申请一块内存,也就是一个新的“点”
scanf("%d",&pnew->year);//这里输入数据
if(pnew->year == -1)
break;
scanf("%d",&pnew->month);
scanf("%d",&pnew->day);
//开始串起来
ptail->next = pnew;
ptail = pnew;
ptail->next = NULL;
}
}
void create1(pNode phead){
while(1){
pNode pnew = (pNode)malloc(sizeof(Node));
scanf("%d",&pnew->year);//这里输入数据
if(pnew->year == -1)
break;
scanf("%d",&pnew->month);
scanf("%d",&pnew->day);
//开始串起来
pnew->next = phead->next;
phead->next = pnew;
}
}
void traverse(pNode phead){
pNode p = phead->next;
while(p!=NULL){
printf("%d-%d-%d\n",p->year,p->month,p->day);
p = p->next;
}
}
void delete(pNode phead){
//简化操作,根据 年 来删除节点
pNode p = phead->next;
pNode pre = phead;
int year;
printf("输入年来删除\n");
scanf("%d",&year);
while(p != NULL){
if(p->year == year){
pre->next = p->next;
break;
}
pre = p;
p = p->next;
}
}
int main()
{
pNode phead = (pNode)malloc(sizeof(Node));
phead->next = NULL;
create1(phead);
traverse(phead);
delete(phead);
return 0;
}