定义
顺序表——用顺序存储的方式实现线性表顺序存储。
把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
但是在线性表中,元素的存放位置不是元素的序号,而是要加上数据元素的大小,原因是数据元素在线性表中存储要占用空间,所以每一个数据元素之间的间隔就是前一个数据元素的大小,如下图所示:
那么如何知道一个数据元素的大小呢?
Ans:在C语言中,使用sizeof(ElemType)
函数
Eg:sizeof(int) = 4B
还有更复杂的结构,如:
typedef struct{
int num; //号数
int people; //人数
} Customer;
sizeof(Customer) = 8B;
顺序表的实现——静态分配
#define MaxSize 10 //定义最大长度
typedef struct{
ElemType data[MaxSize]; //用静态的数组存放数据元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义(静态分配方式)
当声明了data
数组之后,实际上就是在内存中开辟了一整片的连续空间,如图所示:
给各个数据元素分配连续的存储空间
大小为MaxSize * sizeof(ElemType)
其中ElemType
可以是int
类型,也可以是更为复杂的struct
类型
代码示例:
#include <stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct{
int data[MaxSize]; //用静态的“数组”存放数据元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义
//基本操作——初始化一个顺序表
void InitList(SqList &L){
for(int i=0; i<MaxSize; i++){
L.data[i]=0; //将所有元素设置为默认初始值
}
L.length=0; //顺序表初始长度为0
}
int main(){
SqList L; //声明一个顺序表
InitList(L); //初始化顺序表
//...省略后续操作的代码
return 0;
}
代码分析:
- 数据元素的类型是
int
,所以数据元素的大小是4B
- 内存中有脏数据——将
Length
的值设为0
这一步是不可以省略的,因为不确定之前这一片内存中是什么数据
如果不设置元素的初始值,输出出来会是一串“奇怪”的数字
这是因为内存中有遗留的“脏数据”
Q:如果“数组”存满了怎么办?
A:直接放弃治疗,顺序表的表长开始之后就无法修改(存储空间是静态的)
那么以上也就是静态分配的局限性
顺序表的实现——动态分配
#define InitSize 10 //顺序表的初始长度
typedef struct{
ElemType *data; //指示动态分配的数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的最大长度
}SeqList; //顺序表的类型定义(动态分配方式)
在C语言中,分别提供了
malloc
函数动态申请空间free
函数释放内存空间
在C++中,提供了
new
函数delete
函数
new
函数和delete
函数都设计到面向对象相关的内容
所以更建议使用malloc
函数和free
函数来编写
malloc
函数:
- 在内存里申请了一整片连续的内存空间
- 空间的大小为:
L.data = (ElemType *) malloc (sizeof(ElemType) * InitSize);
- 并且在
malloc
函数会返回一个指针,我们需要把返回的这个指针强制转换为定义的数据元素类型的指针 - 如:如果是
int
类型,那么在ElemType
的位置就需要改成int
代码示例:
#define InitSize 10 //默认的最大长度
typedef struct{
int *data; //指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
}SeqList;
void InitList(SeqList &L){
//用malloc申请一片内存空间
L.data=(int *)malloc(InitSize*sizeof(int));
L.length=0;
L.MaxSize=InitSize;
}
//增加动态数组的长度
void IncreaseSize(SeqList &L, int len){
int *p=L.data;
L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0; i<L.length; i++){
L.data[i]=p[i]; //将数据复制到新区域
}
L.MaxSize=L.MaxSize+len; //顺序表最大长度增加len
free(p); //释放原来的内存空间
}
int main(){
SeqList L; //声明一个顺序表
InitList(L); //初始化顺序表
//...插入几个元素
IncreaseSize(L, 5);
return 0;
}
代码分析:
- 首先,
SeqList L;
之后在内存中开辟了一片空间 - 其次,
InitList(L);
之后申请了一片连续的存储空间 - 并且
malloc
返回了一个名为data
的指针,并且在malloc
开辟出来的空间中指向data[0]
的位置 int *p=L.data
定义了一个名为p
的指针,并且把L.data
的值赋给了p
,也就是说p
和data
指向的是同一个位置,也就是data[0]
的位置- 由于新开辟出来的空间里面没有任何的数据,所以需要让
data
指向新的data[0]
的位置 - 最后调用
free(p)
释放原来的内存空间
顺序表的特点
- 随机访问——可以在
O(1)
时间内找到第i
个元素- 代码实现:
data[i-1];
- 静态分配、动态分配都一样
- 代码实现:
- 存储密度高——每个节点只存储数据元素
- 如果采用链式存储
- 每个节点除了数据元素之外
- 还要存储指针
- 拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
- 插入、删除操作不方便,需要移动大量元素