在信息管理系统的程序设计中,会用到大量的数据记录表格,如果采用结构体数组存储这些数据,会出现一些问题
1.数组必须定义固定的长度要按照可能遇到的最大数目定义数组长度,会造成空间浪费
2.在数组中插入,删除一个元素,需移动数组中的大量元素,尤其是数组长度特别大的时候需要移动端元素更多,这样会耗费大量时间,效率很低
那么,怎样解决这些问题呢?
我们可以采用动态存储分配的数据结构-——链表,
链表的特点是:用则申请,不用则释放,插入和删除知识少量操作,这丫没买过就可以大大地提高空间利用率和时间效率,要实现动态链表,必须在程序运行的过程中能根据需要来分配空间或释放空间
我们引入一个案例
编写一个包含学生信息的链表结构,并将链表中的信息进行输出
链表的结构示意图如下
在链表中,有一个头指针变量即head用这个指针变量保存一个地址,即一个变量的地址,也就是说,头指针指向一个变量,这个变量称为元素。每个元素包含两个部分,数据部分和指针部分,数据部分存放元素所包含的数据,指针部分用来只向下一个元素。最后一个元素的指针指向NULL,表示指向的地址为空
在链表中,第一个结点前虚加一个头节点,头指针指向头结点,头结点的指针域指向第一个实际有效节点,也可称首元结点.头节点的数据域可以不使用,带头结点的链表比不带头结点的链表在创建,插入,删除等操作时代码更加简洁
可以将链表想象成铁链,一环套一环,通过头指针寻找链表中的元素,
在链表中。如果每个结点只有一个指针,所有结点都是单线联系,
除了末尾结点指针为空之外,每个节点的指针都指向下一个结点,一环扣一环形成一条线性链,则称此链表为单向线性链表或单链表
单链表的特点
1. 有一个head指针变量,存放头结点的地址,称为头指针。
2. 头结点的指针域head->next,存放首元结点的地址
3. 每个结点都包含一个数据域和指针域,数据域存放实际数据,指针域存放下一个结点的地址
4. 最后一个结点不再指向其他结点,指针域为NULL,表示链表到此结束指向表尾结点的指针称为尾指针
5. 链表各结点的顺序关系由指针域next确定,链表依靠指针相连不需要真难用一片连续的内存空间
6. 随着处理数据量的增加,链表可以不受程序中变量定义的限延长
单链表的初始化
例如建立一个链表表示一个班级,结点表示学生,结点结构体定义如下:
typedef struct node
{
char name[20];
int number;
struct node *next;
}Node,*LinkList;//Node是结构体类型,LinkList 是结构体指针类型 。
//LinkList head相当于Node *head,也相当于struct node *head;
在next成员定义中,引用了本结构体类型,也就是说该类型中采用了递归
单链表初始化代码
linklist initlist()
{
linklist head;
head=(Node*)malloc(sizeof(Node));
head->next=NULL;
return head;
}
尾插法建立单链表
void creatbyyear(linklist head)
{
node *r,*s;
char name[20];
int number;
r=head;
printf("请输入学生的姓名和学号:\n");
while(1)
{
scanf("%s",name);
scanf("%d",number);
if(number==0)
{
break;
}
s=(node*)malloc(sizeof(node));
strcpy(s->name,name);
s->number=number;
r->next=s;
r=s;
}
r->next=NULL;
}
creatbyyear函数的功能是创建链表,在该函数中,首先要定义指针变量r和s,r指向当前单链表的表尾结点,s指向新创建的结点。
在while循环中,读入姓名和学号,如果学号为0,结束输入退出循环,接着用malloc函数分配内存,用s指向新分配的内存,分别赋值姓名和学号;将原来最后一个结点的指针r指向新结点(r->next=s),r也指向s;
创建完一个结点后,循环再次分配内存,输入数据,输入学号为0时退出循环,最后将链表的最后一个结点的指针域赋为NULL,表示链表创建结束。
一个单链表通过动态分配内存方式创建成功
头插法创建单链表
从空表开始重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后,直至读入结束标志为止,头插法数据读入顺序和链表的结点顺序正好相反,即data3,data2,data1;
单链表的遍历
如何遍历链表并将其中的数据进行输出呢?
引入一个函数用来输出链表中的数据output,引用一个临时指针t进行循环操作,head为头指针,
voidoutout(linklist head)
{
node*t;
t=head->next;
while(t!=NULL)
{
printf("姓名",t->name);
printf("学号",t->number);
t=t->next;
}
}
将各功能的大门整合到一起,编写一个包含学生信息的链表结构,并且将链表中的信息进行输出
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct node
{
char name[20];
int number;
struct node *next;
}Node,*linklist;
//Node是结构体类型,LinkList 是结构体指针类型 。
//LinkList head相当于Node *head,也相当于struct node *head;
linklist initlist()
{
linklist head;
head=(Node*)malloc(sizeof(Node));
head->next=NULL;
return head;
}
void creatbyrear(linklist head)
{
node *r,*s;
char name[20];
int number;
r=head;
printf("请输入学生的姓名和学号:\n");
while(1)
{
scanf("%s",name);
scanf("%d",&number);
if(number==0)
{
break;
}
s=(node*)malloc(sizeof(node));
strcpy(s->name,name);
s->number=number;
r->next=s;
r=s;
}
r->next=NULL;
}
void creatbyhead(linklist head) //头插法建立链表
{
node *s;
char name[20];
int number;
printf("请输入学生姓名和学号:\n");
while(1)
{
scanf("%d",number);
if(number==0)
{
break;
}
s=(node*)malloc(sizeof(node));
strcpy(s->name,name);
s->number=number;
s->next=head->next;
head->next=s;
}
}
void output(linklist head)
{
node*t;
t=head->next;
while(t!=NULL)
{
printf("姓名:%s\n",t->name);
printf("学号:%d\n\n",t->number);
t=t->next;
}
}
int main()
{
linklist ha,hb; //定义单链表头指针
ha=initlist(); //初始化单链表
creatbyrear(ha) ;
output(ha);
hb=initlist();
creatbyhead(hb);
output(hb);
return 0;
}
在main函数中,定义了头指针ha,然后调用initlist函数初始化单链表,并将链表的头结点返回给ha 指针变量再利用得到的头指针作为creatbyrear的参数,尾插法创建单链表,最后调用output输出ha链表中各结点的信息,hb头指针同理
单链表的插入
链表的插入可以在链表的头指针位置进行插入,也可以在链表某个结点处进行插入但操作的思想相同以在指定位置进行插入为例,
插入结点的过程就像手拉手的小朋友连成一条线,这是来了一个新朋友,他要站在第二个和第三个小朋友之间,那么就需要第二个小朋友放开第三个小朋友的手,拉上新小朋友的手,新小朋友的手蜡烛第三个小朋友的手,重新连成一条线并且新加入的小朋友变成了这条链上的第三个
设计一个函数,在单链表的第i个位置插入新结点
首先从链表的头结点开始,找到链表的第i-1个结点的地址t,若该结点存在,可以在第i-1个结点后面插入第i个结点,为插入的新结点分配内存,然后想新结点输入数据。插入时,首先将新结点的指针s指向原来第i个结点(s->next=t->next),然后将第i-1个结点指向s(t->next=s),这样就完成了插入结点的操作
void insert(linklist head,int i)
{
node *t=head,*s;
int j=0;
while(j<i-1&&t!=NULL)
{
t=t->next;
j++;
}
if(t)
{
printf("输入待添加学生的姓名和学号:\n");
s=(node*)malloc(sizeof(node));
scanf("%s",s->name);
scanf("%d",&s->number);
s->next=t->next;
t->next=s
}
}
main函数
int main()
{
linklist ha; //定义单链表头指针
ha=initlist(); //初始化单链表
creatbyrear(ha) ;
output(ha);
insert(ha,3);
output(ha);
return 0;
}
请输入学生的姓名和学号:
zz 1
xx 2
cc 3
vv 4
bb 0
姓名:zz
学号:1
姓名:xx
学号:2
姓名:cc
学号:3
姓名:vv
学号:4
输入待添加学生的姓名和学号:
nn 3
姓名:zz
学号:1
姓名:xx
学号:2
姓名:nn
学号:3
姓名:cc
学号:3
姓名:vv
学号:4
Press any key to continue
输出结果如上图
单链表的删除
delete函数中有两个参数,head表示链表的头指针,pos表示要删除节点在链表中的位置定义整型变量j来控制循环次数,定义指针变量p表示该结点之前的结点。接着利用循环找到要删除的结点之前的结点p;如果该结点存在并且删除结点存在,则将指针变量q指向待删除结点(q=p->next)
然后(p->next=q->next),free(q);
代码
void Delete(linklist head,int pos)
{
node *p=head,*q;
int j=0;
printf("删除第%d个学生\n",pos);
while(j<pos-1&&p)
{
p=p->next;
j++;
}
if(p==NULL||p->next==NULL)
printf("the pos is error");
else
{
q=p->next;
p->next=q->next;
free(q);
}
}
int main()
{
linklist ha; //定义单链表头指针
ha=initlist(); //初始化单链表
creatbyrear(ha) ;
output(ha);
Delete(ha,3);
output(ha);
return 0;
}
结果如下
学号:1
姓名:x
学号:2
姓名:c
学号:3
姓名:v
学号:4
删除第3个学生
姓名:z
学号:1
姓名:x
学号:2
姓名:v
学号:4
Press any key to continue
单链表的查询
search函数有两个参数,head表示链表的头指针,name表示要查找的值,定义指针变量p,使其从首元结点开始到链表结束,如果某节点的成员值和给定不等,则继续查找下一个结点p=p->next;
如果查找成功,则返回结点的地址值,若查找失败,则打印提示信息,并返回NULL;
代码如下
node *search(linklist head,char name[])
{
node*p=head->next;
while(p)
{
if(strcmp(p->name,name)!=0)
p=p->next;
else
break;
}
if(p==NULL)
printf("没有找到值为%s的结点",name);
return p;
}
int main()
{
linklist ha;
node *p;//定义单链表头指针
ha=initlist(); //初始化单链表
creatbyrear(ha) ;
p=search(ha,"李四");
printf("\n查找到信息如下\n");
printf("姓名:%s\n",p->name);
printf("学号:%d\n",p->number);
return 0;
}
结果如下
请输入学生的姓名和学号:
张三 1
李四 2
汪汪 3
麻麻 4
慢慢 0
查找到信息如下
姓名:李四
学号:2
Press any key to continue