一 线性表的定义
线性表是具有相同数据类型的N(N >= 0)个数据元素的有限数列
①数据类型 :这点类似数组 比较好理解 表示其存放的元素类型唯一,或全部整形,或全是字符型等等。
二 线性表的物理存储结构
用一段地址连续的存储单元依次存储数据结构
这里要注意地址连续的概念 这里的地址连续指的是物理上的连续,也就是在计算机内部,类似于数组,当我们向堆区申请空间时,系统给我们的是一片连续的物理存储空间,我们看到的1和2在一起在计算机内部,他们也实际上是连在一起的 并且要注意 各个元素之间是一个个挨着的,中间不允许有空位 。
总结为一句话:地址连续,即内部地址的连续,一个挨一个,不许有空位。
三 线性表的管理节点
实际操作中如何才能对线性表中众多的元素进行管理成了一个难题,结合线性表物理地址连续的特性我们引入了管理节点的概念,由于地址连续,管理节点首先的工作便是记录首地址的位置,即让我们知道第一个元素在哪里,接着我们还引入了记录线性表的最大容量,以及当前存储的元素个数的功能
四 线性表的插入操作
核心要义在于 ①明确我们要插入的位置 ② 如果后面有元素,则需要将所有元素都向后移动 ③插入我们的元素
需要注意插入后我们的元素个数增加,此时管理节点也将发生相应变化
五 线性表的删除操作
这里需要注意两点 ① 元素的删除并不真的是计算机将这个元素删除,在图中4元素只是将它的值写入给了3元素覆盖掉了它
② 管理节点中 关于线性表的元素数量要减1 实际上当我们删除最后一个元素时,理论上只需要将管理节点中的length - 1即可 因为逻辑上我们只会对管理节点中现存的元素进行操作,而最后一个元素也在逻辑上被我们删除
六 实际操作
6.1 线性表的初始化
typedef struct Vector {//利用结构体 构造我们的管理节点
int *data;//用于管理数据区。整形的指针类型变量,接收到的将是一个地址,其用于记录这个线性表数据空间的首地址位置,也可以理解为将整个线性表由此开始存储;
int size, length;//线性表的容量和现有元素长度
}Vec;
Vec *init(int n) {//n表示我们要创建一个多大的线性表
Vec *v = (Vec *)malloc(sizeof(Vec));//结合我们管理节点的那张图,我们可以更好的明白利用malloc申请这两个空间的意义!
v->data = (int*)malloc(sizeof(int) * n);//体现物理地址连续
v->size = n;
v->length = 0;
return v;
}
void freeVec(Vec*v) {
if (v == NULL)
return;
free(v->data);//用完malloc一定要记得清理向堆区申请的内存 注意我们要先清理线性表 再清理管理节点;
free(v);
return;
}
6.2 线性表的插入操作
int insert (Vec *v, int idx, int val) {//对什么进行操作,插入位置在哪,插入元素是什么
if (!v){//线性表不存在则返回0
return 0;
}
if (idx < 0 || idx > v->length) {//插入位置不正确则失败 注意线性表下表是从0开始的那么我们最大可以插入的下标位置为length;
return 0;
}
if (v->length == v->size) {
if(!expand(v))//扩容操作
return 0;
}
memcpy(v->data + idx + 1, v->data + idx, sizeof(int) * (v->length - idx));
v->data[idx] = val;
v->length++;
return 1;
}
void *memcpy(void *dest, const void *src, size_t n);以 src指向的地址为起点,将连续的以 src指向的地址为起点,将连续的n个字节数据复制到以dest指向的地址起点的内存中
6.3 线性表的删除操作
int erase (Vec *v, int idx) {
if (!v) {
return 0;
}
if (idx < 0 || idx >= v->length) {//注意这里是>=号 下标不要越界
return 0;
}
memcpy(v->data + idx, v->data + idx + 1, sizeof(int) * (v->length - idx - 1));//参照上方图解 变化正好相反
v->length--;
return 1;
}
6.4 线性表的查找
int find (Vec *v,int val) {
if(!v)
return -1;
for (int i = 0; i < v->length; i++) {//即利用循环去遍历整个线性表,找到元素则返回下标
if(v->data[i] == val) {
return i;
}
}
return -1;
}
6.5 线性表的扩容
当我们插入元素时,若线性表已满,利用realloc函数 动态的去给我们的线性表扩容
void *realloc(void *ptr, size_t size); 给指针指向ptr的一段空间扩容成size的大小 返回值是获得新空间后的首地址
分配空间的三种情况
①我们要给一段数组空间申请扩容为原来一倍大小的空间,这时,realloc会在我们原来数组地址末尾去找有没有这样大小的一倍的没有被占用的一片空间 如果找到了,则返回值依然是原数组的首地址
② 若在其后找不到这样的一段空间 realloc则会重新找一片满足我们需要大小的两倍于我们数组的空间,并且 它还会按找字节顺序将我们原先数组的内容拷贝到我们新开辟的空间,并且主动帮我们释放掉原先的数组内存
③内存中实在没有剩余的空间,realloc会返回一个空地址``
int expand(Vec *v) {
if (!v)
return 0;
int expsize = v->size;//定义好要扩容的空间大小
int *tmp;//这里一定要定义一个新指针, 因为若满足条件3则会返回空指针
while (expsize){
int *tmp = (int*)realloc(v->data, sizeof(int) * (v->size + expsize));//若采用v->data返回空指针时则会导致原来的内存内容全部找不到,用临时指针记录扩容后的新地址
if (tmp)//若扩容成功没有返回空指针则退出循环,否则就降低一半的内存来扩容
break;
expsize /= 2;
}
if (!tmp)
return 0;//若扩容失败则返回0
v->data = tmp;//交付新地址
v->size += expsize;//得到新的容量
printf("expand successfully new size: %d\n",v->size);
return 1;
}