顺序表:由顺序存储的方式实现线性表。说白了就是就是物理空间也是相邻的。通常使用数组实现。
可以分为:
- 静态分配,使用大小固定
- 动态分配,在存满时候,使用malloc进行重新分配,用free进行回收
在讨论数据结构时候,通常通过三个方面进行讨论:逻辑结构,物理结构,基本操作。
一般基本操作包含:
- 初始化
- 插入
- 删除
- 查找,查找又可以分为按值与按位
- 销毁
顺序表特点:
- 支持随机访问,可以在O(1)的复杂度下访问元素(最显著的特点)
- 存储密度高
- 拓展不方便
- 插入与删除不方便,需要将所有后续结点后移,非常麻烦
静态分配
#define MaxSize 10
typedef struct{
ElemType data[MaxSize]; //数据数组
int length; //顺序表目前长度
}SeqList;
//初始化静态链表
void InitList(SeqList & L){
//静态顺序表在定义结构体时候就进行了数组的申请,所以可以不必对其操作,也可以增加循环,初始化数据为0
L.length = 0; //初始化当前顺序表为0
}
//按位插入,在i位置插入元素e
bool ListInsert(SqlList &L, int i, ElemType e){
if (i < 1 || i > L.length + 1) //判断插入点是否越界
return false;
if (L.length + 1 >= MaxSize) //判断插入后数组是否溢出
return false;
for (int j = L.length; j >= i ; j --){
L.data[j] = L.data[j-1];
}
L.data[i-1] = e;
L.length ++;
return true;
}
//按位删除
bool ListDelete(SqlList &L, int i, ElemType &e){
if (i < 1 || i > L.length)
return false;
e = L.data[i-1];
for (int j = i - 1 ; j < l.length-1 ; j ++)
L.data[j] = L.data[j+1];
L.length --;
return true;
}
//按位查找
Elem GetElem(SqlList L, int i){
return L.data[i-1];
}
//按值查找,查找到返回位序,否则返回0
int GetElem(SqlList L, Elem &e){
for (int i = 0 ; i < L.length ;i ++){
if (e == L.data[i])
return i+1;
}
return 0;
}
//判断线性表是否满
bool isFull(SeqList L){
return L.length == MaxSize;
}
动态分配
动态分配与静态分配除了在进行初始化以及空间不够之后再分配,其余操作类似。
#define InitSize 10
typedef struct{
Elem * data;
int maxsize;
int length;
}SeqList;
//初始化,与静态不同点在于通过malloc分配
void InitList(SeqList &L){
maxsize = InitSize;
length = 0;
L.data = (Elem *)malloc(sizeof(Elem) * maxsize);
}
//在增加空间不足时,通过malloc重新分配
void IncreaseSize(SeqList &L, int len){
Elem *p = L.data;
L.data = (Elem *)malloc(sizeof(Elem) * (L.maxsize + len));
for (int i = 0 ; i < L.length ; i ++)
L.data[i] = p[i];
free(p);
L.maxsize += len;
}
//由于动态分配采用malloc进行分配,需要编写析构函数free数组空间
void destroyList(SeqList &L){
free(L.data);
}
时间复杂度分析
进行插入操作时:需要进行将所有元素后移一格,然后进行插入。显然平均复杂度O(n)
进行删除操作类似,O(n)
进行按位查询操作,只要给定位序,就可以直接定位,时间复杂度O(1)。
原因:在已知初始地址,并且每一个元素单元的字节固定,就可以直接直接定位。
进行按值查找操作,在不考虑任何查找优化的前提下,需要遍历一遍数组,复杂度O(n)