顺序表是数据结构里最基础的,今天分享关于顺序表的一些操作:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
因为顺序表又分为静态和动态,而静态的顺序表使用的是定长数组存储,而动态顺序表使用动态开辟的数组存储,生活中常常不知道数据规模的时候居多,需要使用的时候动态扩容,所以静态顺序表局限性更大。
下面就主要讲解动态顺序表:
1.首先定义一个动态顺序表
typedef struct SeqList{
int * arr; //定义一个指针指向动态开辟的数组
int size; //有效数据个数
int capacity; //容量空间大小
}SeqList;
2.下面是一些接口的实现
1.打印顺序表
void SeqListprint(SeqList *ps)
{
if (ps->size == 0)
{
return;
}
for (int i = 0; i < ps->size; i++) //就是纯遍历一遍
{
printf("%d ",ps->arr[i]);
}
}
2.顺序表扩容
static void CheckCapacity(SeqList *ps)
{ //实际长度小于最大容量则不需扩容
if (ps->size < ps->capacity)
{
return;
}
//申请内存空间
int NewCapacity = (ps->capacity) * 2;
int* Newarray = (int*)malloc(sizeof(int)*NewCapacity);
assert(Newarray != NULL); //断言,申请失败则直接终止
//把所有数字搬到新顺序表中
for (int i = 0; i < ps->size; i++)
{
Newarray[i] = ps->arr[i];
}
//释放原来空间
free(ps->arr);
ps->arr = Newarray;
ps->capacity = NewCapacity;
}
3.顺序表的初始化
void SeqListInit(SeqList *ps, int capacity)
{
assert(ps != NULL);
if (ps != NULL)
{
ps->arr = (int*)malloc(sizeof(int)*capacity);
assert(ps->arr != NULL);
ps->size = 0;
ps->capacity = capacity;
}
}
4.销毁顺序表
void SeqListDestroy(SeqList *ps)
{
assert(ps != NULL);
free(ps->arr);
}
5.顺序表的头插
时间复杂度O(n)
void SeqListPushFront(SeqList *ps, int v)
{
CheckCapacity(ps); //首先检查是否需要扩容
for (int i = ps->size - 1; i >= 0; i--)
{
ps->arr[i + 1] = ps->arr[i]; //从后面一直到前面的元素每个一次向后挪一位
}
ps->arr[0] = v;
ps->size++; //size++一定不要忘了
}
6.顺序表的尾插
void SeqListPushBack(SeqList *ps, int v)
{
CheckCapacity(ps); //检查是否需要扩容
ps->arr[ps->size] = v;
ps->size++;
}
7.根据pos下标插入
时间复杂度O(n)
void SeqListInsert(SeqList *ps, int pos, int v)
{
CheckCapacity(ps); //检查是否需要扩容
assert(pos >= 0 && pos <= ps->size); //pos=0就是头插,pos=size就是尾插
for (int i = ps->size - 1; i >= pos; i--)
{
ps->arr[i + 1] = ps->arr[i]; //最后一个元素一直到pos(包括pos)依次向后挪一位
}
ps->size++;
ps->arr[pos] = v;
}
8.顺序表的尾删
void SeqListPopBack(SeqList* ps)
{
assert(ps->size > 0);
ps->size--;
}
9.顺序表的头删
时间复杂度O(n)
void SeqListPopFront(SeqList *ps)
{
assert(ps->size > 0); //先判断是否有元素
for (int i = 0; i <= ps->size - 2; i++)
{
ps->arr[i] = ps->arr[i + 1]; //第二个元素一直到最后一个元素依次向前挪一位
}
ps->size--;
}
10.在指定pos处删除元素
时间复杂度O(n)
void SeqListErase(SeqList *ps, int pos)
{
assert(pos >= 0 && pos< ps->size); //检查pos是否在有效范围内
for (int i = pos; i <= ps->size - 2; i++)
{
ps->arr[i] = ps->arr[i + 1]; //pos后的元素依次向前挪一位
}
ps->size--;
}
11.查找指定元素并返回下标
int SeqListFind(SeqList *ps, int v)
{
assert(ps->size>0);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == v)
{
return i;
}
}
return -1;
}
12.修改指定位置的元素
int SeqListModify(SeqList *ps, int pos, int v)
{
assert(pos >= 0 && pos < ps->size); //判断pos是否在有效范围内
ps->arr[pos] = v;
return 1;
}
13.删除第一个值为指定值的元素
void SeqListRemove(SeqList *ps, int v)
{
int dis = SeqListFind(ps,v); //首先查找是否有这个元素
if (dis == -1)
{
return;
}
SeqListErase(ps, dis);
}
14.删除所有值为指定值的元素
//这是个比较巧妙的方法
void SeqListRemoveAll(SeqList *ps, int v)
{
int i, j;
for (i = 0, j = 0; i < ps->size; i++)
{
if (ps->arr[i] != v) //定义i,j两个变量做下标
{
ps->arr[j] = ps->arr[i]; //arr[i]!=v则把arr[i]赋给arr[j]
j++; //j每次记录的是值不为v的下标
}
}
ps->size = j; //j即为最后顺序表的长度
}
3.小结
顺序表的大部分操作都如上所示,其中比较重要的就是顺序表的扩容,头插,尾插,头删,尾删,要真正理解他每个操作实现的过程,死记代码里的循环条件那是没用的,条件都是可以变的,顺序表的下标随机访问很方便,这是他的一大优点。