什么是顺序表?
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表的分类
1.静态顺序表:使用定长数组存储。
// 顺序表的静态存储
#define N 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType array[N]; // 定长数组
size_t size; // 有效数据的个数
}SeqList;
2.动态顺序表:使用动态开辟的数组存储。
// 顺序表的动态存储
typedef struct SeqList
{
SLDataType* array; // 指向动态开辟的数组
size_t size ; // 有效数据个数
size_t capicity ; // 容量空间的大小
}SeqList;
一般来说,现实中基本都是使用动态顺序表,根据需要动态的分配空间大小。接下来实现动态顺序表。
SeqList.h
#pragma once
typedef int SDataType;
typedef struct SeqList
{
SDataType* array; //存放元素的空间,不够时申请更大的
int capacity;//空间总的大小
int size;//空间中有效元素的个数
}SeqList;
//初始化
void SeqListInit(SeqList* s, int initCapacity);
//尾插
void SeqListPushBack(SeqList* s, SDataType data);
//尾删
void SeqListPopBack(SeqList* s);
//头插
void SeqListPushFront(SeqList* s, SDataType data);
//头删
void SeqListPopFront(SeqList* s);
//任意位置插入
void SeqListInsert(SeqList* s, int pos, SDataType data);
//任意位置删除
void SeqListErase(SeqList* s, int pos);
//获取顺序表中有效元素个数
int SeqListSize(SeqList* s);
//获取顺序表的容量
int SeqListCapacity(SeqList* s);
//检测顺序表是否为空
int SeqListEmpty(SeqList* s);
//查找值为data的元素是否在顺序表中,如果在返回下标,否则返回-1
int SeqListFind(SeqList* s, SDataType data);
//清空
void SeqListClear(SeqList* s);
//销毁
void SeqListDestroy(SeqList* s);
//检查容量
int CheckCapacity(SeqList* s);
SeqList.c
#include "SeqList.h"
#include <assert.h>
#include <malloc.h>
#include <stdio.h>
//初始化
void SeqListInit(SeqList* s, int initCapacity)
{
assert(s);
initCapacity = initCapacity <= 3 ? 3 : initCapacity;
s->array = (SDataType*)malloc(sizeof(SDataType)*initCapacity);
if (NULL == s->array)
return;
s->capacity = initCapacity;
s->size = 0;
}
//检查容量
int CheckCapacity(SeqList* s)
{
assert(s);
if (s->size == s->capacity)
{
//按照两倍的方式进行扩容
int newCapacity = s->capacity * 2;
s->array = (SDataType*)realloc(s->array, newCapacity*sizeof(SDataType));
if (NULL == s->array)
return 0;
s->capacity = newCapacity;
}
return 1;
}
//尾插
//1.不需要扩容,直接将元素插入在size的位置
//2.需要扩容
void SeqListPushBack(SeqList* s, SDataType data)
{
assert(s);
//在插入之前要先保证有空间
if (!CheckCapacity(s))
return;
s->array[s->size++] = data;
}
//尾删
void SeqListPopBack(SeqList* s)
{
if (SeqListEmpty(s))
return;
s->size--;
}
//头插
//1.先检测是否需要扩容--如果需要则扩容
//2.将顺序表中所有元素整体往后搬移一个位置
//3.将data插入到顺序表的起始位置
void SeqListPushFront(SeqList* s, SDataType data)
{
//检测是否需要扩容
if (!CheckCapacity(s))
return;
//需要将顺序表中所有的元素向后搬移一个位置
for (int i = s->size - 1; i >= 0; i--)
s->array[i + 1] = s->array[i];
//在0位置插入data
s->array[0] = data;
s->size++;
}
//头删--除第一个元素外,剩余元素整体往前搬移一个位置
void SeqListPopFront(SeqList* s)
{
if (SeqListEmpty(s))
return;
for (int i = 1; i < s->size; ++i)
{
s->array[i - 1] = s->array[i];
}
s->size--;
}
//任意位置插入
void SeqListInsert(SeqList* s, int pos, SDataType data)
{
assert(s);
if (pos<0 || pos>s->size)
return;
//需要检测是否扩容
if (!CheckCapacity(s))
return;
//将[pos,size)区间中的元素整体往后搬移一个位置
//i:表示需要搬移的元素的下标
for (int i = s->size - 1; i >= pos; --i)
s->array[i + 1] = s->array[i];
s->array[pos] = data;
s->size++;
}
//任意位置删除
void SeqListErase(SeqList* s, int pos)
{
assert(s);
if (pos<0 || pos>=s->size)
return;
for (int i = pos + 1; i < s->size; ++i)
s->array[i - 1] = s->array[i];
s->size--;
}
//获取顺序表中有效元素个数
int SeqListSize(SeqList* s)
{
assert(s);
return s->size;
}
//获取顺序表的容量
int SeqListCapacity(SeqList* s)
{
assert(s);
return s->capacity;
}
//检测顺序表是否为空
int SeqListEmpty(SeqList* s)
{
assert(s);
return 0 == s->size;
}
//查找值为data的元素是否在顺序表中,如果在返回下标,否则返回-1
int SeqListFind(SeqList* s, SDataType data)
{
assert(s);
for (int i = 0; i < s->size; ++i)
{
if (s->array[i] == data)
return i;
}
return -1;
}
//清空
void SeqListClear(SeqList* s)
{
assert(s);
s-> size = 0;
}
//销毁
void SeqListDestroy(SeqList* s)
{
assert(s);
if (s->array)
{
free(s->array);
s->array = NULL;
s->capacity = 0;
s->size = 0;
}
}
顺序表的缺点
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200, 我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
如何解决这些问题,接下来的文章我会进一步讲解链表。