9.链表
9.1 概念
假如:做一个班级信息管理系统,统计班级学生的信息而我们事先不知道班级人数,或者知道人数,但是中间人员可能发生变化:比如有新同学加入,有同学请假,又或者我们需要统计班级的平均成绩等等
目标:要做一个类似 QQ、飞秋类似的通信软件,其中有一个功能,类似用户上下线检测、有新的用户上线、下线实时更新显示,可以实时查询在线状态、按姓名排序等以上问题如何使用学过的C语言知识处理呢?
使用数组远远不能达到我们的要求,因为数组只能存储同类型的数据,这个场景需要的是多种类型的数据存储。
- 数组不能实现动态的内存申请;
- malloc申请的空间也不能实现局部申请和释放
这就要链表登场了
定义: 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。
9.1.1 链表的构成
特点: 链表由一系列节点(链表中每一个元素称为节点)组成,节点在运行时动态生成(malloc),每个节点包括两个部分:
- 一个是存储数据元素的数据域
- 另一个是存储下一个节点地址的指针域
typedef struct student{
int num;
char name; // 数据域
struct student *next; // 指针域,存放下一个节点的首地址
} STU;
9.2链表的操作
9.2.1 链表的创建
#include <stdio.h>
#include <stdlib.h>
typedef struct student
{
int num;
int score;
char name[20];
// 指针域
struct student *next;
} STU;
void link_create_head(STU **p_head, STU *p_new)
{
STU *p_mov = *p_head;
if (*p_head == NULL)
{ // 当第一次加入链表为空
*p_head = p_new; // 头节点保存新插入节点的地址
p_new->next = NULL;
}
else // 第二次及以后加入链表
{
while (p_mov->next != NULL)
{
p_mov = p_mov->next; // 找到原有链表的最后一个节点
}
p_mov->next = p_new; // 将新申请的节点加入链表
p_new->next = NULL;
}
}
int main(int argc, char const *argv[])
{
STU *head = NULL, *p_new = NULL;
int num, i;
printf("请输出链表的初始个数:\n");
scanf("%d", &num);
for (i = 0; i < num; i++)
{
p_new = (STU *)malloc(sizeof(STU)); // 申请一个新节点
printf("请输入学号、分数、名字\n"); // 给新节点赋值
scanf("%d %d %s", &p_new->num, &p_new->score, &p_new->name);
link_create_head(&head, p_new); // 将新节点加入链表
}
return 0;
}
9.2.2 链表的遍历
#include <stdio.h>
#include <stdlib.h>
typedef struct student
{
int num;
int score;
char name[20];
// 指针域
struct student *next;
} STU;
void link_create_head(STU **p_head, STU *p_new)
{
STU *p_mov = *p_head;
if (*p_head == NULL)
{ // 当第一次加入链表为空
*p_head = p_new; // 头节点保存新插入节点的地址
p_new->next = NULL;
}
else // 第二次及以后加入链表
{
while (p_mov->next != NULL)
{
p_mov = p_mov->next; // 找到原有链表的最后一个节点
}
p_mov->next = p_new; // 将新申请的节点加入链表
p_new->next = NULL;
}
}
void link_print(STU *head)
{
STU *p_mov;
// 定义新的指针保存链表,防止使用head改变原本链表
p_mov = head;
// 当循环到最后一个节点的指针域为NULL的时候,结束循环
while (p_mov != NULL)
{
// 先打印当前指针保存节点的指针域
printf("num = %d score = %d name: %s\n", p_mov->num, p_mov->score, p_mov->name);
// 指针后移,指向下一个节点的地址
p_mov = p_mov->next;
}
}
int main(int argc, char const *argv[])
{
STU *head = NULL, *p_new = NULL;
int num, i;
printf("请输出链表的初始个数:\n");
scanf("%d", &num);
for (i = 0; i < num; i++)
{
p_new = (STU *)malloc(sizeof(STU)); // 申请一个新节点
printf("请输入学号、分数、名字\n"); // 给新节点赋值
scanf("%d %d %s", &p_new->num, &p_new->score, &p_new->name);
link_create_head(&head, p_new); // 将新节点加入链表
}
link_print(head);
return 0;
}
执行输出结果
请输出链表的初始个数:
2
请输入学号、分数、名字
1 2 zhangsan
请输入学号、分数、名字
1 3 lisi
num = 1 score = 2 name: zhangsan
num = 1 score = 3 name: lisi
9.2.3 链表的释放
void link_free(STU **p_head){
// 定义一个指针变量保存头结点的地址
STU *pb = *p_head;
while (*p_head != NULL){
// 先保存p_head指向的结点地址
pb = *p_head;
// p_head 保存下一个结点的地址
*p_head = (*p_head)->next;
// 释放结点,防止野指针
free(pb);
pb = NULL;
}
}
9.2.4 链表结点的查找
//链表的查找//按照学号查找
STU *link_search_num(STU *head,int num){
STU *p_mov;
//定义的指针变量保存第一个结点的地址
p_mov=head;
//当没有到达最后一个结点的指针域时循环继续
while(p_mov!=NULL){
// 对比,如果找到是当前需要的数据,则返回结点的地址
if(p_mov->num == num){
//找到了
return p_mov;
};
p_mov=p_mov->next;
}
return NULL; //没有找到
}
9.2.5 链表结点的删除
- 如果链表为空,则不需要删除
- 如果删除的是第一个结点A,则需要将A的指针域保存其下一个节点B的地址
- 如果删除的是中间结点B,那么需要找到这个节点的前一个节点A,使前一个节点的指针域保存这个节点(B)后一个节点C的地址
void link_delete_num(STU **p_head,int num){
STU *pb,*pf;
pb = pf = *p_head;
if (*p_head == NULL){ // 1. 链表为空,不用删
printf("链表为空,没有需要删除的节点");
return;
}
while (pb-> num != num && pb->next !=NULL){ // 循环找需要删除的节点
pf = pbl;
pb = pb->next;
}
if (pb->num ==num)
{ // 找到了一个节点的num和num相同
if( pb ==*p_head){ //2. 如果是头结点,让保存头节点的指针保存后一个节点的地址
*p_head = pb->next;
}else{
pf->next = pb->next; // 3. 如果是中间结点,其前一个节点指针保存其后一个节点地址
}
free(pb);
pb = NULL;
}else{
printf("没有需要删除的节点\n");
}
9.2.6 链表中插入节点
链表中插入节点,按照原链表顺序插入,找到合适的位置
- 如果链表没有节点,则新插入的就是头结点
- 如果新插入的节点数值最小,则其就是头结点
- 如果新插入的结点数值在中间位置,则找到前一个,然后插入到他们中间
- 如果新插入的节点数值较大,则插入到最后
//链表的插入:按照学号的顺序插入
void link insert_num(STU **p_head,STU *p_new){
STU *pb,*pf;
pb=pf=*p_head;
// 1. 链表为空链表,新插入的结点就是头结点
if(*p_head == NULL){
*p_head = p_new;
p_new->next = NULL;
return ;
}
while((p_new->num >= pb->num)&& (pb->next !=NULL)){ // 找到需要插入位置的前一个结点和后一个结点
pf=pb;
pb=pb->next;
}
// 2. 找到一个节点pb的num比新来的节点num大
if (p_new->num < pb->num){
if (pb == *p_head){
p_new->next = *p_head; // 如果这个节点是头结点,那么新来的节点需要插到最前面,作为头结点
*p_head = p_new;
}else{
pf->next = p_new; // 如果这个节点不是头节点,新来的节点插入到中间
p_new->next = pb;
}
}else{ // 没有找到一个节点pb的num比新来的节点大,那么新来的节点就是最后一个结点
pb->next = p_new;
p_new->next = NULL;
}
}
9.2.7 链表的排序
- 如果链表为空,不需要排序
- 如果链表只有一个结点,不需要排序
- 如果链表有多个节点,采用交换排序的方式。先将第一个结点与后面所有的结点依次对比数据域,只要有比第一个结点数据域小的,则交换位置交换之后,拿新的第一个结点的数据域与下一个结点再次对比,如果比他小,再次交换,依次类推第一个结点确定完毕之后,接下来再将第二个结点与后面所有的结点对比,直到最后一个结点也对比完毕为止
void link_order(STU *head){
STU *pb,*pf,temp;
pf = head;
if(head == NULL){
printf("链表为空,不用排序\n");
return ;
}
if (head->next == NULL){
printf("链表只有一个结点,不用排序\n");
return ;
}
while (pf->next != NULL){
pb = pf->next; //pd从基准元素的下个元素开始
while (pb != NULL){ // 结点不为NULL,即不为最后一个节点
if (pf->num > pb->num){ // 交换节点值和节点中的指针指向
temp = *pb;
*pb = *pf;
*pf =temp;
temp.next = pb ->next;
pb->next = pf->next;
pd->next = temp.next
}
pb = pb->next;
}
pf = pf->next;
}
}
9.2.8 链表逆序
//链表逆序
STU *link_reverse(STU *head){
STU *pf,*pb,*r;
pf=head;
pb=pf->next;
while(pb!=NULL){
r=pb->next;
pb->next=pf;
pf=pb;
pb=r;
}
head->next=NULL;
head=pf;
return head;
}
9.3 双向链表
9.3.1 创建和遍历
- 创建一个节点为头结点,将两个指针都保存NULL
- 找到链表中最后一个结点A,A的指针保存新插入的结点B地址;B有两个指针域,一个保存A结点地址,另一个保存NULL
double link creat head(STU **p _head,STU *p_new){
STU *p_mov=*p_head;
if(*p_head==NULL){ //当第一次加入链表为空时
*p_head= p_new;
p_new->front = NULL;
p_new->next = NULL;
}else{
//第二次及以后加入链表
while(p_mov->next!=NULL){
//找到原有链表的最后一个节点
p_mov=p_mov->next;
}
//将新申请的节点加入链表
p_mov->next= p_new;
p_new->front=p_mov;
p_new->next = NULL;
}
}
9.3.2 结点删除
- 如果链表为空,则不需要删除
- 如果删除第一个结点A,则保存链表首地址的指针保存后一个结点B的地址,并且让结点B的front保存NULL
- 如果删除最后一个结点Z,只需要让最后一个结点的前一个结点Y的next保存NULL即可
- 如果删除中间结点M,则让中间结点的前后两个结点的指针域分别保存对方的地址即可
void doubel_link_delete_num(STU **p_head,int num){
STU *pb,*pf;
pb = *p_head;
if (*p_head == NULL){ // 1. 链表为空
printf("链表为空,没有需要删除的节点\n");
return ;
}
while ((pb->num !=num) && (pb->next !=NULL)){ // 遍历需要删除的节点
pb = pb->next;
}
if (pb->num == num){ // 找到了一个节点的num与num相同,删除pb指向的节点
if (pb == *p_head){ // 2. 如果找到的是头节点
if ((*p_head)->next ==NULL){
*p_head = pb->next;
}else{ // 如果有多个节点
*p_head = pb->next;
(*p_head)->front = NULL;
}
}else{ //要删除的节点是其他节点
if (pb->next !=NULL){ // 3.删除中间节点
pf = pb->front;
pf->next = pb->next;
(pb->next)->front = pf;
}else{ // 4.删除尾部节点
pf =pb->front;
pf->next = NULL;
}
}
}
}
9.3.3 插入节点
按照顺序插入节点
//链表的插入:按照学号的顺序插入
void link insert_num(STU **p_head,STU *p_new){
STU *pb,*pf;
pb=pf=*p_head;
// 1. 链表为空链表,新插入的结点就是头结点
if(*p_head == NULL){
*p_head = p_new;
p_new->next = NULL;
return ;
}
while((p_new->num >= pb->num)&& (pb->next !=NULL)){ // 找到需要插入位置的前一个结点和后一个结点
pf=pb;
pb=pb->next;
}
// 2. 找到一个节点pb的num比新来的节点num大
if (p_new->num < pb->num){
if (pb == *p_head){
p_new->next = *p_head; // 如果这个节点是头结点,那么新来的节点需要插到最前面,作为头结点
(*p_head)->front = p_new; // 头结点的front指保存新插入的结点地址
*p_new->front = NULL; // 新插入的节点front保存NULL
*p_head = p_new; // 原本保存链表首地址的指针,保存新插入的节点地址
}else{
pf->next = p_new; // 如果这个节点不是头节点,新来的节点插入到中间
p_new->front = pf;
pf->next =p_new;
pb->front = p_new;
}
}else{ // 没有找到一个节点pb的num比新来的节点大,那么新来的节点就是最后一个结点
pb->next = p_new;
p_new->front = pb;
p_new->next = NULL
}
}
标签:02,pb,head,19,next,链表,new,节点
From: https://www.cnblogs.com/hasaki-yasuo/p/18022939