目录
想要学习顺序表,我们首先需要了解一下线性表。
1.线性表的概念及分类
线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表,链表,栈,队列,字符串......线性表在逻辑上是线性结构,也就是说是一条连续的直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表,是线性表的一种。顺序表在逻辑结构上是连续的,在物理结构上不一定连续。
2.顺序表分类
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。
顺序表分为静态顺序表和动态顺序表。
(1)静态顺序表
静态顺序表是使用定长数组储存元素
typedef int SLDataType;//使顺序表中的数据类型可灵活更改
#define N 100
//静态顺序表
struct SeqList
{
int arr[N];
int size;//有效数据个数
};
静态顺序表的缺陷:空间给少了不够用,给多了造成空间浪费。
(2)动态顺序表
typedef int SLDataType;//使顺序表中的数据类型可灵活更改
//动态顺序表
typedef struct SeqList
{
SLDataType* arr;
int size;//有效数据个数
int capacity;//顺序表空间大小
}SL;//重命名
动态顺序表可以动态的申请内存,使内存能够充分的被利用,不至于造成空间浪费。
3.动态顺序表的实现
(1)顺序表的初始化
在初始化顺序表时,我们应将顺序表中的数组指针置为空,再将有效数据个数size及顺序表空间大小capacity置为零
//顺序表的初始化
void SLInit(SL *ps)
{
ps->arr = NULL;
ps->size =ps->capacity = 0;
}
(2)顺序表的销毁
当我们使用完顺序表后需要及时对顺序表进行销毁,不然会导致空间的泄露,十分危险。首先我们需要判断数组指针arr是否为空指针,如果不是空指针,需要先把数组指针释放内存。再将数组指针置为空指针,最后再把有效数据个数size和顺序表空间大小capacity置为零。
//顺序表的销毁
void SLDestroy(SL* ps)
{
if (ps->arr)//等价于 if(ps->arr !=NULL)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
(3)顺序表的打印
当我们以顺序表为基础实现一个通讯录项目时我们需要用到顺序表的打印。因为顺序表在打印时只需要打印出数组指针中的内容,且顺序表的底层逻辑是数组,所以我们只需要运用for循环打印即可。
//顺序表的打印
void SLPrint(SL s)
{
for (int i = 0; i < s.size; i++)
{
printf("%d", s.arr[i]);
}
}
(4)顺序表的动态增容
当我们想要在顺序表中增加数据时,应该先判断顺序表中的空间是否足够,如果空间足够,直接将数据插入即可,如果空间不够,就需要进行顺序表的动态增容。
在增容时首先判断原顺序表空间大小是否为零,若为零则初始化4个空间,若不为零则直接二倍增容,并将增容后的空间存在newcapacity中。
在申请空间时我们选择的是realloc函数,因为realloc函数可以做到对动态开辟内存大小的调整。
在使用realloc函数时,若在堆区中没有合适的空间,则会扩容失败,为了防止扩容失败后直接将原顺序表置为NULL,导致顺序表中的数据丢失,我们可以先用一个指针判断是否增容成功,若增容成功则将该数组指针赋值给原数组指针并将新的空间大小newcapacity赋值给原空间大小capacity。若增容失败则进行报错并退出程序。
//判断空间大小是否足够
void SLCheckCapacity(SL* ps)
{
//插入数据之前先看空间大小是否足够
if (ps->capacity == ps->size)
{
//申请空间
//增容realloc
//判断原顺序表空间大小是否为零,若为零则初始化,若不为零则直接二倍增容
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//realloc可能会扩容失败,为防止扩容失败后直接将原顺序表置为NULL,需要先用一个指针判断是否扩容成功
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//通常2倍增容;需要类型强转
//空间申请失败
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
//空间申请成功
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
(5)顺序表的数据插入
插入数据分为三种方法,头插,尾插和指定位置插入。
1.头插
头插,顾名思义就是在数组下标为0的位置插入数据,那么首先我们需要先判断顺序表中的空间是否足够,然后利用for循环将顺序表内的数据都向后挪动一位,然后再将数据插入数组下标为零的位置,最后,记得将顺序表中有效数据个数加一。
//头部插入
void SLPushFront(SL* ps, SLDataType x)
{
//插入前先判断空间是否足够
SLCheckCapacity(ps);
//先让顺序表中的已有数据整体向后挪动一位
for (int i = ps->size;i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
2.尾插
尾插,就是在数组最后一位插入数据,因为顺序表中已经存储了顺序表的有效数据个数size,所以只需要在数组下标为size的地方插入数据即可。最后再将有效数据个数加一。
插入数据有两种方法:
方法一:先在下标为size的地方插入数据,然后将size前置++。
方法二:先将size后置++,再在下标为size的地方插入数据。
//尾部插入
void SLPushBack(SL* ps, SLDataType x)
{
//判断ps是否为空指针
//方法一
//if (ps == NULL)
//{
// return;
//}
//方法二
assert(ps);//等价于assert(ps!=NULL);
//插入数据之前先看空间大小是否足够
SLCheckCapacity(ps);
//方法一 前置++
//ps->arr[ps->size] = x;
//++ps->size;
//方法二 后置++
ps->arr[ps->size++] = x;
}
3.在指定位置插入数据
首先需要判断我们想要插入数据的位置下标是否在0~size之间,如果范围正确才可以插入数据。然后利用for循环,将指定位置后的数据均向后挪动一位,再将数据插入指定位置中,最后记得将有效数据个数加一。
//在指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
int i = 0;
for (i = ps->size; i < pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
(6)顺序表的数据删除
删除数据分为三种方法,头删,尾删,指定位置删除。
1.头删
头删,就是把数组中除下标为0以外的数据向前挪动一位,将下标为0的数据覆盖掉就可以实现数据的头删,最后记得将有效数据个数减一。
//头部删除
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
//数据整体向前挪动一位
for (int i = 0; i<ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];//arr[size-2]=arr[size-1]
}
ps->size--;
}
2.尾删
尾删,就是将数组最后一个数据删除,所以,直接将顺序表的有效数据个数减一就可以了。
//尾部删除
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->arr);
--ps->size;
}
3.在指定位置删除数据
想要删除指定位置的数据只需要将指定位置后的数据向前挪动一位,最后再将有效数据个数减一就好了。
//在指定位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int i = 0;
for (i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
(7)在顺序表中查找数据
查找数据的时候需要将顺序表从头到尾遍历一遍,如果顺序表中有数据与查找数据相同的话则返回该数据的下标,如果没有则返回一个无意义的数据,例如-1.
//顺序表查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i]==x)
{
return i;
}
}
return -1;
}
顺序表的基本实现思路就讲完了,大家可以应用顺序表的知识思考一下,自行实现通讯录的程序。