顺序表(线性表的顺序存储)---C语言版

顺序表

上篇文章中我们已经介绍了线性表。
这篇文章我们将介绍线性表的顺序存储(具体实现)----顺序表

一、顺序表的定义

仍旧使用该例子,我们要如何存储该表呢?

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=%s\n",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=%s\n",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=%s\n",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=%s\n",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=%s\n",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)内找到指定的元素

五、总结

特点一:逻辑顺序与其物理顺序相同

顺序表是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。
因为逻辑相邻物理上也相邻,导致插入和删除操作需要移动大量元素。

特点二:随机存取

对于按位查找,修改某个位置上的值,打印特定位置上的值这些操作很迅速。

特点三:存储密度高

每个结点上只存储数据元素,空间利用率高。

猜你喜欢

转载自blog.csdn.net/weixin_47138646/article/details/121578132
今日推荐