假设以学生信息结构体作为链表节点,其中结构体成员包括三个元素,学号,成绩和一个结构体指针。前两个成员是节点存储的本身内容,而最后一个成员,定义为指向结构体的指针,是用来指向下一个节点的,因为只有指向下一个节点,才可以连成一串,形成所谓的链表。总的来说,链表中的每一个节点,应该包括两部分,即数据+指向下一节点的指针。
结构体如下:
struct student
{long num;
float score;
struct student * next;};//最后一个成员就是指向下一个节点的指针
在编程前,我们必须要有一个整体的框架思路才能写出代码,所以,先走一遍逻辑思路吧!
1.建立链表
建立链表是最简单的部分,其中主要重要一个void * malloc(unsigned int size)函数,用来为节点开辟一个容量为size的区域。建立表的时候可以不断的去malloc,则不断的增加节点。片段代码如下:
#include <stdio.h>
#include <malloc.h>
#define len sizeof(struct student)
#define NULL 0
struct student
{
long int num;
float score;
struct student * next;
};
int n; //定义全局n,表示节点数
/*****创建链表子函数开始*******/
struct student * creat(void)
{
n=0;
struct student *p1,*p2,*head;
head=NULL;//初始化头结点指针,使其指向空,表示当前链表没有节点
p1=p2=(struct student *)malloc(len);//开辟一个新节点区域,使得P1P2均指向该区域的首地址
scanf("%ld,%f",&p1->num,&p1->score);
while((p1->num)!=0)
{
n=n+1;//节点数加1
if(n==1)
head=p1;
else
p2->next=p1;//使得p2的next指针指向新区域的首地址(因为此时p1已经是新区域的首地址了)
p2=p1; //下面p1就要指向新区域了,临走之前必须让p2来记录一下自己
p1=(struct student *)malloc(len);//在while循环中不断的创造新节点
scanf("%ld,%f",&p1->num,&p1->score);
}
p2->next=NULL; //尾部指针为空,说明是表尾
return(head);
}
可能对上面定义的三个指针有疑问.即head,*p1,*p2,下面分别介绍其作用:
作为一个链表必须要有头指针,即指向链表的首地址,它就是head,而为了访问下一结点,必须要另一个指针去移动,它就是p1,有人可能会说,那么定义这两个指针似乎不就够了么,还定义p3干什么。其实不然,试想想,p1是访问下一结点的,它会不断的往后走,去访问满足条件的节点(比如要插入一个节点,那么p1肯定要去找寻比这个要插入节点大,和小的值,从而找到具体的位置进行插入),而在这个过程中,并没有谁去记录p1走过的路程。例如,现在要插入一个新节点p0,p1去找呀找,p1找到了第二个节点,发现第二个节点比p0里的值小,于是它就朝向下一个节点,咦,发现第三个节点的值比p0所指值大(这里默认排序是由小到大),那么p1就找到了p0应该插入的地方,这时就可以让p1(第三个节点)去和p0(要插入的节点)连接。但是问题出来了,p0还需要和左边的第二个节点连接呀,这时候没有哪个指针是指向第二个节点的(更不可能让p1去退一步),这就需要p2去时刻跟在p1后面去记录它。
举个最通俗的例子,如果有10个小朋友手拉手按身高由小到大站在一起,现在来了一个新同学,他的个子经过比对,应该插入到第二个同学和第三个同学之间。那么,应该让第二个和第三个同学的手松开,然后第二个同学拉住新来的同学,第三个同学也拉着新来的同学,这样才可以在不影响队伍的情况下重新组建链表。这里head指向第一个同学,p1是去寻找满足条件的节点,它指向(找到)的是第三个同学,而p2指向的是第二个同学,这样新来的同学就被"夹在"p2和p1之间了。
2.输出链表
这比建表还容易么不是,只需要让p1指向已经建立好的链表的头结点,然后不断的后移,输入每一个节点指向的单元内容即可。
void print(struct student * head)
{
struct student * p;
printf("现在这%d个记录是:\n",n);
p=head;
if(head!=NULL)
{
do
{
printf("%ld%5.1f\n",p->num,p->score);
p=p->next;
} while (p!=NULL);
}
}
3.删除节点
有必要说一下啊,这里程序默认的是链表按学号由小到大排列,所以进行后移的条件就是进行学号值的比较。并且如果学号为0,则退出对应的循环。
有几个特殊情况提前分析一下:
1.如果要删除的节点是头结点,则只需要将head指针指向第二个节点即可。
2.如果删除的节点是最后一个节点,则只需要让倒数第二个节点的next指针为空。
3.其余情况均为,将p2的next指针指向p1的next指针所指节点(即跳过那个要删除的节点)。
代码片段如下:
struct student * del(struct student * head,long number)
{
struct student * p1,* p2;
if (head==NULL)
{ printf("\n It is empty!");goto end;}
p1=head;
//当所输入的number(学号)不等于p1所指节点的num成员,且p1的下一个节点不为空
while(number!=p1->num && p1->next!=NULL)
{
p2=p1; //p2为已经检查过的节点的定位,随着p1的后移,p2也在后移,但p2始终在p1前面一个位置
p1=p1->next;//p1后移
}
if(number==p1->num)//找到了
{
//分两种情况
//当p1指向头节点的时候
if (p1==head)
{
head=p1->next;//删除第一个节点(特殊情况)
}
else
{
p2->next=p1->next;//正常情况下的删除
}
printf("已删除:%ld\n",number);
n=n-1;
}
else
{
printf("没有找到%ld节点!",number);
}
end:
return(head);
4.插入节点
由于默认插入的顺序是按学号由小到大,则需要将插入节点单元里的学号与当前每个节点里的学号相比较,找到具体的位置进行插入:
1.若插入节点在最前面,则head指向插入节点(即待插入节点变为头结点),插入节点的next指针指向原链表的第一个节点。
2.若待插入节点在最后一个,则p1(p1此时肯定指向的是原链表的最后一个节点)的next指针指向待插入节点,而待插入节点的next指针为NULL,表示表尾。
3.除了上面两种特殊情况外,正常情况下应该是p2的next指针指向待插入节点,而待插入节点的next指针指向p1。
代码片段如下:
struct student *insert(struct student * head,struct student * stud)
{
struct student *p1,*p2,*p0;
p1=head;//p1指向头结点开始寻找
p0=stud;//p0指向待插入节点
if (head==NULL)
{
head=p0;p0->next=NULL;//空表,只返回待插入节点
}
else
{
while((p0->num>p1->num) && (p1->next!=NULL))//按默认的由小到大的顺序去寻找待插入点的位置
{
p2=p1; //p1在后移之前p2去记录它的位置
p1=p1->next;//p1后移
}
//在while循环中,其实已经找到了待插入节点的临界位置,也就是说p0肯定是大于p1,而p1此时在后移一次就该大于p0了,
//所以赶紧让p2记录p1,p1后移,这样p0就夹在p2和p1之间了
if (p0->num<=p1->num)//跨越了上面的临界位置,找到了p0的位置
{
if (head==p1)
{
head=p0;p0->next=p1;//p1指向头结点(特殊情况)
}
else //(正常情况)
{
p2->next=p0;
p0->next=p1;
}
}
else //找不到比p0小的p1,说明p0的位置在最后一个
{
p1->next=p0;p0->next=NULL;//尾节点(特殊情况)
}
n=n+1; //节点加1
return(head); //返回链表头地址
}
}
5.链表综合操作汇总,全代码如下:
#include <stdio.h>
#include <malloc.h>
#define len sizeof(struct student)
#define NULL 0
struct student
{
long int num;
float score;
struct student * next;
};
int n; //定义全局n,表示节点数
/*****创建链表子函数开始*******/
struct student * creat(void)
{
n=0;
struct student *p1,*p2,*head;
head=NULL;//初始化头结点指针,使其指向空,表示当前链表没有节点
p1=p2=(struct student *)malloc(len);//开辟一个新节点区域,使得P1P2均指向该区域的首地址
scanf("%ld,%f",&p1->num,&p1->score);
while((p1->num)!=0)
{
n=n+1;//节点数加1
if(n==1)
head=p1;
else
p2->next=p1;//使得p2的next指针指向新区域的首地址(因为此时p1已经是新区域的首地址了)
p2=p1; //下面p1就要指向新区域了,临走之前必须让p2来记录一下自己
p1=(struct student *)malloc(len);//在while循环中不断的创造新节点
scanf("%ld,%f",&p1->num,&p1->score);
}
p2->next=NULL; //尾部指针为空,说明是表尾
return(head);
}
/***********输出链表函数**********************/
void print(struct student * head)
{
struct student * p;
printf("现在这%d个记录是:\n",n);
p=head;
if(head!=NULL)
{
do
{
printf("%ld%5.1f\n",p->num,p->score);
p=p->next;
} while (p!=NULL);
}
}
/********删除节点函数开始*******************/
struct student * del(struct student * head,long number)
{
struct student * p1,* p2;
if (head==NULL)
{ printf("\n It is empty!");goto end;}
p1=head;
//当所输入的number(学号)不等于p1所指节点的num成员,且p1的下一个节点不为空
while(number!=p1->num && p1->next!=NULL)
{
p2=p1; //p2为已经检查过的节点的定位,随着p1的后移,p2也在后移,但p2始终在p1前面一个位置
p1=p1->next;//p1后移
}
if(number==p1->num)//找到了
{
//分两种情况
//当p1指向头节点的时候
if (p1==head)
{
head=p1->next;//删除第一个节点(特殊情况)
}
else
{
p2->next=p1->next;//正常情况下的删除
}
printf("已删除:%ld\n",number);
n=n-1;
}
else
{
printf("没有找到%ld节点!",number);
}
end:
return(head);
}
/********插入节点函数开始**************/
struct student *insert(struct student * head,struct student * stud)
{
struct student *p1,*p2,*p0;
p1=head;//p1指向头结点开始寻找
p0=stud;//p0指向待插入节点
if (head==NULL)
{
head=p0;p0->next=NULL;//空表,只返回待插入节点
}
else
{
while((p0->num>p1->num) && (p1->next!=NULL))//按默认的由小到大的顺序去寻找待插入点的位置
{
p2=p1; //p1在后移之前p2去记录它的位置
p1=p1->next;//p1后移
}
//在while循环中,其实已经找到了待插入节点的临界位置,也就是说p0肯定是大于p1,而p1此时在后移一次就该大于p0了,
//所以赶紧让p2记录p1,p1后移,这样p0就夹在p2和p1之间了
if (p0->num<=p1->num)//跨越了上面的临界位置,找到了p0的位置
{
if (head==p1)
{
head=p0;p0->next=p1;//p1指向头结点(特殊情况)
}
else //(正常情况)
{
p2->next=p0;
p0->next=p1;
}
}
else //找不到比p0小的p1,说明p0的位置在最后一个
{
p1->next=p0;p0->next=NULL;//尾节点(特殊情况)
}
n=n+1; //节点加1
return(head); //返回链表头地址
}
}
/*************主函数开始***********************/
void main()
{
struct student * head,* stu;
long del_num;
printf("输入记录:\n");
head=creat(); //建立链表,返回头指针
print(head); //输出全部节点
printf("\n输入要删除的节点号:");
scanf("%ld",&del_num);
while(del_num!=0) //只要输入的节点号不是0,就可以一直进行删除节点操作
{ head=del(head,del_num); //返回删除节点后链表的头指针
print(head);
printf("输入继续删除的节点,注意:\n若号码为0则结束删除功能:");
scanf("%ld",&del_num);
}
printf("\n输入要插入的节点:");
stu=(struct student *)malloc(len); //要插入节点必须要开辟新空间,
scanf("%ld,%f",&stu->num,&stu->score); //并且进行赋值
while (stu->num!=0)
{
head=insert(head,stu); //只要输入的节点号不为0,就可以一直进行插入节点操作
print(head); //并且执行的时候还是要不断进行空间的开辟
printf("输入要插入的节点:");
stu=(struct student *)malloc(len);
scanf("%ld,%f",&stu->num,&stu->score);
}
}