顺序表是线性表的一种,它是用一段物理地址连续的空间来存储数据的一种线性结构,本质就是数组,顺序表有静态顺序表和动态顺序表,当空间不够时,动态顺序表可以实现增容,因此我们此处要实现的是动态顺序表的增删查改
头文件包含,函数声明
结构体的成员变量是我们要关注的重点, a指向了我们动态内存开辟空间的起始地址,size记录了顺序表中当前有多少个元素,capacity表示顺序表的容量,当元素个数达到容量时,我们便需要扩容,动态顺序表的意义也就在于此。
#define _CRT_SRECURE_NO_WARNINGFS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
#define DEFAULT_SIZE 3
#define INC_SIZE 2
typedef struct SeqList
{
SLDataType* a;
int size;
int capacity;
}SL;
//初始化
void SLInit(SL* pc);
//尾插
void SLPushBack(SL* pc, SLDataType x);
//打印
void SLPrint(SL* pc);
//头插
void SLPushFront(SL* pc, SLDataType x);
//销毁
void SLDestory(SL* pc);
//尾删
void SLPopBack(SL* pc);
//头删
void SLPopFront(SL* pc);
//任意位置插入
void SLInsert(SL* pc,int pos,SLDataType x);
//任意位置删除
void SLErase(SL* pc, int pos);
//查找
int SLFind(SL* pc, SLDataType x);
//修改
void SLModify(SL* pc, int pos, SLDataType x);
顺序表功能的实现
1.顺序表初始化
初始化的时候,我们选择开辟一块空间,默认大小为DEFAULT_SIZE, 然后把开辟空间的首地址放到a变量,容量改成DEFAULT_SIZE;
void SLInit(SL* pc)
{
pc->size = 0;
SLDataType* tmp = (SLDataType*)malloc(sizeof(SLDataType) * DEFAULT_SIZE);
if (tmp == NULL)
{
perror("malloc fail\n");
return;
}
pc->a = tmp;
pc->capacity = DEFAULT_SIZE;
}
2.尾插
由于插入操作会增加顺序表元素,因此判断顺序表是否已满使我们每次要判断的,而且满的话是需要扩容的,因此我们把其封装成函数
······判断顺序表是否为空:
void SLCheckCapacity(SL* pc)
{
assert(pc);
if (pc->size == pc->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(pc->a, sizeof(SLDataType) * (pc->capacity + INC_SIZE));
if (tmp == NULL)
{
printf("%s", strerror(errno));
return;
}
pc->a = tmp;
pc->capacity += INC_SIZE;
}
}
重点关注判断顺序表已满的条件:元素个数 = 容量
这里扩容时,我们每次选择增加两个元素,定义变量为 INC_SIZE
·······尾插:
void SLPushBack(SL* pc, SLDataType x)
{
assert(pc);
SLCheckCapacity(pc);
pc->a[pc->size] = x;
pc->size++;
}
注意,size是当前顺序表元素个数,也恰好是要尾插元素的下标
3.打印顺序表
void SLPrint(SL* pc)
{
assert(pc);
for (int i = 0;i < pc->size;i++)
{
printf("%d ", pc->a[i]);
}
}
4.销毁顺序表
void SLDestory(SL* pc)
{
assert(pc);
pc->size = 0;
pc->capacity = 0;
free(pc->a);
pc->a = NULL;
}
5.头插
void SLPushFront(SL* pc, SLDataType x)
{
assert(pc);
SLCheckCapacity(pc);
int end = pc->size - 1;
while (end >= 0)
{
pc->a[end + 1] = pc->a[end];
end--;
}
pc->a[0] = x;
pc->size++;
}
6.尾删
注意尾删的限制条件,必须有元素才能尾删(size > 0),否则会导致 size 的值 变为负数,产生数组越界行为,可以采用暴力方式进行断言,也可以使用if条件进行判断
void SLPopBack(SL* pc)
{
assert(pc);
//法一:
if (pc->size > 0)
{
pc->size--;
}
//法二:
/*assert(pc->size);
pc->size--;*/
}
7.头删
注意头删条件,和尾删一样,不再赘述
void SLPopFront(SL* pc)
{
if (pc->size > 0)
{
int start = 0;
while (start < pc->size - 1)
{
pc->a[start] = pc->a[start + 1];
start++;
}
pc->size--;
}
}
8.任意位置插入
void SLInsert(SL* pc, int pos, SLDataType x)
{
assert(0 <= pos <= pc->size - 1);
SLCheckCapacity(pc);
int end = pc->size - 1;
while (end >= pos)
{
pc->a[end + 1] = pc->a[end];
end--;
}
pc->a[pos] = x;
pc->size++;
}
9.任意位置删除
void SLErase(SL* pc, int pos)
{
assert(pos>=0 && pos<pc->size);
//assert(pc->size >= 0); 在这里面这句话没用
int start = pos+1;
while (start < pc->size)
{
pc->a[start-1] = pc->a[start];
start++;
}
pc->size--;
}
10.查找元素
int SLFind(SL* pc, SLDataType x)
{
for (int i = 0;i < pc->size;i++)
{
if (x == pc->a[i])
{
return i;
}
}
return -1;
}
11.修改
void SLModify(SL* pc, int pos, SLDataType x)
{
assert(pos >= 0 && pos < pc->size);
pc->a[pos] = x;
}
顺序表功能的测试
#include "SeqList.h"
int main()
{
//SL s1;
//SLInit(&s1);
//SLPushBack(&s1, 1);
//SLPushBack(&s1, 2);
//SLPushBack(&s1, 3);
//SLPushBack(&s1, 4);
SLPrint(&s1);
///*printf("\n");*/
SLPushFront(&s1, 3);
SLPushFront(&s1, 2);
SLPushFront(&s1, 1);
///*SLPrint(&s1);*/
SLPopBack(&s1);
SLPopBack(&s1);
SLPopBack(&s1);
SLPopBack(&s1);
SLPopBack(&s1);
///*SLPushBack(&s1, 5);*/
//SLPrint(&s1);
//SLPopFront(&s1);
//SLPopFront(&s1);
//SLPopFront(&s1);
//SLPopFront(&s1);
//SLPopFront(&s1);
//printf("\n");
//SLPrint(&s1);
//SLPushBack(&s1, 6);
//SLDestory(&s1);
SL s2;
SLInit(&s2);
SLPushBack(&s2, 1);
SLPushBack(&s2, 2);
SLPushBack(&s2, 3);
SLPushBack(&s2, 4);
SLPushBack(&s2, 5);
SLPushBack(&s2, 6);
SLPrint(&s2);
/*SLErase(&s2, 2);
printf("\n");
SLPrint(&s2);
SLPopBack(&s2);
printf("\n");
SLPrint(&s2);
SLModify(&s2, 2, 10);
printf("\n");
SLPrint(&s2);
int pos = SLFind(&s2, 10);
SLModify(&s2, pos, 20);
printf("\n");
SLPrint(&s2);*/
}
顺序表整体来说是比较简单的,关键就是对数组和指针的掌握,同时应该注意一些可能出错的细节,如数组越界,挪动数据时循环的结束条件等等。
顺序表示有它的优点的,但它的缺点也很明显,比如说每次都要申请固定内存的空间,申请小了不够用导致频繁扩容,申请大了空间浪费;还有就是头插时挪动数据消耗很多。
因此我们后面会引出链表这样的数据结构,会弥补这些缺点。