今天我们来学习数据结构中的线性表,本文主要介绍一种常见的线性表——顺序表。
本文着重介绍顺序表的概念以及顺序表各种基本操作的实现过程(C语言实现),以后会更新更多的数据结构,觉得有用的朋友可以三连关注一波,一起学习。
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结
构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物
理上存储时,通常以数组和链式结构的形式存储。
接下来就先给大家介绍顺序表的相关知识。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般分为:
1.静态顺序表:使用定长数组存储元素。
2.动态顺序表:使用动态开辟的数组存储。
使用定长数组存储元素。
//顺序表的静态存储
#define N 100
typedef int SLDateType;
//静态顺序表 - N太小,可能不够用,N太大,可能造成内存浪费
struct SeqList
{
SLDateType a[N];//定长数组
int size;//有效数据个数
};
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
使用动态开辟的数组存储。
//顺序表的动态存储
typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;//指向动态数组的指针
int size;//有效数据个数
int capacity;//容量空间的大小
}SL;
顺序表能够实现尾部插入删除,头部插入删除,查找,修改等功能。接下来我们就来实现顺序表的各种接口功能。
不论怎么样,第一步都应该是初始化数据,以便其他功能正常使用。
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
因为我们初始化时并没有赋予初始大小,所以第一步就应该检查空间,如果空间不足就扩容,后面的每次插入操作,都要检查空间大小。
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = ((ps->capacity == 0) ? 4 : (ps->capacity * 2));
SLDateType* tmp = (SLDateType*)realloc(ps->a, newcapacity * (sizeof(SLDateType)));
if (tmp == NULL)
{
perror("realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
printf("扩容成功\n");
}
}
在顺序表最后的位置插入一个数据,实现的时候要主要先检查空间大小,插入成功后,有效数据个数要加1。
void SLPushBack(SL* ps, SLDateType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
直接让有效数据个数减1即可,但要注意,有效数据个数不能小于0,否则再进行插入就会出现越界访问的问题。
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--;
}
首先检查空间大小,然后让所以数据向后移动一个位置即可,但要注意数据移动时的先后顺序,应该从最后一个数据开始移动,依次向前,这样可以防止数据被覆盖导致程序出现错误,最后让有效数据个数加1。
void SLPushFront(SL* ps, SLDateType x)
{
assert(ps);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
这里可以让第2个数据开始依次向前覆盖,最后有效数据个数减1即可。
void SLPopFront(SL* ps)
{
assert(ps);
int begin = 0;
while (begin < ps->size - 1)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
}
任意位置插入与头部插入类似,只不过是起始位置改变了,整体思想不变。
任意位置删除也与头部删除类似,改变起始位置即可,但在插入和删除时要注意输入的位置要在有效范围内。
任意位置插入:
void SLInsert(SL* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
任意位置删除:
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size - 1);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
顺序表的查找和修改非常简单,查找只需要遍历数据,如果找到,返回下标即可,修改时只要修改位置是合法的,直接修改数据即可。
查找:
int SLFind(SL* ps, SLDateType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
修改:
void SLModify(SL* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size - 1);
ps->a[pos] = x;
}
顺序表打印和销毁也比较简单,打印时只要遍历数据并依次打印即可。
因为我们顺序表的空间是动态开辟的,所以使用完一定要销毁,释放掉动态开辟的内存,避免出现内存泄漏的问题。
打印:
void SLPrint(SL* ps)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
销毁:
void SLDestory(SL* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
}
到这里,顺序表基本操作的实现就介绍完了,顺序表是数据结构里比较简单的一种结构,所以内容不是特别多,如果大家看完觉得有收获的话可以三连关注一波,十分感谢各位朋友的支持,最后,感谢大家的耐心阅读。