顺序表的基本功能:增删查改
我将对每一个接口的功能及实现方法进行介绍
顺序表的结构与数组相似,都可以通过对下标的访问,访问到数据
结构体的创建:
首先定义一个结构体类型,对属性进行添加,我的结构体中定义了一个地址(用来存放动态开辟空间的地址),如果需要的是简单的静态顺序表,可以将指针换做数组使用。接着定义一个capacity,表示容量(用于动态开辟内存的大小),一个size用于记录内存中有效数据的个数。第一个typdef的作用是:将数据类型进行重命名,在改变数据的类型时,将int改变即可。第二个typdef的作用:在C语言中在使用结构体变量时,必须加上struct,为了方便起见,将struct SeqList重命名为SeqList使用,效果与原来是等价的。最后创建了一个结构体类型的全局变量,为了方便能在整个代码中使用,创建临时变量也可。
typedef int SLDataType;
struct SeqList {
SLDataType *array; // 指针,指向存放数据的空间,真正的空间在堆上
int capacity; // 顺序表整体的容量
int size; // 顺序表中真正有效的个数
// 初始值为 0,同时也表示下一个有效位置的下标
};
typedef struct SeqList SeqList;
SeqList seqlist;
各个接口的功能及实现:
顺序表的初始化/销毁:
初始化:将结构体中的各个属性进行赋初值,array的初值为在堆上申请开辟capacity个大小为SLDataType的连续空间的首地址,并将申请到的所有空间初始化为0。
//Init
void SeqListInit(SeqList *sl, size_t capacity)
{
assert(sl);
sl->array = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
for (size_t i = 0; i < capacity; i++)
{
sl->array[i] = 0;
}
sl->size = 0;
sl->capacity = capacity;
return;
}
销毁:将结构体的各个属性变为初值,将堆上申请的空间进行释放。
//销毁
void SeqListDestroy(SeqList *sl)
{
assert(sl);
free(sl->array);
sl->size = 0;
sl->capacity = 0;
return;
}
插入:
尾插:在顺序表的尾部插入一个数据
如图所示将4插入下一个空间,每插入一个数据,size就会加1,所以size指向下一个存放数据的位置,所以将需要插入的数据data放入这个位置即可—sl->array[sl->size] = data。在插入结束后必须对size进行更新(sl->size++)。
注意:将数据插入,需要先考虑下一个空间是不是有效空间,如果对无效空间进行赋值就是未定义行为。所以需要一个单独的接口判断是否需要扩容。我们后边会讲到这个接口(CheckCapacity),暂时先使用。
void SeqListPushBack(SeqList *sl, SLDataType data)
{
assert(sl);
CheckCapacity(&seqlist);
sl->array[sl->size] = data;
sl->size++;
return;
}
头插:将数据插入到第一个位置
如图所示,在插入前,我们应该将里面已有的值进行搬移,否则将导致一些值被覆盖丢失,所以应该从后向前将已有的数字进行搬移,再将data插入到下标为0的空间。结束后更新size。
注意:在开始搬移前先对容量进行检查。当有效值为0个时,头插就相当于尾插。
//头插,插入在顺序表的头部
void SeqListPushFront(SeqList *sl, SLDataType data)
{
assert(sl);
CheckCapacity(&seqlist);
for (int i = sl->size; i > 0; i--)
{
sl->array[i] = sl->array[i-1];
}
sl->array[0] = data;
sl->size++;
return;
}
在下标为pos的地方进行插入:将数据插入到下标为pos的空间
如图所示,在pos(下标为2)的地方插入一个data。类似于头插,先将需要插入的地方空出来,然后将data放入。结束时更新size。
注意:要先对pos的有效范围进行确定,pos应该大于等于0,小于size。应该对异常进行相应的处理,为了方便起见,我选择用assert直接断言,不准输入异常值,否则直接让程序崩溃。仍然应该对容量进行检查。当pos的值等于0时,就是对顺序表进行头插,当pos的值等于size是就是对顺序表进行尾插,
//在pos位置进行插入
void SeqListInsert(SeqList *sl, size_t pos, SLDataType data)
{
assert(sl);
assert((int)pos >= 0 && (int)pos <=sl->size);
CheckCapacity(&seqlist);
if (pos == 0)
{
SeqListPushFront(&seqlist,data);
return;
}
if (pos == sl->size)
{
SeqListPushBack(&seqlist,data);
return;
}
for (int i = sl->size; i > (int)pos; i--)
{
sl->array[sl->size] = sl->array[sl->size - 1];
}
sl->array[pos] = data;
sl->size++;
return;
}
检测是否需要扩容:
因为是动态开辟的内存,所以我们在插入的时候,必须先判断插入后申请来的内存是否够用,如果够用,不做任何操作,直接返回;如果不够用就需要动态开辟一块更大的内存将原来的数据进行搬移。在每一次插入中都要进行扩容检测的操作。
注意:在申请到新的内存空间,搬移后需要对旧的空间进行及时释放,否则就属于内存泄漏。在释放时记得先对旧的空间进行释放,然后对新地址进行赋值,否则会导致找不到旧的空间了,也属于内存泄漏。用一个新地址记录旧的地址进行释放也可以。开辟结束后要对地址和容量进行更新。
//检测是否需要扩容
void CheckCapacity(SeqList *sl)
{
assert(sl);
if (sl->size < sl->capacity)
{
return;
}
SLDataType newCapacity = sl->capacity * 2;
SLDataType *newarray = (SLDataType*)malloc(sizeof(SLDataType) * newCapacity);
if (newarray == NULL)
{
printf("内存开辟失败\n");
return;
}
for (int i = 0; i < sl->size; i++)
{
newarray[i] = sl->array[i];
}
free(sl->array);
sl->array = newarray;
sl->capacity = newCapacity;
newarray = NULL;
return;
}
删除:
尾删:删除顺序表的最后一个数据
尾删的思路是非常简单的,我只要将最后一个数据认为是无效的数据,就可以完成删除。在顺序表中,有效数据由size进行控制。所以size-1就完成了操作。
注意:当size的值为0时,不能进行删除。可以对这种异常进行相应的处理,为了方便起见,我使用了assert进行断言,不允许size为0的情况下,仍然进行删除。
//尾删
void SeqListPopBack(SeqList *sl)
{
assert(sl);
assert(sl->size != 0);
sl->size--;
return;
}
头删:删除顺序表的第一个数据
头删的思路就是将第一个元素后面的元素全部向前移动一个位置,再将顺序表中的有效长度减一(size-1)就完成了头删。
注意:当size的值为0时,不能进行删除。可以对这种异常进行相应的处理,为了方便起见,我使用了assert进行断言,不允许size为0的情况下,仍然进行删除。当顺序表中的元素只有一个时,我在这里调用了尾删的接口,不调用仍然是正确的。
//头删
void SeqListPopFront(SeqList *sl)
{
assert(sl);
assert(sl->size != 0);
if (sl->size == 1)
{
SeqListPopBack(&seqlist);
return;
}
for (int i = 0; i < sl->size-1; i++)
{
sl->array[i] = sl->array[i + 1];
}
sl->size--;
return;
}
在pos位置进行删除:将下标为pos位置的数据进行删除
在pos位置删除与在pos位置插入相似,pos位置插入是将pos位置空出来,删除则是将pos位置覆盖掉,所以应该将pos位置后的元素向前移动,然后将有效长度减一(size-1)。
***注意***在顺序表的长度为0时,不允许进行删除操作。当pos的值为0时调用头删操作,当pos的值为size-1时调用尾插操作。
//在pos的位置进行删除
void SeqListErase(SeqList *sl, size_t pos)
{
assert(sl);
assert(sl->size != 0);
assert((int)pos >= 0 && (int)pos < sl->size);
if (pos == 0)
{
SeqListPopFront(&seqlist);
return;
}
if (pos == sl->size - 1)
{
SeqListPopBack(&seqlist);
return;
}
for (int i = pos; i < sl->size - 1; i++)
{
sl->array[i] = sl->array[i + 1];
}
sl->size--;
return;
}
查找:在顺序表中查找返回第一个查询到的数据下标
查找的思路就是从前向后找,因为在这里顺序表还是无序的,在后面会讲到将顺序表重新排序后,利用二分查找进行查找,可以提高查找的效率。如果找到就返回数据所在的下标,如果找不到就返回-1.
//查找
int SeqListFind(SeqList *sl, SLDataType data)
{
assert(sl != NULL);
for (int i = 0; i < sl->size; i++)
{
if (sl->array[i] == data)
{
return i;
}
}
return -1;
}
删除(数据):根据数据在顺序表中找到第一个出现的进行删除
这个删除需要先进行查找,找到第一个相等的数据后,返回下标,然后根据这个下标删除对应的数据
//(数据)删除---先查找--根据下标删除
void SeqListRemove(SeqList *sl, SLDataType data)
{
assert(sl);
size_t ret = SeqListFind(&seqlist, data);
SeqListErase(&seqlist, ret);
return;
}
修改:根据下标修改对应位置的数据
修改操作非常简单,给定了下标,就根据这个下标对数据进行更改,需要注意的是要对pos的有效值进行判定,不可以对无效空间进行赋值。
//修改
void SeqListModify(SeqList *sl, size_t pos, SLDataType data)
{
assert(sl);
assert((int)pos >= 0 && (int)pos < sl->size);
sl->array[pos] = data;
return;
}
冒泡排序:将顺序表排序,为二分查找提供前提
Swap函数被static修饰,表示的是此函数为此源文件私有,不能被其他源文件调用。作用是交换两个数
//冒泡排序,为二分查找做准备
static void Swap(SLDataType *x, SLDataType *y)
{
SLDataType tmp = *x;
*x = *y;
*y = tmp;
return;
}
void SeqListBubbleSort(SeqList *sl)
{
assert(sl);
for (int i = 0; i < sl->size - 1; i++)
{
for (int j = 0; j < sl->size - 1 - i; j++)
{
if (sl->array[j] > sl->array[j + 1])
{
Swap(sl->array + j, sl->array + j + 1);
}
}
}
}
二分查找:在有序顺序表中查找数据,返回数据所在的下标。
二分查找的思路就是找一半,丢一半。比如我们在1–100的数字中,猜一个数(20)。我们第一次猜50,他会反馈猜大了(1–50),第二次猜25,反馈猜大了(1–25),第三次猜13,反馈猜小了(13–25),第四次猜19,反馈猜小了(19–25),第五次猜22,反馈猜大了(19–22),第六次猜20,反馈猜对了。二分查找的思想就是这样,确定一半的范围,丢掉另一半。
int SeqListBanarySearch(SeqList *sl, SLDataType data)
{
SLDataType left = 0;
SLDataType right = sl->size - 1;
SLDataType mid = 0;
while (left <= right)
{
mid = left + ((right - left) >> 1);
if (sl->array[mid] < data)
{
left = mid + 1;
}
else if (sl->array[mid] > data)
{
right = mid - 1;
}
if (sl->array[mid] == data)
{
return mid;
}
}
return -1;
}
删除同一个数全部:在顺序表中从前到后将data数据全部删除。
这里介绍一种方法,我觉得是非常好的,在很多情况下可以使用。在用i遍历顺序表时,定义一个j,当i遇到的不是要删除的元素时,j跟随i移动。如果i遇到了要删除的元素时,i向后走,j留在原地,知道i走到不是要删除元素的位置时,将i所在的元素赋给j的下一个位置,最后顺序表的长度就是j的值,更新size的值就完成了删除同一个数全部。
//(数据)删除同一个数全部
void SeqListRemoveAll(SeqList *sl, SLDataType data)
{
int j = 0;
for (int i = 0; i < sl->size; i++)
{
if (sl->array[i] != data)
{
sl->array[j++] = sl->array[i];
}
}
sl->size = j;
return;
}
打印:将顺序表中的所有内容打印到屏幕上
这个接口的实现非常简单,遍历整个顺序表即可。
//打印
void SeqListPrint(SeqList *sl)
{
assert(sl);
for (int i = 0; i < sl->size; i++)
{
printf("%d ",sl->array[i]);
}
printf("\n");
return;
}
参考的源代码如下:
SeqList.h:
#pragma once
// Sequence List
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
struct SeqList {
SLDataType *array; // 指针,指向存放数据的空间,真正的空间在堆上
int capacity; // 顺序表整体的容量
int size; // 顺序表中真正有效的个数
// 初始值为 0,同时也表示下一个有效位置的下标
};
typedef struct SeqList SeqList;
SeqList seqlist;
// 封装的接口
// 初始化/销毁
void SeqListInit(SeqList *sl, size_t capacity);
void SeqListDestroy(SeqList *sl);
// 增删查改
// 尾插,插入在顺序表的尾部
void SeqListPushBack(SeqList *sl, SLDataType data);
// 头插,插入在顺序表的头部 ([0])
void SeqListPushFront(SeqList *sl, SLDataType data);
// 尾删,删除顺序表尾部的数据
void SeqListPopBack(SeqList *sl);
// 头删,删除顺序表头部的数据
void SeqListPopFront(SeqList *sl);
// 查找
int SeqListFind(SeqList *sl, SLDataType data);
//在pos的位置进行插入
void SeqListInsert(SeqList *sl, size_t pos, SLDataType data);
//在pos的位置进行删除
void SeqListErase(SeqList *sl, size_t pos);
//(数据)删除
void SeqListRemove(SeqList *sl, SLDataType data);
//修改
void SeqListModify(SeqList *sl, size_t pos, SLDataType data);
//冒泡排序
void SeqListBubbleSort(SeqList *sl);
//二分查找
int SeqListBanarySearch(SeqList *sl, SLDataType data);
//(数据)删除同一个数全部
void SeqListRemoveAll(SeqList *sl, SLDataType data);
// 打印
void SeqListPrint(SeqList *sl);
// (内部接口)检测是否需要扩容
void CheckCapacity(SeqList *sl);
sqlist.c:
#include "SeqList.h"
//Init
void SeqListInit(SeqList *sl, size_t capacity)
{
assert(sl);
sl->array = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
for (size_t i = 0; i < capacity; i++)
{
sl->array[i] = 0;
}
sl->size = 0;
sl->capacity = capacity;
return;
}
//销毁
void SeqListDestroy(SeqList *sl)
{
assert(sl);
free(sl->array);
sl->size = 0;
sl->capacity = 0;
return;
}
/*
增删查改
*/
// 尾插,插入在顺序表的尾部
void SeqListPushBack(SeqList *sl, SLDataType data)
{
assert(sl);
CheckCapacity(&seqlist);
sl->array[sl->size] = data;
sl->size++;
return;
}
//头插,插入在顺序表的头部
void SeqListPushFront(SeqList *sl, SLDataType data)
{
assert(sl);
CheckCapacity(&seqlist);
for (int i = sl->size; i > 0; i--)
{
sl->array[i] = sl->array[i-1];
}
sl->array[0] = data;
sl->size++;
return;
}
//尾删
void SeqListPopBack(SeqList *sl)
{
assert(sl);
assert(sl->size != 0);
sl->size--;
return;
}
//头删
void SeqListPopFront(SeqList *sl)
{
assert(sl);
assert(sl->size != 0);
if (sl->size == 1)
{
SeqListPopBack(&seqlist);
return;
}
for (int i = 0; i < sl->size-1; i++)
{
sl->array[i] = sl->array[i + 1];
}
sl->size--;
return;
}
//查找
int SeqListFind(SeqList *sl, SLDataType data)
{
assert(sl != NULL);
for (int i = 0; i < sl->size; i++)
{
if (sl->array[i] == data)
{
return i;
}
}
return -1;
}
//在pos位置进行插入
void SeqListInsert(SeqList *sl, size_t pos, SLDataType data)
{
assert(sl);
assert((int)pos >= 0 && (int)pos <=sl->size);
CheckCapacity(&seqlist);
if (pos == 0)
{
SeqListPushFront(&seqlist,data);
return;
}
if (pos == sl->size)
{
SeqListPushBack(&seqlist,data);
return;
}
for (int i = sl->size; i > (int)pos; i--)
{
sl->array[sl->size] = sl->array[sl->size - 1];
}
sl->array[pos] = data;
sl->size++;
return;
}
//在pos的位置进行删除
void SeqListErase(SeqList *sl, size_t pos)
{
assert(sl);
assert(sl->size != 0);
assert((int)pos >= 0 && (int)pos < sl->size);
if (pos == 0)
{
SeqListPopFront(&seqlist);
return;
}
if (pos == sl->size - 1)
{
SeqListPopBack(&seqlist);
return;
}
for (int i = pos; i < sl->size - 1; i++)
{
sl->array[i] = sl->array[i + 1];
}
sl->size--;
return;
}
//(数据)删除---先查找--根据下标删除
void SeqListRemove(SeqList *sl, SLDataType data)
{
assert(sl);
size_t ret = SeqListFind(&seqlist, data);
SeqListErase(&seqlist, ret);
return;
}
//修改
void SeqListModify(SeqList *sl, size_t pos, SLDataType data)
{
assert(sl);
assert((int)pos >= 0 && (int)pos < sl->size);
sl->array[pos] = data;
return;
}
//冒泡排序,为二分查找做准备
static void Swap(SLDataType *x, SLDataType *y)
{
SLDataType tmp = *x;
*x = *y;
*y = tmp;
return;
}
void SeqListBubbleSort(SeqList *sl)
{
assert(sl);
for (int i = 0; i < sl->size - 1; i++)
{
for (int j = 0; j < sl->size - 1 - i; j++)
{
if (sl->array[j] > sl->array[j + 1])
{
Swap(sl->array + j, sl->array + j + 1);
}
}
}
}
//二分查找
int SeqListBanarySearch(SeqList *sl, SLDataType data)
{
SLDataType left = 0;
SLDataType right = sl->size - 1;
SLDataType mid = 0;
while (left <= right)
{
mid = left + ((right - left) >> 1);
if (sl->array[mid] < data)
{
left = mid + 1;
}
else if (sl->array[mid] > data)
{
right = mid - 1;
}
if (sl->array[mid] == data)
{
return mid;
}
}
return -1;
}
//(数据)删除同一个数全部
void SeqListRemoveAll(SeqList *sl, SLDataType data)
{
int j = 0;
for (int i = 0; i < sl->size; i++)
{
if (sl->array[i] != data)
{
sl->array[j++] = sl->array[i];
}
}
sl->size = j;
return;
}
//打印
void SeqListPrint(SeqList *sl)
{
assert(sl);
for (int i = 0; i < sl->size; i++)
{
printf("%d ",sl->array[i]);
}
printf("\n");
return;
}
//检测是否需要扩容
void CheckCapacity(SeqList *sl)
{
assert(sl);
if (sl->size < sl->capacity)
{
return;
}
SLDataType newCapacity = sl->capacity * 2;
SLDataType *newarray = (SLDataType*)malloc(sizeof(SLDataType) * newCapacity);
if (newarray == NULL)
{
printf("内存开辟失败\n");
return;
}
for (int i = 0; i < sl->size; i++)
{
newarray[i] = sl->array[i];
}
free(sl->array);
sl->array = newarray;
sl->capacity = newCapacity;
newarray = NULL;
return;
}
这里是我使用的一些测试用例,可参考
main.c:
#include "SeqList.h"
void Test()
{
unsigned int c = 10;
SeqListInit(&seqlist,c);
SeqListPushFront(&seqlist, 5);
SeqListPushFront(&seqlist, 0);
SeqListPushFront(&seqlist, 1);
SeqListPushFront(&seqlist, 2);
SeqListPushFront(&seqlist, 5);
SeqListPushFront(&seqlist, 3);
SeqListPushFront(&seqlist, 4);
SeqListPushFront(&seqlist, 5);
SeqListPrint(&seqlist);
SeqListRemoveAll(&seqlist,5);
SeqListPrint(&seqlist);
#if 0
SeqListBubbleSort(&seqlist);
SeqListPrint(&seqlist);
printf("%d\n",SeqListBanarySearch(&seqlist,3));
printf("%d\n",SeqListBanarySearch(&seqlist,5));
#endif
#if 0
for (int i = 0; i < 100; i++)
{
SeqListPushBack(&seqlist, i);
}
SeqListPrint(&seqlist);
SeqListModify(&seqlist, 0,100);
SeqListModify(&seqlist, 50,200);
SeqListModify(&seqlist, 99,1000);
SeqListPrint(&seqlist);
#endif
#if 0
SeqListRemove(&seqlist, 50);
SeqListRemove(&seqlist, 0);
SeqListRemove(&seqlist, 99);
SeqListPrint(&seqlist);
#endif
#if 0
SeqListErase(&seqlist,0);
SeqListErase(&seqlist,98);
SeqListErase(&seqlist,50);
SeqListPrint(&seqlist);
#endif
#if 0
SeqListInsert(&seqlist,0,100);
SeqListInsert(&seqlist,50,1000);
SeqListInsert(&seqlist,101,2000);
SeqListPrint(&seqlist);
#endif
#if 0
printf("%d\n",SeqListFind(&seqlist,5));
printf("%d\n", SeqListFind(&seqlist, 0));
printf("%d\n", SeqListFind(&seqlist, 99));
// 1 2
#endif
//SeqListPushFront(&seqlist,10);
//SeqListPushFront(&seqlist,20);
//SeqListPrint(&seqlist);
10 20 1 2
//SeqListPopFront(&seqlist);
//SeqListPopFront(&seqlist);
//SeqListPrint(&seqlist);
1 2
//SeqListPopBack(&seqlist);
//SeqListPrint(&seqlist);
1
return;
}
int main()
{
Test();
SeqListDestroy(&seqlist);
return 0;
如果发现问题,可评论,我会及时改正…