一、概念
顺序表:是用一段物理地址连续的存储单元依次存储数据元素的线性结构,即逻辑相邻,物理相邻。
顺序表分为:
- 静态顺序表:使用定长的数组进行存储,和数组操作类似。
- 动态顺序表:使用动态开辟的内存进行存储,长度可变,不固定。
顺序表的特点:
【1.】优点:
- 空间连续,存储数据密度大,空间利用率高。
- 存取速度快,通过下标来直接存储,随机访问,根据下标查找元素O(1)。
- 不频繁申请或者释放内存,没有内存碎片的问题。
- 代码简单。
【2.】缺点:
- 插入和删除比较慢,时间复杂度为O(n),比如:插入或者删除一个元素时,整个表需要遍历移动元素来重新排一次顺序。
- 有空间限制,需要先一次性分配一定的空间。当需要存取的元素个数可能多于顺序表的元素个数时,会出现"溢出"问题;当元素个数远少于预先分配的空间时,空间浪费巨大。
动态顺序表需要动态开辟内存和扩容,就会使用到下面几种函数:
【malloc函数】
malloc函数,功能是动态分配内存,内存未被初始化,函数原型如下:
# include<stdlib.h>
void * malloc(unsigned int size);
size表示申请的内存大小,它并不知道内存中应该存储何种数据类型,所以需要对其进行进行类型说明,一般是
(数据类型指针)malloc(长度*数据类型)
【realloc函数】
realloc函数,用来扩容,函数原型如下:
void realloc(void *ptr,unsigned int newsize);
ptr是指向原来内存块的指针,按照size大小进行扩容,原始内存中的数据不变。使用方式和malloc类似。
【calloc函数】
calloc函数,功能是分配内存,并将分配内存的空间每一位初始化为0,内部用memset实现初始化。函数原型为:
void* calloc(unsigned int n,unsigned int size);
表示开辟n个为size大小的连续空间。一般不用它,都用malloc函数开辟。
二、实现
我们主要讲解动态顺序表的实现,因为静态顺序表和数组操作类似。
(一)设计
【一、动态顺序表的结构体设计:】
- 要实现长度不固定,需要引入扩容机制,所以需要有个成员保存动态内存的地址,定义为int* elem。
- 长度不固定,所以需要一个变量保存现在顺序表的总长度,定义为size。
- 需要有一个变量保存当前的有效数据,即格子中放的数据的个数,定义为length。
那么整个顺序表的图示:
当我们存放数据时:
【二、顺序表功能设计:】
对顺序表有一定的操作,如初始化,增加,删除等,我们设计主要的操作功能,在实现设计时,一定要对参数进行一定的判断,增强代码的健壮性:
【1. 初始化操作】
初始化:目的是把顺序表空间建立起来,三个成员初始化。
- 进行空指针的判断,用断言,如果为空就直接返回,常量放在左边,变量放在右边是一个好的编码风格。
- 指针指向malloc开辟的空间,其他两个成员进行初始化,要记住每次处理三个成员。
//1.初始化,将结构体的三个成员都进行一个初始化
void InitSeq(SqList ps)
{
//判空
assert(ps != NULL);
if(ps == NULL)
{
return;
}
//开辟SIZE格子的大小
ps->elem = (int*)malloc(sizeof(int)*SIZE);
ps->size = SIZE;
ps->length = 0;
}
【2. 向指定的位置插入数据val,O(N)】
-
判断参数是否合法,空指针,位置的判断,是否现在已经没有位置可以插入了。
-
所以需要写判满函数,判满函数写为内部的,即static修饰的,判断当前顺序表长度是否等于有效数据个数即可。
//2-1.判断格子是否满了,写为内部函数 static bool IsFull(SqList ps) { return ps->length == ps->size; }
-
判断顺序表是否满了,满了需要扩容,所以需要扩容函数,也写成私有的,扩容到原来的两倍即可。使用realloc扩容,扩容后记得将总长度扩大。 那么STL里面扩容是2倍还是1.5倍?答案都对,VS下为1.5倍,gcc为2倍。
//2-2.扩容函数 static void ReLength(SqList ps) { ps->elem = (int*)realloc(ps->elem,sizeof(int)*ps->size*2);//表示在原来内存的基础上增加内存 ps->size *= 2; }
-
数据从后往前移动。然后pos位置插入即可。
//2.向pos的位置插入val数值,返回是否插入成功,需要对参数进行判断
bool InsertSeq(SqList ps,int pos,int val)
{
assert(ps!=NULL);
if(ps==NULL)
return false;
if(pos < 0 || pos > ps->length)
{
return false;
}
if(IsFull(ps))//判满
{
ReLength(ps);//扩容
}
//后移插入
for(int i = ps->length-1;i >= pos;i--)
{
ps->elem[i+1]=ps->elem[i];
}
ps->elem[pos] = val;
ps->length++;
return true;
}
【3. 按元素查找下标,O(N)】
当前顺序表元素无序,所以顺序找,O(n)复杂度。如果序列数据有序折半查找O(logn),哈希O(1)数据存放的位置和值相关,不常用,因为有哈希冲突。
//3.查找从头开始的第一个key,成功返回下标,失败返回-1
int Search(SqList ps,int key)
{
assert(ps != NULL);
if(ps == NULL)
return -1;
for(int i = 0;i< ps->length;i++)
{
if(ps->elem[i] == key)
return i;
}
return -1;
}
【4. 删除pos位置的值,O(N)】
- 判断下标的合法性
- 从pos位置将后面的数据往前挪即可。
//4.删除pos位置的值
bool DeletePos(SqList ps,int pos)
{
assert(ps != NULL);
if(ps == NULL)
return false;
if(pos < 0 || pos > ps->length)
{
return false;
}
//前移删除
for(int i = pos;i < ps->length-1; i++)
{
ps->elem[i] = ps->elem[i+1];
}
ps->length--;//记住,不要忘了。
return true;
}
【5. 删除key,O(N)】
- 调用查找函数,得到下标pos。
- 调用根据下标位置删除元素的函数,删除pos位置的元素。
//5.删除从头开始的第一个key
bool DeleteVal(SqList ps,int key)
{
assert(ps != NULL);
if(ps == NULL)
return false;
int pos = Search(ps,key);
return DeletePos(ps,pos);
}
【6. 销毁】
判断是否存在内存泄露,可以调两次测试一下,O(N)
- 内存的释放,free函数。
- 将其他成员的值重新初始化,指针指向空,长度变为0。
//6.销毁
void Destroy(SqList ps)
{
assert(ps != NULL);
free(ps->elem);
ps->elem = NULL;
ps->size = 0;
ps->length = 0;
}
【7. 打印,O(N)】
循环打印顺序表数据即可。
//打印函数
void Show(SqList ps)
{
for(int i = 0;i < ps->length;i++)
{
printf("%d ",ps->elem[i]);
}
printf("\n");
}
(二)代码
我们给出完整的代码,将函数声明写到另一个头文件中,测试也写到另一个.cpp文件中,所以有三个文件:
【 Seqlist.h 】
#pragma once//防止头文件重复包含
# define SIZE 20 //格子的大小
typedef struct SeqList
{
int* elem;//指向动态内存的地址
int size;//保存顺序表的大小
int length;//保存有效数据长度
}SeqList,*SqList;
//1.初始化
void InitSeq(SqList ps);
//2.向pos的位置插入val数值,返回是否插入成功
bool InsertSeq(SqList ps,int pos,int val);
//3.查找从头开始的第一个key,成功返回下标,失败返回-1
int Search(SqList ps,int key);
//4.删除pos位置的值
bool DeletePos(SqList ps,int pos);
//5.删除从头开始的第一个key
bool DeleteVal(SqList ps,int key);
//6.销毁
void Destroy(SqList ps);
//7.打印函数
void Show(SqList ps);
【SeqList.cpp】
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include "Seqlist.h"
//1.初始化,将结构体的三个成员都进行一个初始化
void InitSeq(SqList ps)
{
//判空
assert(ps != NULL);
if(ps == NULL)
{
return;
}
//开辟SIZE格子的大小
ps->elem = (int*)malloc(sizeof(int)*SIZE);
ps->size = SIZE;
ps->length = 0;
}
//2-1.判断格子是否满了,写为内部函数
static bool IsFull(SqList ps)
{
return ps->length == ps->size;
}
//2-2.扩容函数
static void ReLength(SqList ps)
{
ps->elem = (int*)realloc(ps->elem,sizeof(int)*ps->size*2);//表示在原来内存的基础上增加内存
ps->size *= 2;
}
//2.向pos的位置插入val数值,返回是否插入成功,需要对参数进行判断
bool InsertSeq(SqList ps,int pos,int val)
{
assert(ps!=NULL);
if(ps==NULL)
return false;
if(pos < 0 || pos > ps->length)
{
return false;
}
if(IsFull(ps))//判满
{
ReLength(ps);//扩容
}
//后移插入
for(int i = ps->length-1;i >= pos;i--)
{
ps->elem[i+1]=ps->elem[i];
}
ps->elem[pos] = val;
ps->length++;
return true;
}
//3.查找从头开始的第一个key,成功返回下标,失败返回-1
int Search(SqList ps,int key)
{
assert(ps != NULL);
if(ps == NULL)
return -1;
for(int i = 0;i< ps->length;i++)
{
if(ps->elem[i] == key)
return i;
}
return -1;
}
//4.删除pos位置的值
bool DeletePos(SqList ps,int pos)
{
assert(ps != NULL);
if(ps == NULL)
return false;
if(pos < 0 || pos > ps->length)
{
return false;
}
//前移删除
for(int i = pos;i < ps->length-1; i++)
{
ps->elem[i] = ps->elem[i+1];
}
ps->length--;//记住,不要忘了。
return true;
}
//5.删除从头开始的第一个key
bool DeleteVal(SqList ps,int key)
{
assert(ps != NULL);
if(ps == NULL)
return false;
int pos = Search(ps,key);
return DeletePos(ps,pos);
}
//6.销毁
void Destroy(SqList ps)
{
assert(ps != NULL);
free(ps->elem);
ps->elem = NULL;
ps->size = 0;
ps->length = 0;
}
//打印函数
void Show(SqList ps)
{
for(int i = 0;i < ps->length;i++)
{
printf("%d ",ps->elem[i]);
}
printf("\n");
}
【main.cpp】
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include "Seqlist.h"
int main()
{
SeqList ps;
InitSeq(&ps);
for(int i=0;i<10;i++)
{
InsertSeq(&ps,i,i);
}
printf("插入0~9的数值:\n");
Show(&ps);
printf("---------------\n");
int pos= Search(&ps,3);
printf("查找3的位置为:%d\n",pos);
printf("---------------\n");
DeletePos(&ps,pos);
printf("按照下标删除元素3:\n");
Show(&ps);
printf("---------------\n");
DeleteVal(&ps,5);
printf("按照元素删除5:\n");
Show(&ps);
printf("---------------\n");
Destroy(&ps);
printf("销毁\n");
Destroy(&ps);
Show(&ps);
}
代码运行结果:
加油哦!🍝。