关于线性表的介绍如下:
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
而今天我们要学习的就是线性表中最简单的顺序表,因为顺序表不管是在逻辑上还是在物理上都和数组差不多,所以一般情况下采用数组进行存储。顺序表的逻辑图大致如下所示:
顺序表要实现的功能大致如下:
// 初始化顺序表,初始化容量为10
void init_seq_list(SL* pc);
// 顺序表的尾插
void seq_list_push_back(SL* pc, data_type x);
// 顺序表的尾删
void seq_list_pop_back(SL* pc);
// 顺序表的打印
void print_seq_list(SL* pc);
// 顺序表的增容
void seq_list_check_up(SL* pc);
// 顺序表的头插
void seq_list_push_front(SL* pc, data_type x);
// 顺序表的头删
void seq_list_pop_front(SL* pc);
// 顺序表的随机插入
void seq_list_insert(SL* pc, int pos, data_type x);
// 顺序表的随机删除
void seq_list_remove(SL* pc, int pos);
// 顺序表的修改
void modify_seq_list(SL* pc, int pos, data_type x);
// 顺序表的销毁
void destroy_seq_list(SL* pc);
我们先来定义一个顺序表的结构体类型,其中包含了节点类型指针,顺序表长度和顺序表容量:
typedef struct seq_list {
data_type* data;
int num; // 标记当前顺序表中的元素个数
int capacity; // 标记当前顺序表的容量
} SL;
将节点类型使用typedef重命名主要是为了将来修改的时候方便,这里先将类型定义成int:
typedef int data_type;
创建好了类型之后我们就要对其进行初始化了,我们先初始化容量为10。
这个操作只需要使用malloc函数来申请一块10个data_type类型大小的空间即可:
// 初始顺序表,初始化容量为10
void init_seq_list(SL* pc) {
assert(pc);
data_type* temp = (data_type*)malloc(10 * sizeof(data_type));
if (NULL == temp) {
perror("malloc");
return;
}
pc->data = temp;
pc->num = 0;
pc->capacity = 10;
}
顺序表的尾插即在顺序表的尾部插入一个节点
如果我们将顺序表的下标设计成从0开始,那我们的尾插就很简单了,因为这样的话成员变量num的值就是每次尾插要插入的坐标的值:
插入后再让num++即可。
void seq_list_push_back(SL* pc, data_type x) {
assert(pc);
// 先检查是否需要增容
seq_list_check_up(pc);
pc->data[pc->num] = x;
pc->num++;
}
(增容后面会说)
顺序表的尾删即删除顺序表尾部的节点
实现这个功能其实我们只需要让num–即可,因为我们顺序表的可用长度是通过num来控制的,后面其实还可能会有空间的,但我们不管就行了:
void seq_list_pop_back(SL* pc) {
assert(pc);
// 判断顺序表是否为空
if (pc->num <= 0) {
printf("顺序表已空,没有信息可删除……\n");
return;
}
pc->num--;
}
有了插入和删除的功能,那我们就要来测试一下,所以我们先写一个打印函数,打印出信息来测试:
void print_seq_list(SL* pc) {
assert(pc);
if (pc->num <= 0) {
printf("顺序表已空,没有信息可显示……\n");
return;
}
int i = 0;
for (i = 0; i < pc->num; i++) {
printf("[%d]", pc->data[i]);
if (i < pc->num - 1) {
printf("-->");
}
}
printf("\n");
}
然后我们就可以来测试一下我们的尾插和尾删功能了,先插入几个节点:
再删除几个节点:
从结果可以看出,我们的代码是没有什么问题的。
我们顺序表的空间总归是有限的,所以随着我们不断的插入节点,顺序表就有可能满。所以我们就需要对顺序表进行增容,我们默认每次都增容增到原来的二倍。
这个操作不用多说,交给realloc函数完成即可:
void seq_list_check_up(SL* pc) {
assert(pc);
if (pc->num == pc->capacity) {
int new_capacity = pc->capacity * 2;
data_type* temp = (data_type*)realloc(pc->data, new_capacity * sizeof(data_type));
if (NULL == temp) {
perror("malloc");
return;
}
pc->data = temp;
pc->capacity = new_capacity;
}
}
顺序表的头插即在顺序表的头部插入节点
为了保证插入后其他元素的相对位置不变且连续,我们需要先将原来的所有元素都向后移动一位,然后再将data[0]改成要插入的数据并让num++:
而且要注意的是,这个操作必须从后往前移动,因为从前往后移动的话,移动一个后,后面的元素也就被覆盖了。
void seq_list_push_front(SL* pc, data_type x) {
assert(pc);
// 还是先检查是否需要增容
seq_list_check_up(pc);
int end = pc->num;
// 将所有元素全都向后移一位
while (end > 0) {
pc->data[end] = pc->data[end - 1];
end--;
}
pc->data[0] = x;
pc->num++;
}
顺序表的头删即删除顺序表头部的节点
完成这个操作我们只需要将头元素之后的元素全都先前移动一个位置,覆盖掉前一个元素即可:
最后再让num–。
void seq_list_pop_front(SL* pc) {
assert(pc);
// 判断顺序表是否为空
if (pc->num <= 0) {
printf("顺序表已空,没有信息可删除……\n");
return;
}
int i = 0;
for (i = 0; i < pc->num - 1; i++) {
pc->data[i] = pc->data[i + 1];
}
pc->num--;
}
通过下标插入就可实现在任意位置插入数据
其实和头插的原理一样,我们需要先将从坐标pos起的元素全都向后移动一位,然后再将数据放入到data[pos],只不过是头插的pos刚好等于0:
然后再让num++即可。
void seq_list_insert(SL* pc, int pos, data_type x) {
assert(pc);
// 先判断坐标pos的合法性
if (pos < 0 || pos > pc->num) {
printf("坐标非法!\n");
return;
}
// 先将pos位置后面的元素都向后移动一位
int end = pc->num;
while (end > pos) {
pc->data[end] = pc->data[end - 1];
end--;
}
pc->data[pos] = x;
pc->num++;
}
注意这里在做坐标判断的时候用的还是pos > pc->num而不是pos >= pc->num。这是因为pos是可以等于num的,当pos等于num时,达到的效果就和尾插一样了。
通过坐标来删除元素就可以实现随即删除了
其实原理也是跟头删一样,只不过头删的pos刚好等于0,我们还是只需要将pos后面的元素都向前移动一位并让num–即可:
在这里插入图片描述
void seq_list_remove(SL* pc, int pos) {
assert(pc);
// 先判断坐标pos的合法性
if (pos < 0 || pos > pc->num - 1) {
printf("坐标非法!\n");
return;
}
int i = 0;
for (i = pos; i < pc->num - 1; i++) {
pc->data[i] = pc->data[i + 1];
}
pc->num--;
}
而我们这里的坐标判断就要改成pos > pc->num - 1因为顺序表中的节点的坐标最大就只能到达num - 1。
我们通过坐标定位节点,然后修改其数据即可:
void modify_seq_list(SL* pc, int pos, data_type x) {
assert(pc);
// 先判断坐标pos的合法性
if (pos < 0 || pos > pc->num - 1) {
printf("坐标非法!\n");
return;
}
pc->data[pos] = x;
}
这个操作其实就是用free函数将data给释放了:
void destroy_seq_list(SL* pc) {
assert(pc);
free(pc->data);
pc->data = NULL;
pc->num = 0;
pc->capacity = 0;
}