概述
目录
- 顺序表
- 一、顺序表的定义
- 二、顺序表上基本操作的具体实现
- 1.`InitList_Sq(*L)`:初始化表。构造一个空的顺序表。
- 分类一:主函数里声明的是一个指向顺序表的指针
- 分类二:主函数里声明的是一个顺序表
- 2. `Length(L)`:求表长。
- 3.`ListInsert_Sq(*L,i,e)`:插入操作。在顺序表L中的第i个位置上插入指定元素e。
- 4.`ListDelete_Sq(*L,i,*e)`:删除操作。删除表中L中第i个位置的元素,并用e返回删除的元素。
- 5.`LocateElem(L,e)`:按值查找操作。在表L中查找具有给定关键字值的元素。
- 6.`GetElem(L,i,*e)`:按位查找操作。获取表L中第i个位置的元素的值。
- 7.`PrintList(L)`:输出操作。按前后顺序输出线性表L的所有元素值。
- 8.`Empty(L)`:判空操作。
- 9.`DestroyList(*L)`:销毁操作。销毁顺序表,并释放顺序表L所占用的内存空间。
- 三、完整代码实现
- 四、分析操作的复杂度
- 五、总结
顺序表
上篇文章中我们已经介绍了线性表。
这篇文章我们将介绍线性表的顺序存储(具体实现)----顺序表
一、顺序表的定义
仍旧使用该例子,我们要如何存储该表呢?
id | name | description |
---|---|---|
1 | 史强 | 最强辅助 |
2 | 章北海 | 我不需要钢印,我是自己思想的主人 |
3 | 罗辑 | 人类不感谢罗辑 |
4 | 维德 | 失去人性,失去很多。失去兽性,失去一切 |
步骤一:声明数据元素类型
首先我们使用结构体声明数据元素(对应表中的一行数据)的类型
关于数据元素的描述可以查看上篇文章。
typedef struct{
int id;//对应表中的id
char name[100];//对应表中的姓名
char description[200];//对应表中的描述
}ElemType;//此处的ElemType是个类型名
步骤二:顺序表的静态定义
步骤一中已经定义了数据元素,那么n个数据元素如何存储?
我们采用数组来存储所有的数据元素
注意:用数组的话我们在定义时需要声明数组的大小,这里使用变量MaxSize
那又如何确定已经存储了多少数据元素呢?(也就是表有多少行?)
我们需要用一个变量length来存储当前的长度
#define MaxSize 10 //定义顺序表的最大长度,在声明数组时使用
typedef struct{
ElemType data[MaxSize];//顺序表的数据元素
int length;//顺序表的当前长度
}Sqlist;//顺序表的类型定义
步骤三:顺序表的动态定义
上面的定义有什么问题呢?
采用静态分配时,由于数组的大小和空间事先已经固定,一旦空间占满,再加入新的数据就会产生溢出,进而导致程序崩溃
如何改进?
采用动态分配(关于动态分配的内容可以查看博文)
#define InitSize 10;//表的初始长度
#define ListIncrement 2;//扩容时的分配增量
typedef struct{
ElemType *data;//数组的基地址,指示动态分配的数组的指针
int length;//当前长度
int MaxSize;//当前表的最大存储长度
}Sqlist;//顺序表的类型定义
二、顺序表上基本操作的具体实现
InitList_Sq(*L)
:初始化表。构造一个空的顺序表。Length(L)
:求表长。ListInsert(*L,i,e)
:插入操作。在表L中的第i个位置上插入指定元素e。ListDelete(*L,i,*e)
:删除操作。删除表中L中第i个位置的元素,并用e返回删除的元素。LocateElem(L,e)
:按值查找操作。在表L中查找具有给定关键字值的元素。GetElem(L,i,*e)
:按位查找操作。获取表L中第i个位置的元素的值。PrintList(L)
:输出操作。按前后顺序输出顺序表L的所有元素值。Empty(L)
:判空操作。DestroyList(*L)
:销毁操作。销毁顺序表,并释放顺序表L所占用的内存空间。
当我们需要对一个传入的参数的值进行修改时,我们传入它的指针,像初始化、删除、插入、销毁这样的操作,我们传入*L;
同理,像删除之后,如果要用e返回,查找到该元素后,要用e返回,我们传入*e
1.InitList_Sq(*L)
:初始化表。构造一个空的顺序表。
每种方法的写法都有很多种,这里我们都展示一下:
分类一:主函数里声明的是一个指向顺序表的指针
Sqlist *L;
方法一:使用指针的指针
int InitList_Sq(Sqlist **L){
*L=(Sqlist*)malloc(sizeof(Sqlist)); //先给顺序表分配空间
(*L)->data=(ElemType *)malloc(sizeof(ElemType)*InitSize); //给数组分配存储空间
if(!(*L)->data) return FALSE; //分配失败返回FALSE
(*L)->length=0; //空表长度为0
(*L)->MaxSize=InitSize;//初始最大长度
// 另一种赋值方式
// (*(*L)).data=(ElemType *)malloc(sizeof(ElemType)*InitSize);
// (*(*L)).length=0;
// (*(*L)).MaxSize=InitSize;
return TRUE;//成功初始化返回TRUE
}
在主函数种调用:
Sqlist *L;
int i=InitList_Sq(&L);
这种方式写起来很麻烦
方法二:使用return返回值
Sqlist* InitList_Sq(){
Sqlist *L; //定义一个顺序表指针,注意不要赋值为空
L->data=(ElemType *)malloc(sizeof(ElemType)*InitSize);给数组分配存储空间
if(!L->data) return NULL;//分配失败返回NULL
L->length=0;//空表长度为0
L->MaxSize=InitSize;//初始最大长度
return L;//成功初始化返回该指针
}
在主函数种调用:
Sqlist *L;
L=InitList_Sq();
这种方式写起来比上一种简单一点
分类二:主函数里声明的是一个顺序表
Sqlist L;
方法一:使用指针
int InitList_Sq(Sqlist *L){
L->data=(ElemType *)malloc(sizeof(ElemType)*InitSize); //给数组分配存储空间
if(!L->data) return FALSE; //分配失败返回FALSE
L->length=0; //空表长度为0
L->MaxSize=InitSize;//初始最大长度
return TRUE;//成功初始化返回TRUE
}
在主函数种调用:
Sqlist L;
int i=InitList_Sq(&L);
个人喜好这种方式,后面的操作采用这种方式进行具体实现
方法二:使用return返回值
Sqlist InitList_Sq(){
Sqlist L;
L.data=(ElemType *)malloc(sizeof(ElemType)*InitSize);给数组分配存储空间
if(!L.data) return;//分配失败
L.length=0;//空表长度为0
L.MaxSize=InitSize;//初始最大长度
return L;//成功初始化
}
在主函数种调用:
Sqlist L;
L=InitList_Sq();
2. Length(L)
:求表长。
顺序表中的表长很好求得,故不把它写为一个函数,在主函数中直接访问。
SqList *L;
L->length //表长
SqList L;
L.length //表长
3.ListInsert_Sq(*L,i,e)
:插入操作。在顺序表L中的第i个位置上插入指定元素e。
插入操作示例:
使用指针
int ListInsert_Sq(Sqlist *L,int i,ElemType e){
if (i<1||i>L->length+1) return FALSE;//不在插入的有效范围内,返回FALSE
if (L->length>=L->MaxSize){ //空间不足,扩容
ElemType *new_base=(ElemType *)realloc(L->data,sizeof(ElemType)*(L->MaxSize+ListIncrement));
if (!new_base) return FALSE; //分配空间失败
L->data=new_base;
L->MaxSize+=ListIncrement;
}
ElemType *q=&(L->data[i-1]);//q为插入的位置
ElemType *p;//p为最后一个元素的位置,在for中赋值
for(p=&(L->data[L->length-1]);p>=q;p--) *(p+1)=*p; //插入位置之后的元素右移一个
*q=e;//插入e
L->length++;//表长加一
return TRUE;
}
在主函数种调用:
Sqlist L;
ElemType e
int i=ListInsert_Sq(&L,1,e);//在第一个位置上插入e
4.ListDelete_Sq(*L,i,*e)
:删除操作。删除表中L中第i个位置的元素,并用e返回删除的元素。
删除操作示例:
指针实现
int ListDelete_Sq(Sqlist *L,int i,ElemType *e){
if(i<1||i>L->length) return FALSE;//不在删除的有效范围内
ElemType *q=&(L->data[i-1]);//q为删除的元素的位置
*e=*q;//把q的值传给e
ElemType *p=&(L->data[L->length-1]);//p为最后一个元素的位置
for(++q;q<=p;++q) *(q-1)=*q; //被删除元素之后的元素左移
L->length--;//表长减一
}
在主函数种调用:
Sqlist L;
ElemType e
int i=ListDelete_Sq(&L,1,&e);//删除表中L中第1个位置的元素,并用e返回删除的元素。
5.LocateElem(L,e)
:按值查找操作。在表L中查找具有给定关键字值的元素。
这里我们假设每个插入的数据中id的值是唯一的,按值查找时,我们只比较id值
实现
int LocateElem_Sq(Sqlist L,ElemType e){
int i;
for (i = 0; i <L.length; i++){
if(L.data[i].id==e.id) return TRUE;
}
return FALSE;
}
在主函数种调用:
Sqlist L;
ElemType e
int i=LocateElem_Sq(L,e);//在线性表中查找元素e是否存在
6.GetElem(L,i,*e)
:按位查找操作。获取表L中第i个位置的元素的值。
实现
int GetElem_Sq(Sqlist L,int i,ElemType *e){
if(i<1||i>L.length) return FALSE; //不在有效范围内
*e=L.data[i-1];//用e返回查找的值
return TRUE;
}
在主函数种调用:
Sqlist L;
ElemType e
int i= GetElem_Sq(&L,1,&e);//获取表L中第1个位置的元素的值。
7.PrintList(L)
:输出操作。按前后顺序输出线性表L的所有元素值。
实现
void PrintList_Sq(Sqlist L){
int i;
for (i = 0; i < L.length; i++){
printf("第%d行:id=%d,name=%s,description=%sn",i+1,L.data[i].id,L.data[i].name,L.data[i].description);
}
}
在主函数种调用:
Sqlist L;
PrintList_Sq(L);
8.Empty(L)
:判空操作。
这个对于线性表来说也很好实现,这里也不把它写成函数
在主函数中直接判断:
Sqlist L;
if(L.length==0){
printf("空表");
}
9.DestroyList(*L)
:销毁操作。销毁顺序表,并释放顺序表L所占用的内存空间。
实现
void DestroyList(Sqlist *L){
free(L->data);
L->length=0;
L->MaxSize=0;
}
在主函数种调用:
Sqlist L;
DestroyList(&L);
三、完整代码实现
#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0
#define InitSize 10 //表的初始长度
#define ListIncrement 2 //扩容时的分配增量
typedef struct{
int id;//对应表中的id
char name[100];//对应表中的姓名
char description[200];//对应表中的描述
}ElemType;//此处的ElemType是个类型名
typedef struct{
ElemType *data;//数组的基地址,指示动态分配的数组的指针
int length;//当前长度
int MaxSize;//当前表的最大存储长度
}Sqlist;//顺序表的类型定义
/*初始化*/
int InitList_Sq(Sqlist *L){
L->data=(ElemType *)malloc(sizeof(ElemType)*InitSize); //给数组分配存储空间
if(!L->data) return FALSE; //分配失败返回FALSE
L->length=0; //空表长度为0
L->MaxSize=InitSize;//初始最大长度
return TRUE;//成功初始化返回TRUE
}
/*插入*/
int ListInsert_Sq(Sqlist *L,int i,ElemType e){
if (i<1||i>L->length+1) return FALSE;//不在插入的有效范围内,返回FALSE
if (L->length>=L->MaxSize){ //空间不足,扩容
ElemType *new_base=(ElemType *)realloc(L->data,sizeof(ElemType)*(L->MaxSize+ListIncrement));
if (!new_base) return FALSE; //分配空间失败
L->data=new_base;
L->MaxSize+=ListIncrement;
}
ElemType *q=&(L->data[i-1]);//q为插入的位置
ElemType *p;//p为最后一个元素的位置,在for中赋值
for(p=&(L->data[L->length-1]);p>=q;p--) *(p+1)=*p; //插入位置之后的元素右移一个
*q=e;//插入e
L->length++;//表长加一
return TRUE;
}
/*删除*/
int ListDelete_Sq(Sqlist *L,int i,ElemType *e){
if(i<1||i>L->length) return FALSE;//不在删除的有效范围内
ElemType *q=&(L->data[i-1]);//q为删除的元素的位置
*e=*q;//把q的值传给e
ElemType *p=&(L->data[L->length-1]);//p为最后一个元素的位置
for(++q;q<=p;++q) *(q-1)=*q; //被删除元素之后的元素左移
L->length--;//表长减一
}
/*按值查找*/
int LocateElem_Sq(Sqlist L,ElemType e){
int i;
for (i = 0; i <L.length; i++){
if(L.data[i].id==e.id) return TRUE;
}
return FALSE;
}
/*按位查找*/
int GetElem_Sq(Sqlist L,int i,ElemType *e){
if(i<1||i>L.length) return FALSE; //不在有效范围内
*e=L.data[i-1];//用e返回查找的值
return TRUE;
}
/*打印*/
void PrintList_Sq(Sqlist L){
int i;
for (i = 0; i < L.length; i++){
printf("第%d行:id=%d,name=%s,description=%sn",i+1,L.data[i].id,L.data[i].name,L.data[i].description);
}
}
/*销毁顺序表*/
void DestroyList(Sqlist *L){
free(L->data);
L->length=0;
L->MaxSize=0;
}
int main(void){
///初始化
Sqlist L;
int i=InitList_Sq(&L);
if (i==1){
printf("初始化成功n");
}else{
printf("初始化失败n");
}
printf("n");
///插入
ElemType a[4]={
{1,"史强","最强辅助"},
{2,"章北海","我不需要钢印,我是自己思想的主人"},
{3,"罗辑","人类不感谢罗辑"},
{4,"维德","失去人性,失去很多。失去兽性,失去一切"}
};
int j;
for ( j = 0; j < 4; j++){
i=ListInsert_Sq(&L,j+1,a[j]);
}
PrintList_Sq(L);
printf("n");
删除
ElemType e;
ListDelete_Sq(&L,4,&e);
printf("被删除的元素:id=%d,name=%s,description=%sn",e.id,e.name,e.description);
printf("删除之后:n");
PrintList_Sq(L);
printf("n");
//按值查找
i=LocateElem_Sq(L,a[2]);
if (i==1){
printf("按值查找成功n");
}else{
printf("按值查找失败n");
}
printf("n");
//按位查找
i=GetElem_Sq(L,1,&e);
if (i==1){
printf("按位查找成功n");
}else{
printf("按位查找失败n");
}
printf("被查找的元素为:id=%d,name=%s,description=%sn",e.id,e.name,e.description);
printf("n");
//销毁查找
DestroyList(&L);
PrintList_Sq(L);//打印为空了
}
运行结果
初始化成功
第1行:id=1,name=史强,description=最强辅助
第2行:id=2,name=章北海,description=我不需要钢印,我是自己思想的主人
第3行:id=3,name=罗辑,description=人类不感谢罗辑
第4行:id=4,name=维德,description=失去人性,失去很多。失去兽性,失去一切
被删除的元素:id=4,name=维德,description=失去人性,失去很多。失去兽性,失去一切
删除之后:
第1行:id=1,name=史强,description=最强辅助
第2行:id=2,name=章北海,description=我不需要钢印,我是自己思想的主人
第3行:id=3,name=罗辑,description=人类不感谢罗辑
按值查找成功
按位查找成功
被查找的元素为:id=1,name=史强,description=最强辅助
四、分析操作的复杂度
在分析复杂度之前,我们先介绍一下,顺序表最主要的特点:随机存取
随机存取
如图所示:我们可以通过首地址和元素的序号在时间O(1)内找到指定的元素。
例如:打印时,我们可以直接通过数组下标访问元素.
printf("第%d行:id=%d,name=%s,description=%sn",i+1,L.data[i].id,L.data[i].name,L.data[i].description);
插入操作
最好情况:在表尾插入,元素无需移动,移动0次,时间复杂度为O(1)。
最坏情况:在表头插入,所有元素都需要向后移动一次,移动n次,时间复杂度为O(n)
平均情况:可以想象,移动次数和插入位置的函数是一个一次函数,平均情况应该为n/2,时间复杂度为O(n)。
综上:时间复杂度为O(n)。
删除操作
最好情况:删除表尾元素,元素无需移动,移动0次,时间复杂度为O(1)。
最坏情况:删除表头元素,除表头外都需要向后移动一次(移动n-1次),时间复杂度为O(n)
平均情况:可以想象,移动次数和删除位置的函数也是一个一次函数,平均情况应该为(n-1)/2,时间复杂度为O(n)。
综上:时间复杂度为O(n)。
按值查找操作
最好情况:查找的元素就在表头,需要比较1次,时间复杂度为O(1)。
最坏情况:查找的元素在表尾或者不存在,需要比较n次,时间复杂度为O(n)
平均情况:可以想象,该函数也是一个一次函数,平均情况应该为(n+1)/2,时间复杂度为O(n)。
综上:时间复杂度为O(n)。
打印操作
打印n次,时间复杂度为O(n)。
其他操作
因为随机存取,时间复杂度为O(1)
例如:按位查找,可以直接根据首地址和元素的序号在时间O(1)内找到指定的元素
五、总结
特点一:逻辑顺序与其物理顺序相同
顺序表是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。
因为逻辑相邻物理上也相邻,导致插入和删除操作需要移动大量元素。
特点二:随机存取
对于按位查找,修改某个位置上的值,打印特定位置上的值这些操作很迅速。
特点三:存储密度高
每个结点上只存储数据元素,空间利用率高。
最后
以上就是直率鼠标为你收集整理的顺序表(线性表的顺序存储)---C语言版的全部内容,希望文章能够帮你解决顺序表(线性表的顺序存储)---C语言版所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复