顺序表概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素,通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系的线性结构,一般情况下采用数组存储,并在数组上完成数据的增删查改操作的一类线性表
动态顺序表的开辟
在建立顺序表时首先明确我们所需要引用的变量,一个存储数据的数组 array[ ] 、记录有效数据个数的 size、记录数组容量的 capacity
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储
// 顺序表的静态存储
#define N 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType array[N]; // 定长数组
size_t size; // 有效数据的个数
}SeqList;
- 动态顺序表:使用动态开辟的数组存储
// 顺序表的动态存储
typedef struct SeqList
{
SLDataType* array; // 指向动态开辟的数组
size_t size ; // 有效数据个数
size_t capicity ; // 容量空间的大小
}SeqList;
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组容易导致空间浪费或数组越界,所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小
顺序表基本的增删查改
一 . 增删查改前期步骤
- 建初始化函数
传入数组及初始容量,给数组开辟对应容量的空间,再进行判空操作则可以正常运行,此时有效数据个数为 0
void SeqListInit(SeqList*psl, size_t capacity)
{
psl->capacity = capacity;
psl->array = (SLDataType*)malloc(capacity*sizeof(SeqList));//malloc内存不够会失败,先判空
assert(psl->array);
psl->size = 0;
}
- 建容量查询函数
若容量与数据个数相等,则使容量为原先的两倍,且为数组开辟新空间,但要注意判断容量为 0 的情况,若容量为 0 ,则容量加10。开辟空间时,应开辟对应容量个片段,片段长度应为 SeqList 类型对应长度
void CheckCapacity(SeqList*psl)
{
assert(psl);
if (psl->capacity && psl->size == psl->capacity)
{
psl->capacity *= 2;
psl->array = (SLDataType*)realloc(psl,psl->capacity*sizeof(SeqList));
}
else if (!psl->capacity)
{
psl->capacity += 10;
psl->array = (SLDataType*)realloc(psl,psl->capacity * sizeof(SeqList));
}
}
- 建销毁函数
创建顺序表时,应考虑到当实验任务完成时,应当释放开辟数组对应空间。如果数组内存在有效值,先释放有效值,并使数组指向空,最后另 size 、 capacity 为 0 ,即完成顺序表销毁过程
void SeqListDestory(SeqList*psl)
{
assert(psl);
if (psl->array)
{
free(psl->array);
psl->array = NULL;
psl->size= 0;
psl->capacity = 0;
}
}
- 输出顺序表
void SeqListPrint(SeqList*psl)
{
assert(psl);
for (int i = 0; i < (int) psl->size; i++)
{
printf("%d ", psl->array[i]);
}
putchar(('\n'));
}
二 . 增加数据操作
- 在尾部插入数据
先判断容量,令数组末尾元素为插入值,size + 1即可,时间复杂度为 O(1)
void SeqListPushBack(SeqList*psl,SLDataType x)
{
assert(psl);
CheckCapacity(psl);
psl->array[psl->size] = x;
psl->size++;
}
- 在头部插入数据
首先应当将数组内元素全部向后移动一个单位,为了避免覆盖,数据从后向前进行移动,最后在首位插入数值,size + 1,相较于尾插,头插需要移动元素,时间复杂度为 O(n)
void SeqListPushFront(SeqList*psl, SLDataType x)
{
assert(psl);
CheckCapacity(psl);
int i;
for (i = psl->size - 1; i >= 0; i--)
{
psl->array[i + 1] = psl->array[i];
}
psl->array[0] = x;
psl->size++;
}
- 在任意位置实现插入操作
传入插入位置 pos,先判断 pos 与 size 的关系,将 pos 之后的元素依次从后往前后移,插入元素后 size + 1
void SeqListInsert(SeqList*psl, size_t pos, SLDataType x)
{
assert(psl||pos <= psl->size);
CheckCapacity(psl);
for (int i = (int)psl->size - 1; i >= (int)pos; i--)
{
psl->array[i + 1] = psl->array[i];
}
psl->array[pos] = x;
psl->size++;
}
三 . 删除数据操作
- 在尾部删除数据
先判断容量,size - 1即可,时间复杂度为 O(1)
void SeqListPopFront(SeqList*psl)
{
assert(psl);
CheckCapacity(psl);
psl->size--;
}
- 在头部删除数据
首先应当判断数组实际长度不为 0 ,将数组内元素全部向前移动一个单位,为了避免覆盖,数据从前向后进行移动,最后 size - 1
void SeqListPopBack(SeqList*psl)
{
assert(psl||psl->size);
CheckCapacity(psl);
psl->size--;
}
- 在任意位置实现删除操作
传入插入位置 pos,先判断 pos 与 size 的关系,将 pos 之后的元素依次从前往后前移,删除元素后 size - 1
void SeqListErase(SeqList*psl, size_t pos)
{
assert(psl || pos < psl->size);
psl->size--;
int i;
for (i = pos; i < (int)psl->size; i++)
{
psl->array[i] = psl->array[i + 1];
}
}
- 删除顺序表中所给数据
对所给数据位置进行查找,若 pos 大于等于 0 ,证明找到该数据,再进行删除数据操作即可,但是上述操作最多只删除一个数据
void SeqListRemove(SeqList*psl, SLDataType x)
{
assert(psl);
int pos = SeqListFind(psl, x);
if (pos >= 0)
{
SeqListErase(psl, pos);
}
}
- 批量删除顺序表中所给数据
有两种操作方法:
1)调用两个数组,一个原数组,第二个存放非指定元素,遍历顺序表后,将第二个数组拷贝到第一个数组中,并修改 size
2)调用两个指针,一个找非指定元素,第二个指向首元素,当程序运行时,第一个指针向后找非指定元素,每查找到一个元素,调到第二个指针所在位置,覆盖原先元素,第二个指针向后移动,遍历完成后修改 size,此方法只调用了原数组,下列代码采取了该方法
void SeqListRemoveAll(SeqList*psl, SLDataType x)
{
assert(psl);
size_t index = 0;
for (size_t i = 0; i < psl->size; ++i)
{
if (psl->array[i] != x)
{
psl->array[index] = psl->_array[i];
index++;
}
}
psl->_size = index;
}
四 . 查找数据操作
- 查找相应位置元素操作
先对 pos 范围进行判断,后直接返回数值即可
SLDataType SeqListAt(SeqList*psl, size_t pos)
{
assert(psl&& pos < psl->size);
return psl->array[pos];
}
- 一般查找元素位置操作
挨个查找元素,若找到,则返回位置
int SeqListFind(SeqList*psl, SLDataType x)
{
assert(psl);
int i;
for (i = 0; i <(int)psl->size; i++)
{
if (psl->array[i] == x)
{
return i;
}
}
return -1;
}
- 二分查找操作
实现二分查找操作首先应该对数组进行排序,冒泡排序后进行查找操作,返回位置,排序时要注意数组越界问题
void SeqListBubbleSort(SeqList*psl)
{
assert(psl);
size_t end = psl->size;
while (end > 1)
{
//单趟排序
for (size_t i = 1; i < end; ++i)
{
if (psl->array[i - 1]>psl->array[i])
{
DataType tmp = psl->_array[i - 1];
psl->_array[i - 1] = psl->_array[1];
psl->_array[i] = tmp;
}
}
--end;
}
}
int SeqListBinaryFind(SeqList*psl, SLDataType x)
{
assert(psl);
size_t start = 0;
size_t end = psl->size - 1;
while (start <= end)
{
size_t mid = start + (end - start) / 2;
if (psl->array[mid] == x)
return mid;
else if (psl->array[mid] > x)
mid = end;
else
mid = start;
}
return -1;
}
五、修改数据操作
void SeqListModify(SeqList*psl, size_t pos, SLDataType x)
{
assert(psl || pos < psl->size);
psl->array[pos] = x;
}
上述即为几种关于顺序表的基本增删查改操作的代码思想,通过顺序表,亦可实现许多其他的操作,只要清楚了顺序表的基本结构原理,就可以实现函数,例如实现查找最大值,逆序输出等操作