在今天的学习中,将会介绍线性表的概念,以及顺序表的增删查改功能的实现,以及分析利弊
线性表
线性表是n个具有相同特性的数据元素的有限序列,是一种广泛使用的数据结构,常用的线性表有:顺序表、链表、栈、队列等等
线性表在逻辑上是线性结构,也就是一条连续的直线,但是实际上他有可能是连续的,还有可能是链接在一起的,例如链表。
顺序表
顺序表是用一段连续的存储单元依次存储数据元素的线性结构,听着很像数组,数组是连续的,而且是n个相同元素的集合,而事实上也是如此,顺序表就是用数组存储的,在数组上完成增删查改的工作。
顺序表又分为静态顺序表和动态顺序表,静态顺序表就是使用定长数组存储元素,动态就是随时扩容,两个都不难,咱直接用写动态顺序表,就把静态包括了。
动态顺序表实现过程
首先我们要有一个数据元素类型,这里我们可以采用结构体,因为我们要存储顺序表的地址,顺序表的有效元素有多少,因为是需要扩容的,我们还要知道顺序表现在的容量有多少。
typedef int DataType;
typedef struct SeqList
{
DataType* arr; //存储动态开辟数组首地址
size_t size; //有效数据个数
size_t capicity; //容量
}SeqList;
这里还重命名了int,实际上这是为了顺序表的通用性着想,如果我们原本顺序表中储存的是int类型的数据,我们现在不想让他存int了,想让它存字符类型的数据,这时候我们不需要把所有原本是int类型的元素都改成char,只需要把上面的int重命名变成char的重命名就可以了。
下面就进入到顺序表功能的实现:
1. 顺序表的初始化
2. 顺序表的销毁
3. 顺序表扩容
4. 顺序表的头插
5. 顺序表的头删
6. 顺序表的尾插
7. 顺序表的尾删
8. 顺序表的查找
9. 任意位置插入
10. 任意位置的删除
11. 打印链表
1.顺序表的初始化
void SeqListInit(SeqList* ps)
{
assert(ps);
ps->arr= NULL;
ps->capicity= 0;
ps->size = 0;
}
这个没什么说的,我们让指向数组的指针先为空,并给其他值初始化。
2.顺序表的销毁
void SeqListDestroy(SeqList* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capicity = ps->size = 0;
}
销毁同样很好理解,注意前面的assert 首先储存顺序表信息的结构体指针要存在,不存在怎么销毁呢?因此我们使用断言 ,然后我们释放顺序表的值,因为本来就是动态申请来的内存,用free就直接释放掉了,然后把顺序表的容量以及有效数据数量变为0.
3.顺序表的扩容
void CheckCacpity(SeqList *ps)
{
if(ps->size == ps->capicity)
{
size_t newcapacity = ps->capicity == 0 ? 4 : ps->capicity*2;
ps->arr = (DataType*)realloc(ps->arr,newcapacity * sizeof(DataType));
ps->capicity = newcapacity;
}
}
这个要解释一下,realloc是什么意思,我们知道malloc是申请一片空间,然后把地址存下来,relloc是什么呢,relloc是对你原有的空间进行扩容,如果当前所在的这片空间装不下了,那就是换到另一片重充足的空间,然后把空间原有的内容拷贝进去。
在这个函数中就是,如果有效数字和容量相同了,这时候代表我们的空间已经满了,这就需要扩容,扩多少呢?扩大一倍。
4.顺序表任意位置插入
我们先不去看头插和尾插,我们先来看任意位置的插入,任意位置的插入知道了,那头插不就是第一个位置的插入和最后一个位置的插入吗,直接复用任意位置插入函数就可以了。
void SeqListInsert(SeqList* ps,size_t pos,DataType x)
{
assert(ps);
assert(pos <= ps->size);
CheckCacpity(ps);
size_t end = ps->size;
while(end > pos)
{
ps->arr[end] = ps->arr[end -1];
--end;
}
ps->arr[pos] = x;
ps->size++;
}
在每次插入操作之前我们都要扩容,检测一下顺序表中的容量还够不够了。顺序表的插入很简单,就是从后往前,把pos后的顺序表整个后移一位
大概就这样,最后让pos位置的数据等于我们给的值,就可以了。
5.任意位置的删除
先说整个的原因和之前一样,方便复用。
void SeqListErase(SeqList* ps,size_t pos)
{
assert(ps);
assert(pos < pa->size);
size_t start = pos+1;
while(start < ps->size)
{
ps->arr[start - 1] = ps->arr[start];
++start;
}
ps->size--;
}
原理和插入差不多,只不过这回我们是让后一个数据覆盖前一个数据,这样到pos位置,pos位置的数据就被覆盖了。
6.头插
因为之前写了任意位置的插入,那现在就可以复用我们写过的函数了
void SeqlistPushFront(SeqList* ps,DataType x)
{
assert(ps);
SeqListInsert(ps, 0,x);
}
7.尾插
void SeqListPushBack(SeqList* ps, DataType x)
{
SeqListInsert(ps,ps->size,x);
}
8.头删
void SeqListPopFront(SeqList* ps)
{
assert(ps);
SeqListErase(ps,0);
}
9.尾删
void SeqListPopBack(SeqList*ps)
{
assert(ps);
SeqListErase(ps,ps->size-1);
}
10.查找
int SeqListFind(SeqList* ps,DataType x)
{
for(size_t i = 0; i < ps->size ;++i)
{
if(ps->arr[i]==x)
{
return i;
}
}
return -1;
}
查找就遍历一遍我们的顺序表就可以了,找到就返回下标,找不到就返回-1
11.打印
void SeqListPrint(SeqList* ps)
{
assert(ps);
for(size_t i = 0;i < ps->size ; ++i)
{
printf("%d",ps->arr[i]);
}
printf("\n");
}
也很简单,就是迭代打印。
测试
代码写完了,我们总要测试一下吧
#include"SeqList.h"
int main()
{
SeqList ps ;
SeqListInit(&ps);
SeqListInsert(&ps,0,1);
SeqListPushBack(&ps,2);
SeqListPushBack(&ps,2);
SeqListPushBack(&ps,2);
SeqListPushBack(&ps,2);
SeqListPrint(&ps);
return 0;
}
如果代码是正确的,应该打印出来12222,现在我们运行一下
可以看到啊,当我们编译完成运行的时候,他确实是12222,那我们的代码就成功了。
总结(顺序表优缺点)
那么到现在我们已经完成了顺序表的增删查改等功能的实现,有没有发现顺序表的优缺点呢?
优点:
可以直接访问到顺序表中的任意下标数据
缺点:
扩容时有可能要拷贝到新空间,释放旧空间,对空间消耗大,并且在我们扩容的时候,不一定所有空间都利用上了,可能会造成空间的浪费。
源码
SeqList.h
#ifndef __SeqList_h
#define __SeqList_h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct SeqList
{
DataType* arr; //存储动态开辟数组首地址
size_t size; //有效数据个数
size_t capicity; //容量
}SeqList;
void SeqListInit(SeqList* ps);
void SeqListDestroy(SeqList* ps);
void CheckCacpity(SeqList *ps);
void SeqListPushBack(SeqList* ps, DataType x);
void SeqlistPushFront(SeqList* ps,DataType x);
void SeqListPopFront(SeqList* ps);
void SeqListPopBack(SeqList*ps);
void SeqListPrint(SeqList* ps);
int SeqListFind(SeqList* ps,DataType x);
void SeqListInsert(SeqList* ps,size_t pos,DataType x);
void SeqListErase(SeqList* ps,size_t pos);
#endif
Seqfun.c
#include"SeqList.h"
void SeqListInit(SeqList* ps)
{
assert(ps);
ps->arr= NULL;
ps->capicity= 0;
ps->size = 0;
}
void SeqListDestroy(SeqList* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capicity = ps->size = 0;
}
void SeqListPrint(SeqList* ps)
{
assert(ps);
for(size_t i = 0;i < ps->size ; ++i)
{
printf("%d",ps->arr[i]);
}
printf("\n");
}
void CheckCacpity(SeqList *ps)
{
if(ps->size == ps->capicity)
{
size_t newcapacity = ps->capicity == 0 ? 4 : ps->capicity*2;
ps->arr = (DataType*)realloc(ps->arr,newcapacity * sizeof(DataType));
ps->capicity = newcapacity;
}
}
void SeqListPushBack(SeqList* ps, DataType x)
{
SeqListInsert(ps,ps->size,x);
}
void SeqlistPushFront(SeqList* ps,DataType x)
{
assert(ps);
SeqListInsert(ps, 0,x);
}
void SeqListPopFront(SeqList* ps)
{
assert(ps);
SeqListErase(ps,0);
}
void SeqListPopBack(SeqList*ps)
{
assert(ps);
SeqListErase(ps,ps->size-1);
}
int SeqListFind(SeqList* ps,DataType x)
{
for(size_t i = 0; i < ps->size ;++i)
{
if(ps->arr[i]==x)
{
return i;
}
}
return -1;
}
void SeqListInsert(SeqList* ps,size_t pos,DataType x)
{
assert(ps);
assert(pos <= ps->size);
CheckCacpity(ps);
size_t end = ps->size;
while(end > pos)
{
ps->arr[end] = ps->arr[end -1];
--end;
}
ps->arr[pos] = x;
ps->size++;
}
void SeqListErase(SeqList* ps,size_t pos)
{
assert(ps);
assert(pos < ps->size);
size_t start = pos+1;
while(start < ps->size)
{
ps->arr[start - 1] = ps->arr[start];
++start;
}
ps->size--;
}
SeqList.c
#include"SeqList.h"
int main()
{
SeqList ps ;
SeqListInit(&ps);
SeqListInsert(&ps,0,1);
SeqListPushBack(&ps,2);
SeqListPushBack(&ps,2);
SeqListPushBack(&ps,2);
SeqListPushBack(&ps,2);
SeqListPrint(&ps);
return 0;
}