1. 顺序表
1.1 什么是线性表
介绍顺序表之前,我们先简单的了解一下什么是线性表,线性表就是逻辑上是连续的。
1.2 顺序表概念
顺序表,本质就是数组,动态增长,但是要求里面的数据在逻辑上和物理上都是连续的,通俗一点讲就是数据一个接一个的存储,而且存储的位置也是连续的。
1.3 顺序表的缺陷
我们在设计表的时候,不能说一上来就给数组一个准确的大小,大了或者小了都不合适,所以在设计表的时候,考虑动态增长,少了我就开辟一些空间。但是动态内存开辟空间是有性能消耗的。这里有一篇博客使用代码演示了动态内存开辟时候的消耗。
(46条消息) malloc动态申请内存空间对程序效率的影响_thinkercui的博客-CSDN博客_malloc效率
那顺序表的缺陷我们就显而易见了
- 动态内存增容造成的性能消耗
- 动态内存开辟的时候会浪费空间(因为顺序表要求连续存储,假如之前开辟的空间,后面有别的数据,就需要另外找一块空间替换之前的空间)。
- 对头部的数据进行操作的时候需要挪动数据
1.4 顺序表的实现
- 顺序表不能只存一种数据,所以我们首先先对类型进行一个重定义,这样我们再存别的数据的时候修改一下数据类型即可。
- 接下来就是顺序表的结构体,结构体成员有数组,数组里存了多少个元素,数组有多大。
- 设计函数,包括实习顺序表的增删改查、打印、初始化,动态内存开辟、销毁。
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SeqDataType;
typedef struct SeqList
{
SeqDataType* a;
int size;
int capacity;
}SeqList;
void SeqListInit(SeqList* pSeq); //顺序表初始化
void SeqListCheck(SeqList* pSeq); //动态内存开辟,如果空间不够了就开辟
void SeqListDestory(SeqList* pSeq); //顺序表销毁
void SeqListPrint(SeqList* pSeq); //顺序表元素打印
void SeqListPushBack(SeqList* pSeq, SeqDataType x); //尾部插入数据
void SeqListPushFront(SeqList* pSeq, SeqDataType x);//头部插入数据
void SeqListPopBack(SeqList* pSeq); //尾部删除数据
void SeqListPopFront(SeqList* pSeq); //头部删除数据
int SeqListQuery(SeqList* pSeq, SeqDataType x); //查询数据
void SeqListInsert(SeqList* pSeq, int pos, SeqDataType x);//任意位置插入
void SeqListErase(SeqList* pSeq, int pos); //任意位置删除
void SeqListModify(SeqList* pSeq, int pos, SeqDataType x);//修改数据
1.4.1 顺序表的初始化
void SeqListInit(SeqList* pSeq)
{
assert(pSeq);//防止传入空指针
pSeq->a = NULL;
pSeq->capacity = pSeq->size = 0;
}
1.4.2 动态增容
void SeqListCheck(SeqList* pSeq)
{
assert(pSeq);
int newcapacity = pSeq->capacity == 0 ? 4 : pSeq->capacity * 2;//如果当前数组的容量为0,那么就开辟4个空间
if (pSeq->capacity == pSeq->size)
{
SeqDataType* newArea = realloc(pSeq->a, newcapacity * sizeof(SeqDataType));
if (newArea == NULL)
{
printf("内存开辟失败\n");
exit(-1);
}
pSeq->a = newArea;
pSeq->capacity = newcapacity;
}
}
这里有个点注意一下,realloc是可以传入空指针的,传入空指针的作用等同于malloc
1.4.3 顺序表销毁
void SeqListDestory(SeqList* pSeq)
{
assert(pSeq);
free(pSeq->a);//这里面这个数组是动态开辟的,所以要释放的是这个数组
pSeq->a = NULL;
pSeq->capacity = pSeq->size = 0;
}
1.4.5 顺序表打印
void SeqListPrint(SeqList* pSeq)
{
assert(pSeq);
for (int i = 0; i < pSeq->size; i++)
{
printf("%d ", pSeq->a[i]);
}
printf("\n");
}
1.4.6 顺序表尾部插入
void SeqListPushBack(SeqList* pSeq, SeqDataType x)
{
assert(pSeq);
SeqListCheck(pSeq);//是不是需要增容
pSeq->a[pSeq->size] = x;
pSeq->size++;
}
1.4.7 顺序表头部插入
void SeqListPushFront(SeqList* pSeq, SeqDataType x)
{
assert(pSeq);
SeqListCheck(pSeq);
int end = pSeq->size - 1;//下标,一共有size个元素需要往后移
while (end >= 0)
{
pSeq->a[end + 1] = pSeq->a[end];
end--;
}
pSeq->a[0] = x;
pSeq->size++;
}
我们在头部插入数据的时候,需要将整个数组的元素向后移一个,把第一个元素的位置让出来,那么数据在数组中应该是从后往前移,先移8然后再依次拿前面的元素向后移,这样不会干扰元素,如果我们拿前面的元素向后移,我们会发现,后面的元素还没移已经发生变化了,这样不可行。我们只能从后往前移,但是我们得考虑数组能不能往后移,所以在移之前我们判断一下空间是否够,不够的话我们对空间进行开辟,然后把数据依次向后移,然后将我们插入的数据赋给第一个元素,这就是头插的过程。
void SeqListPushFront(SeqList* pSeq, SeqDataType x)
{
assert(pSeq);
SeqListCheck(pSeq);
int end = pSeq->size - 1;//下标,一共有size个元素需要往后移
while (end >= 0)
{
pSeq->a[end + 1] = pSeq->a[end];
end--;
}
pSeq->a[0] = x;
pSeq->size++;
}
1.4.8 尾部删除数据
void SeqListPopBack(SeqList* pSeq)
{
pSeq->size--;
}
尾部删除数据我们直接将数组的大小减1,在寻找的时候就不会找到后面这个元素,删除数据就不进行容量减少,本身增容对性能的影响比较大。
1.4.9 任意位置插入数据
比方说从pos位置开始插入数据,那么后面的数据就会向后移,数据向后移的话,必然是从后往前一个一个的向后移,然后会出现数据覆盖的情况。,那一直移到什么程度呢?一直把end移动到pos的位置。最后将pos的位置赋值即可。
void SeqListInsert(SeqList* pSeq, int pos, SeqDataType x)
{
assert(pSeq);
assert((pos >= 0) && pos <= pSeq->size);
SeqListCheck(pSeq);
int end = pSeq->size - 1;
while (end >= pos)
{
pSeq->a[end+1] = pSeq->a[end];
end--;
}
pSeq->a[pos] = x;
pSeq->size++;
}
1.4.10 任意位置删除数据
我们想要删除位置2上的元素,必然会将后面的元素向前移动,思路还是一样,从后往前移动会有数据覆盖的问题,所以这里从前往后向前移动,从pos开始后面的数据向前移动,一直移动到最后一个元素向前移动完。
void SeqListErase(SeqList* pSeq, int pos)
{
assert(pSeq);
assert(pos >= 0 && pos <= pSeq->size);
int begin = pos;
while (begin < pSeq->size - 1)
{
pSeq->a[begin] = pSeq->a[begin + 1];
begin++;
}
pSeq->size--;
}
1.4.11 修改数据
void SeqListModify(SeqList* pSeq, int pos, SeqDataType x)
{
assert(pSeq);
assert(pos >= 0 && pos <= pSeq->size);
pSeq->a[pos] = x;
}
1.4.12 查找元素
int SeqListQuery(SeqList* pSeq, SeqDataType x)
{
assert(pSeq);
for (int i = 0; i < pSeq->size; i++)
{
if (x == pSeq->a[i])
{
return i;
}
}
return -1;
}