链表结构
- 逻辑结构:线性表
- 存储结构:链式存储
- 基本运算:
1. 初始化
2. 查找节点
3. 插入节点
4. 删除节点
5. 创建单链表
准备数据
其中,节点包含两部分的内容:
数据域: 保存该节点的实际数据。
指针域: 保存的是下一节点的地址。
struct Student //学生结构
{
char Name[10]; //姓名
int No; //学号
float Score[3]; //三科的成绩
float Total; //总分
float Ave; //平均分
};
typedef struct Node //结点
{
struct Student st; //数据域
struct Node *next; //指针域
}NODE, *PNODE; //NODE等价于struct Student st||PNODE等价于struct Node *next
//NODE * P==PNODE P。为了提高可读性用NODE定义指向其他任意节点。用PNODE专指向头结点
初始化
初始化一个头结点,它不存储学生信息,也可以不存任何信息,也可以存附加信息,如存表长。它的指针域指向第一个节点。之所以引入头结点,就是为了方便,即使不用头结点,也能完成相同的功能。
PNODE InitList(PNODE pHead)
{
PNODE p=pHead;
p = (PNODE)malloc(sizeof(NODE));//为头结点分配内存
if(NULL == p) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
p->next = NULL;//清空指针域
return p;
}
查找
查找可以按不同的域去查找,这里以按姓名查找为例,核心代码都一样。
void SearchStudent(PNODE pHead) //查找学生信息
{
char Name[10];
printf("请输入你需要查找的学生的姓名:");
scanf("%s",Name);
PNODE p = pHead->next; //p指向第一个节点
while(p)
{
if(0 == strcmp(Name,p->st.Name))
{
printf("%s %d %g %g %g %g %g\n",p->st.Name, p->st.No, p->st.Score[0], p->st.Score[1], p->st.Score[2], p->st.Total, p->st.Ave);
}
p = p->next;
}
}
插入
void InsertStudent(PNODE pHead) //插入学生信息
{
PNODE p = pHead;
int i = 0;
struct Student stu; //学生结构,为数据域
int pos; //插入结点的位置
printf("请输入插入学生的位置:");
scanf("%d",&pos);
while(p && i<pos-1)
{
p = p->next;
i++;
}
if(p == NULL || i>pos)
{
printf("插入结点的位置不存在!\n");
return;
}
printf("你将在第%d个学生后面插入一个学生\n",pos-1);
printf("请输入第%d个学生的姓名:",pos);
scanf("%s",stu.Name);
printf("请输入第%d个学生的学号:",pos);
scanf("%d",&stu.No);
printf("请输入第%d个学生的语文成绩:",pos);
scanf("%f",&stu.Score[0]);
printf("请输入第%d个学生的数学成绩:",pos);
scanf("%f",&stu.Score[1]);
printf("请输入第%d个学生的英语成绩:",pos);
scanf("%f",&stu.Score[2]);
stu.Total = stu.Score[0] + stu.Score[1] + stu.Score[2];//计算总分
stu.Ave = stu.Total / 3.0f;//计算平均分
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew)
{
printf("动态内存分配失败,程序终止!\n");
exit(-1);
}
pNew->st = stu;
PNODE q = p->next; //先把后继存起来
p->next = pNew; //然后把要插的插进来
pNew->next = q; //将刚刚保存的节点挂上
}
删除
删除与插入其实差不多,主要就是改变指针的指向,使其指向合适的位置即可。
void DeleteStudent(PNODE pHead) //删除学生信息
{
PNODE p = pHead;
int i = 0;
int pos;
printf("请输入你需要删除的学生的编号:");
scanf("%d",&pos);
while(p && i<pos-1)
{
p = p->next;
i++;
}
if(p == NULL || i>pos-1)
{
printf("没找到需要删除的学生的编号!\n");
return;
}
PNODE q = p->next;
p->next = q->next;
free(q);
q == NULL;
printf("你已经成功删除了第%d个学生的信息!\n",pos);
}
创建单链表
链表的形成,有两种策略:有点栈和队列的意味。
前插法创建单链表: 后插入的值在链表的前面。
后插法创建单链表: 后插入的就挂在链尾即可。
- 采用后插法
void InputStudent(PNODE pHead) //采用后插法创建单链表
{
NODE stu;
PNODE p=pHead;
PNODE pTail=NULL;
while(NULL != p->next)
{
p = p->next;
}
pTail=p;
printf("请输入学生的人数:");
scanf("%d",&len);
int i;
for(i=0; i<len; i++)
{
system("cls"); //清屏
//下面得到数据域
printf("请输入第%d个学生的姓名:\n", i+1);
scanf("%s", stu.st.Name);
printf("请输入第%d个学生的学号:\n", i+1);
scanf("%d", &stu.st.No);
printf("请输入第%d个学生的语文成绩:\n", i+1);
scanf("%f", &stu.st.Score[0]);
printf("请输入第%d个学生的数学成绩:\n", i+1);
scanf("%f", &stu.st.Score[1]);
printf("请输入第%d个学生的英语成绩:\n", i+1);
scanf("%f", &stu.st.Score[2]);
stu.st.Total = stu.st.Score[0] + stu.st.Score[1] + stu.st.Score[2]; //计算总分
stu.st.Ave = stu.st.Total / 3.0f; //计算平均分
PNODE pNew = (PNODE)malloc(sizeof(NODE)); //为新节点分配内存
if(NULL == pNew) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
pNew->st = stu.st; //初始化结点的数据域
pNew->next = NULL; //清空新结点的指针域
//以上两步构建了新的尾巴
pTail->next = pNew; //将新结点挂在了链上
//后插法,插入的位置就是即将成为尾部的位置
pTail = pNew; //将pTail移到新的尾结点上
}
}
- 采用前插法
PNODE InputStudent() //采用前插法创建单链表
{
int len;//学生的人数
NODE stu;
PNODE pHead = (PNODE)malloc(sizeof(NODE)); //定义一个头结点并且为头结点分配内存
if(NULL == pHead) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
pHead.next = NULL;//清空指针域
printf("请输入学生的人数:");
scanf("%d",&len);
for(int i=0; i<len; i++)
{
system("cls"); //清屏
//下面得到数据域
printf("请输入第%d个学生的姓名:\n", i+1);
scanf("%s", stu.st.Name);
printf("请输入第%d个学生的年龄:\n", i+1);
scanf("%d", &stu.st.Age);
printf("请输入第%d个学生的学号:\n", i+1);
scanf("%d", &stu.st.No);
printf("请输入第%d个学生的语文成绩:\n", i+1);
scanf("%f", &stu.st.Score[0]);
printf("请输入第%d个学生的数学成绩:\n", i+1);
scanf("%f", &stu.st.Score[1]);
printf("请输入第%d个学生的英语成绩:\n", i+1);
scanf("%f", &stu.st.Score[2]);
stu.st.Total = stu.st.Score[0] + stu.st.Score[1] + stu.st.Score[2];//计算总分
stu.st.Ave = stu.st.Total / 3.0f; //计算平均分
PNODE pNew = (PNODE)malloc(sizeof(NODE)); //为新节点分配内存
if(NULL == pNew) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
pNew.st = stu.st; //初始化结点的数据域
pNew.next = pHead.next; //新结点的指针域为头结点的指针域 。
// 前插法插入的位置就是即将取代当前头结点的位置。
// 因为 pNew与pHead数据域不一样,所以不能 pNew=pHead。
pHead.next=pNew; //更新头结点
}
return pHead;
}
总的代码如下:
代码中将初始化嵌套在了输入函数中,也可以拿出开成为独立的功能函数。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student //学生结构
{
char Name[10]; //姓名
int No; //学号
float Score[3]; //三科的成绩
float Total; //总分
float Ave; //平均分
};
typedef struct Node //结点
{
struct Student st; //数据域
struct Node *next; //指针域
}NODE, *PNODE; //NODE等价于struct Student st||PNODE等价于struct Node *next
//NODE * P==PNODE P。为了提高可读性用NODE定义指向其他任意节点。用PNODE专指向头结点
PNODE InputStudent2(); //输入函数,用于输入学生信息(前插法)
PNODE InputStudent(PNODE pHead); //输入函数,用于输入学生信息(后插法)
void OutputStudent(PNODE pHead); //输出学生信息
void DeleteStudent(PNODE pHead); //删除学生信息
void SearchStudent(PNODE pHead); //查找学生信息
void ChangeStudent(PNODE pHead); //修改学生信息
void InsertStudent(PNODE pHead); //增加学生信息
void ScortByChinese(PNODE pHead); //对学生的语文成绩排序
void ScortByMath(PNODE pHead); //对学生的数学成绩排序
void ScortByEnglish(PNODE pHead); //对学生的英语成绩排序
void ScortByTotal(PNODE pHead); //对学生的总分排序
void welcome(){
printf("========================================================\n\n");
printf("========================================================\n\n");
printf("**************欢迎使用学生成绩管理系统******************\n\n");
printf("--------------------------------------------------------\n\n");
printf("********************************************************\n\n");
printf("========================================================\n\n");
printf("请按任意将进入学生管理系统:\n");
getchar();
}
void menu(){
printf("====================================================\n\n");
printf("------------- 请选择要操作的命令:------------------\n\n");
printf("------ 1 输入学生信息------- 2 输出学生信息------\n\n");
printf("------ 3 删除学生信息------- 4 查找学生信息------\n\n");
printf("------ 5 修改学生信息------- 6 增加学生信息------\n\n");
printf("------ 7 将学生的语文成绩按从大到小排--------------\n\n");
printf("------ 8 将学生的数学成绩按从大到小排--------------\n\n");
printf("------ 9 将学生的英语成绩按从大到小排--------------\n\n");
printf("------ 10 将学生的总成绩按从大到小排----------------\n\n");
printf("====================================================\n\n");
printf("------ ¤¤¤¤ 其他键退出系统 ¤¤¤¤ ------\n\n");
printf("====================================================\n\n");
}
int main()
{
welcome();
system("cls");
int Item;
PNODE pHead = NULL;//定义一个指针
while(1)
{
menu();
printf("请选择操作命令:");
scanf("%d",&Item);
system("cls");
switch(Item)
{
case 1:pHead = InputStudent(pHead);break;
case 2:OutputStudent(pHead);break;
case 3:DeleteStudent(pHead);break;
case 4:SearchStudent(pHead);break;
case 5:ChangeStudent(pHead);break;
case 6:InsertStudent(pHead);break;
case 7:ScortByChinese(pHead);
OutputStudent(pHead);break;
case 8:ScortByMath(pHead);
OutputStudent(pHead);break;
case 9:ScortByEnglish(pHead);
OutputStudent(pHead);break;
case 10:ScortByTotal(pHead);
OutputStudent(pHead);break;
default:printf("系统已经退出,欢迎再次使用!\n"),exit(1);
}
}
system("pause");
}
PNODE InputStudent(PNODE pHead) //输入函数,用于输入学生信息。采用后插法创建单链表
{
int len; //学生的人数
NODE stu;
PNODE p=pHead;
PNODE pTail=NULL;
if(NULL == p) //判断之前是否录入过数据
{
p = (PNODE)malloc(sizeof(NODE));//为头结点分配内存
if(NULL == p) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
pTail = p; //尾指针初始时指向头结点
pTail->next = NULL; //清空指针域
}
else{
PNODE temp = p;
while(NULL != temp->next)
{
temp = temp->next;
}
pTail=temp;
}
printf("请输入学生的人数:");
scanf("%d",&len);
int i;
for(i=0; i<len; i++)
{
system("cls");//清屏
printf("请输入第%d个学生的姓名:\n", i+1);
scanf("%s", stu.st.Name);
printf("请输入第%d个学生的学号:\n", i+1);
scanf("%d", &stu.st.No);
printf("请输入第%d个学生的语文成绩:\n", i+1);
scanf("%f", &stu.st.Score[0]);
printf("请输入第%d个学生的数学成绩:\n", i+1);
scanf("%f", &stu.st.Score[1]);
printf("请输入第%d个学生的英语成绩:\n", i+1);
scanf("%f", &stu.st.Score[2]);
stu.st.Total = stu.st.Score[0] + stu.st.Score[1] + stu.st.Score[2];//计算总分
stu.st.Ave = stu.st.Total / 3.0f; //计算平均分
PNODE pNew = (PNODE)malloc(sizeof(NODE)); //为新节点分配内存
if(NULL == pNew) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
pNew->st = stu.st; //初始化结点的数据域
pNew->next = NULL; //清空新结点的指针域
//以上两步构建了新的尾巴
pTail->next = pNew; //将新结点挂在了链上
//后插法,插入的位置就是即将成为尾部的位置
pTail = pNew; //将pTail移到新的尾结点上
}
return p;
}
PNODE InputStudent2()//输入函数,用于输入学生信息。采用前插法创建单链表
{
int len;//学生的人数
NODE stu;
PNODE pHead = (PNODE)malloc(sizeof(NODE));//定义一个头结点并且为头结点分配内存
if(NULL == pHead) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
pHead->next = NULL;//清空指针域
printf("请输入学生的人数:");
scanf("%d",&len);
int i;
for(i=0; i<len; i++)
{
system("cls");//清屏
printf("请输入第%d个学生的姓名:\n", i+1);
scanf("%s", stu.st.Name);
printf("请输入第%d个学生的学号:\n", i+1);
scanf("%d", &stu.st.No);
printf("请输入第%d个学生的语文成绩:\n", i+1);
scanf("%f", &stu.st.Score[0]);
printf("请输入第%d个学生的数学成绩:\n", i+1);
scanf("%f", &stu.st.Score[1]);
printf("请输入第%d个学生的英语成绩:\n", i+1);
scanf("%f", &stu.st.Score[2]);
stu.st.Total = stu.st.Score[0] + stu.st.Score[1] + stu.st.Score[2];//计算总分
stu.st.Ave = stu.st.Total / 3.0f; //计算平均分
PNODE pNew = (PNODE)malloc(sizeof(NODE)); //为新节点分配内存
if(NULL == pNew) //判断内存是否为空
{
printf("内存分配失败,程序终止!\n");
exit(-1);
}
pNew->st = stu.st; //初始化结点的数据域
pNew->next = pHead->next; //新结点的指针域为头结点的指针域 。
// 前插法插入的位置就是即将取代当前头结点的位置。
// 因为 pNew与pHead数据域不一样,所以不能 pNew=pHead。
pHead->next=pNew; //更新头结点
}
return pHead;
}
void InsertStudent(PNODE pHead)//增加学生信息
{
PNODE p = pHead;
int i = 0;
struct Student stu;//学生结构
int pos;//插入结点的位置
printf("请输入插入学生的位置:");
scanf("%d",&pos);
while(p && i<pos-1)
{
p = p->next;
i++;
}
if(p == NULL || i>pos)
{
printf("插入结点的位置不存在!\n");
return;
}
printf("你将在第%d个学生后面插入一个学生\n",pos-1);
printf("请输入第%d个学生的姓名:",pos);
scanf("%s",stu.Name);
printf("请输入第%d个学生的学号:",pos);
scanf("%d",&stu.No);
printf("请输入第%d个学生的语文成绩:",pos);
scanf("%f",&stu.Score[0]);
printf("请输入第%d个学生的数学成绩:",pos);
scanf("%f",&stu.Score[1]);
printf("请输入第%d个学生的英语成绩:",pos);
scanf("%f",&stu.Score[2]);
stu.Total = stu.Score[0] + stu.Score[1] + stu.Score[2];//计算总分
stu.Ave = stu.Total / 3.0f;//计算平均分
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew)
{
printf("动态内存分配失败,程序终止!\n");
exit(-1);
}
pNew->st = stu;
PNODE q = p->next; //先把后继存起来
p->next = pNew; //然后把要插的插进来
pNew->next = q; //将刚刚保存的节点挂上
}
void OutputStudent(PNODE pHead)//输出学生信息
{
PNODE p = pHead->next;//定义一个指针用于遍历学生信息,p指向第一个节点
printf(" 姓名 学号 语文 数学 英语 总分 平均分\n");
while(p)
{
printf("%6s %8d %6g %6g %6g %6g %10g\n", p->st.Name,p->st.No, p->st.Score[0], p->st.Score[1], p->st.Score[2], p->st.Total, p->st.Ave);
p = p->next;
}
}
void DeleteStudent(PNODE pHead)//删除学生信息
{
PNODE p = pHead;
int i = 0;
int pos;
printf("请输入你需要删除的学生的编号:");
scanf("%d",&pos);
while(p && i<pos-1)
{
p = p->next;
i++;
}
if(p == NULL || i>pos-1)
{
printf("没找到需要删除的学生的编号!\n");
return;
}
PNODE q = p->next;
p->next = q->next;
free(q);
q == NULL;
printf("你已经成功删除了第%d个学生的信息!\n",pos);
}
void SearchStudent(PNODE pHead)//查找学生信息
{
char Name[10];
printf("请输入你需要查找的学生的姓名:");
scanf("%s",Name);
PNODE p = pHead->next;//p指向第一个节点
while(p)
{
if(0 == strcmp(Name,p->st.Name))
{
printf("%s %d %g %g %g %g %g\n",p->st.Name, p->st.No, p->st.Score[0], p->st.Score[1], p->st.Score[2], p->st.Total, p->st.Ave);
}
p = p->next;
}
}
void ChangeStudent(PNODE pHead)//修改学生信息
{
char Name[10];
printf("请输入你需要修改的学生的姓名:");
scanf("%s",&Name);
PNODE p = pHead->next;//定义一个指针用于遍历学生信息
while(p)
{
if(0 == strcmp(Name, p->st.Name))
{
printf("姓名 年龄 学号 语文 数学 英语 总分 平均分\n");
printf("修改前的学生信息!\n");
printf("%s %d %g %g %g %g %g\n",p->st.Name, p->st.No, p->st.Score[0], p->st.Score[1], p->st.Score[2], p->st.Total, p->st.Ave);
system("pause");
system("cls");//清屏
printf("请输入新的学生姓名:");
scanf("%s", p->st.Name);
printf("请输入新的学生学号:");
scanf("%d", &p->st.No);
printf("请输入新的学生的语文成绩:");
scanf("%f", &p->st.Score[0]);
printf("请输入新的学生的数学成绩:");
scanf("%f", &p->st.Score[1]);
printf("请输入新的学生的英语成绩:");
scanf("%f", &p->st.Score[2]);
p->st.Total = p->st.Score[0] + p->st.Score[1] + p->st.Score[2];//计算总分
p->st.Ave = p->st.Total / 3.0f; //计算平均分
break;
}
p = p->next;
}
}
void ScortByChinese(PNODE pHead)//对学生的语文成绩排序
{
PNODE p, q;//定义两个指针
NODE temp;
for(p=pHead->next; NULL != p; p=p->next)
{
for(q=p->next; NULL !=q; q=q->next)
{
if(p->st.Score[0] < q->st.Score[0])//当前一个学生的语文成绩小于后一个学生的语文成绩时
{
temp.st = p->st;//交换学生的位置
p->st = q->st;
q->st = temp.st;
}
}
}
}
void ScortByMath(PNODE pHead)//对学生的数学成绩排序
{
PNODE p, q;//定义两个指针
NODE temp;
for(p=pHead->next; NULL != p; p=p->next)
{
for(q=p->next; NULL !=q; q=q->next)
{
if(p->st.Score[1] < q->st.Score[1])//当前一个学生的数学成绩小于后一个学生的数学成绩时
{
temp.st = p->st;//交换学生的位置
p->st = q->st;
q->st = temp.st;
}
}
}
}
void ScortByEnglish(PNODE pHead)//对学生的英语成绩排序
{
PNODE p, q;//定义两个指针
NODE temp;
for(p=pHead->next; NULL != p; p=p->next)
{
for(q=p->next; NULL !=q; q=q->next)
{
if(p->st.Score[2] < q->st.Score[2])//当前一个学生的英语成绩小于后一个学生的英语成绩时
{
temp.st = p->st;//交换学生的位置
p->st = q->st;
q->st = temp.st;
}
}
}
}
void ScortByTotal(PNODE pHead)//对学生的总分排序
{
PNODE p, q;//定义两个指针
NODE temp;
for(p=pHead->next; NULL != p; p=p->next)
{
for(q=p->next; NULL !=q; q=q->next)
{
if(p->st.Total < q->st.Total)//当前一个学生的总分小于后一个学生的总分时
{
temp.st = p->st;//交换学生的位置
p->st = q->st;
q->st = temp.st;
}
}
}
}
运行结果:
后插法得到的如下:
前插法得到的如下:
从上面可以看出前插法与后插法得到的顺序相反。