C语言数据结构线性表(顺序表和单链表)
C语言线性表
C语言数据结构
线性表的顺序存储:
顺序储存的方法有数组与动态分配内存空间(关键在于这两者都可以在内存中分配一段连续的内存空间)
1.利用数组来完成一个顺序表:
首先定义一个结构体(如学生信息):
typedef struct student { char stu_ID;//学生学号 char name[10];//学生姓名 int score;//学生成绩 }student;//结构体数据类型名
然后再main函数里面定义一个结构体数组
int main() { student st[n];//n为你所需要的数据个数 return 0; }
最后再自定义一些对这个结构体数组进行操作的函数。
2.使用动态分配内存空间:(包含三者的头文件stdilb.h)
在此之前你需要了解C语言中的几个关于动态空间管理函数(malloc、realloc、free)
-
malloc(申请动态内存空间)
void* malloc(n*sizeof(数据类型));//申请成功后会返回这段连续空间的首地址,此时这个接收的指针就相当于一维数组名 可以看到这个函数的返回值是一个void*的指针,它可以用来指向任何类型的数据空间,但是在使用时我们需要将它强制转换为我们所需要的数据类型的指针(可以是基本数据类型:int float double char 也可以是构造数据类型结构体:那就需要注意自己构造的数据类型名)
-
realloc(改变动态空间大小)
void* realloc(n*sizeof(数据类型),(n+1)*sizeof(数据类型)); 这个函数主要用于顺序储存,在顺序储存中我们一开始就分配了我们所需要的连续的内存空间,但是在后续的造作当中我们往往是需要添加数据或删除数据的,这是就需要扩充或者减少内存空间的长度。
-
free(释放动态空间)
void free(void *p); 这个函数的功能主要是释放掉我们刚刚分配的内存空间,执行这个函数之后,系统就可以重新把这部分空间分配给其它变量或者进程。
-
补充申请动态内存函数还有(calloc)
void* calloc(n,sizeof(数据类型));//有两个参数(数据个数,数据类型) 与函数malloc一样,分配成功后也是返回这段连续空间的首地址,分配失败后返回NULL。
接下来进行操作
首先定义两个结构体
typedef struct student//用于存放数据 { int stu_ID;//学生学号 char name[10];//学生姓名 int score;//学生成绩 }student;//结构体数据类型名
typedef struct stu_Adm { student *pstu;//定义一个与数据类型同名的指针变量用于接收分配空间以及访问结构体成员 int lenth;//用于记录表长 }Adm;
接下来定义一个初始化函数
void init_stu(Adm &L,int n)//再main函数里面定义一个结构体变量L Adm L; { L->pstu=(student*)malloc(n*sizeof(student));//分配一个连续空间给pstu if(L->pstu==NULL) { printf("分配空间失败!\n"); } L->lenth=0;//当前数据个数 return; }
然后再定义输入函数、输出函数等等。(从这里开始一个长度为n,首地址即L->pstu的顺序表就已经构造好了)
参考代码:
#include <stdio.h> #include <stdlib.h> #define MAX 10 int i;//定义一个全局变量i用于后续for循环 typedef struct student//定义一个学生信息结构体 { char stu[11];//学号 char name[8];//姓名 int score;//成绩 }student; typedef struct Adm { student *ps;//用于间接访问 int lenth;//用于计数 }Adm; void init_stu(Adm *L,int n)//初始化顺序表(Adm* 表示形式参数是这个类型的指针,"L"是一个地址值用于接收分配的地址) n表示分配这个数据类型的长度即数据个数 { L->ps=(student *)malloc(n*sizeof(student));//调用头文件函数malloc分配内存空间并将首地址返回给定义的结构体变量 if(L->ps==NULL) { printf("分配空间失败!"); exit(-1);//结束当前进程 返回0是正常退出 不为0表示异常退出 } L->lenth=0;//表示当前数据个数 return; } void Input(Adm *L,int n)//输入函数 { if(n<0||n>MAX)//n小于一或大于十 { return; } for(i=0;i<n;i++) { scanf("%s %s %d",&L->ps[i].stu,&L->ps[i].name,&L->ps[i].score); } //由于mallco函数动态配了一段连续空间的首地址给指针变量ps 现在ps就相当于一个一维数组名(这里就是数据结构类型(student)的结构体数组名) L->lenth=n; return; } void Output(Adm *L,int n) { if(n<0||n>MAX) { return; } for(i=0;i<n;i++) { printf("%s %s %d\n",L->ps[i].stu,L->ps[i].name,L->ps[i].score); } } void Insert(Adm *L,int num,int n)//插入函数 { if(num<0||num>n+1||num>MAX) { printf("插入位置不合理!"); return; } L->ps=(student *)realloc(L->ps,(n+1)*sizeof(student));//realloc扩充分配的内存空间(这里只添加一个所以n+1) if(L->ps==NULL) { printf("扩充内存空间失败!"); exit(-1); } for(i=n+1;i>num-1;i--)//扩充空间之后第n+1的位置没有元素将n->n+1同理依次移动 { L->ps[i]=L->ps[i-1]; } printf("请输入你需要增加的数据(学号 姓名 单科成绩):\n"); scanf("%s %s %d",&L->ps[num-1].stu,&L->ps[num-1].name,&L->ps[num-1].score); L->lenth=n+1; return; } student Dele(Adm *L,int num1,int n)//删除函数 { student em;//定义一个空的同类型结构体用于删除位置赋值为空 student mid;//定义一个中间变量mid用于存放删除的数据 if(num1<0||num1>n||num1>MAX) { printf("删除位置不合理!\n"); return em; } mid=L->ps[num1-1];//先将被删数据赋值给mid L->ps[num1-1]=em;//再将被删位置赋值为空 for(i=num1-1;i<n;i++) { L->ps[i]=L->ps[i+1]; } L->lenth=n-1;//数据个数减一 return mid; } int main() { int n,num,num1; student num2;//用于存放删除函数 printf("请输入你需要的数据个数(n):\n"); scanf("%d",&n); Adm L; init_stu(&L,n);//调用初始化函数初始化顺序表 printf("请输入对应的数据(学号 姓名 单科成绩):\n"); Input(&L,n);//调用输入函数 printf("输出数据为:\n"); Output(&L,n);//调用输出函数 printf("请输入你需要插入数据的位置(num):\n"); scanf("%d",&num); Insert(&L,num,n); n=L.lenth; printf("插入后的结果为:\n"); Output(&L,n); printf("请输入你需要删除数据的位置(num1):\n"); scanf("%d",&num1); num2=Dele(&L,num1,n); printf("删除的值为:%s %s %d\n",num2.stu,num2.name,num2.score); n=L.lenth; printf("删除后结果为:\n"); Output(&L,n); free(L.ps);//程序结束释放由malloc分配的内存空间 return 0; }
线性表的链式存储:
链式储存有别于顺序储存的地方就是空间在物理结构上并不连续了(即链表的每一个结点的空间不一定是相邻的了)
显然链表的空间分配不能再使用数组而是需要动态分配内存空间了
不连续就意味着不能用数组名加下标的方法来进行随机存取了,那么我们就需要利用指针来对每一个结点来进行连接
首先我们来看一看链表的结点的结构(仍以学生信息为例):
typedef struct Lnode { 数据类型 data;//数据域(这里的数据类型可以是基本数据类型也可以是构造数据类型) struct Lnode *next;//指针域 }Lnode,LinkList*;//定义结构体数据类型与其同类型的指针类型
这里我们以带头结点的单链表为例:
1.数据域是用来存放我们所需要的数据,而指针域则是用于指向下一个结点
2.构造的数据类型Lnode主要是用于除了头结点的结点进行指针或数据变量的定义,而LinkList*则是用于头结点。
3.构造成功后Lnode* L等价于LinkList L。
知道结点的结构之后我们就可以开始创建链表了
创建一个单链表
typedef struct student//数据域结构体 { char stu_ID;//学生学号 char name[10];//学生姓名 int score;//学生成绩 }student;//数据域数据类型 typedef struct Lnode { student data;//结构体数据域变量 struct Lnode *next;//构造同类型指针用于指向结点本身 }Lnode,LinkList*; LinkList init_list(LinkList L)//链表头结点初始化 { L=(LinkList)malloc(sizeof(Lnode)); if(L==NULL) { printf("分配内存空间失败!\n"); exit(-1); } L->next=NULL;//此时只含头结点 return L;//返回头结点地址 } void Trail(LinkList L,int Num)//尾插法 (需要在main函数中输入所需数据个数Num和将初始化成功的链表头结点的地址传递过来) { student Data;//定义一个同类型数据域用于循环输入数据 Lnode *p,*q;//定义一个间接替换前一个结点指针的同类型指针p 同时定义一个尾插结点的指针 p=L;//先将头结点的地址放在指针p中 while(i<Num)//循环结束条件 { q=(Lnode*)malloc(sizeof(Lnode));//每一次增加一个结点都为其分配一个内存空间 if(q==NULL) { printf("分配内存空间失败!\n"); return; } scanf("%s %s %d",&Data.num,&Data.name,&Data.score);//输入中间转换数据 q->data=Data;//将其赋值给每一次增加的结点的数据域 q->next=p->next; //开始操作 首先将前一个结点的指针域赋值给后一个结点 p->next=q;//然后将后一个结点的地址赋值给前一个结点 p=q;//最后让这个添加的新节点又成为前一个结点然后进行循环 i++; } return; }
此处忽略了main函数中的一些操作,主要是展示链表的构造。所谓”万事开头难“,能够构造好链表之后,后续的操作也就水到渠成了
参考代码:
#include <stdio.h> #include <stdlib.h> int i=0,i1=1;//定义全局变量用于后续的使用 typedef struct student { char num[8]; char name[8]; int score; }student;//数据域 typedef struct Lnode { student data;//用于存放学生信息 struct Lnode *next;//指针域 }Lnode,*LinkList;//定义结构体数据类型与指针数据类型 LinkList init_list(LinkList L)//链表的初始化 { L=(LinkList)malloc(sizeof(Lnode)); if(L==NULL) { printf("分配内存空间失败!\n"); exit(-1); } L->next=NULL; return L; } void Trail(LinkList L,int Num)//尾插法 { student Data;//定义一个同类型数据域用于循环输入数据 Lnode *p,*q;//定义一个间接替换前一个结点指针的同类型指针p 同时定义一个尾插结点的指针 p=L;//先将头结点的地址放在指针p中 while(i<Num)//循环结束条件 { q=(Lnode*)malloc(sizeof(Lnode));//每一次增加一个结点都为其分配一个内存空间 if(q==NULL) { printf("分配内存空间失败!\n"); return; } scanf("%s %s %d",&Data.num,&Data.name,&Data.score);//输入中间转换数据 q->data=Data;//将其赋值给每一次增加的结点的数据域 q->next=p->next; //开始操作 首先将前一个结点的指针域赋值给后一个结点 p->next=q;//然后将后一个结点的地址赋值给前一个结点 p=q;//最后让这个添加的新节点又成为前一个结点然后进行循环 i++; } return; } void output(LinkList L)//定义一个链表的输出函数用于输出链表 { Lnode *p; p=L->next; printf("学生信息如下所示:\n"); while(p!=NULL) { printf("%s %s %d\n",p->data.num,p->data.name,p->data.score); p=p->next; } return; } void Insert(LinkList L,int num1,int Num)//插入(这里指针p的作用要理解) !!!p指针从头指针开始指向每一个结点直到找到你所需要的结点为止 { if(num1<0||num1>Num+1)//Num传入函数用于此处的判断越界 { printf("插入的位置不合理!\n"); return; } Lnode *p,*s;//其中的s是用于输入需要插入的结点(类似于头插法只是不一定是在头结点与首元结点之间插入) p=L; while(p!=0&&(i1<num1))//p还能指到元素(值不为空) { p=p->next; i1++; } s=(Lnode *)malloc(sizeof(Lnode)); if(s==NULL) { printf("分配内存空间失败!\n"); exit(-1); } printf("请输入你需要插入的值(学号 成绩 单科成绩)\n"); scanf("%s %s %d",&s->data.num,&s->data.name,&s->data.score); s->next=p->next;//p指到了要插入的前一个元素 p->next=s; return; } LinkList Delete(LinkList L,int num2,int Num)//删除 { Lnode *em; if(num2<0||num2>Num) { printf("删除的位置不合理!\n"); return em; } Lnode *Det,*p;//用于存放删除数据 p=L; while(p!=0&&(i1<num2))//这样结束循环后p指向了要删除的前一个结点的位置 { p=p->next; i1++; } Det=p->next; p->next=Det->next; return Det; } int main() { int Num,num1,num2; LinkList L,H,Det; H=init_list(L); printf("请输入你需要的数据个数(Num):\n"); scanf("%d",&Num); printf("请输入对应数据(学号 姓名 单科成绩):\n"); Trail(H,Num); output(H); printf("请输入你需要插入的位置(num1):\n"); scanf("%d",&num1); Insert(H,num1,Num); output(H); printf("请输入你需要删除的位置(num2):\n"); scanf("%d",&num2); Det=Delete(H,num2,Num); printf("删除的值为:%s %s %d\n",Det->data.num,Det->data.name,Det->data.score); output(H); free(H); return 0; }
关于线性表的操作还有很多,此处只是提到了插入与删除(参考代码仅供参考本人很菜)
本文作者:如此而已~~~
本文链接:https://www.cnblogs.com/fragmentary/p/16043816.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步