引入
学习数据结构有什么意义呢?
我们很疑惑在以后的实际工作中可能根本用不到这些东西,但实际上这些东西会潜移默化的改变我们的思维方式(如何将自然语言巧妙地转换成编码能力),网上有个例子我觉得很有意思:我们所学的语言是在教我们识别食材,但数据结构会教我们如何使用食材做出一道美食。
顺序表
概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
补充:
1.线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
2.常见的线性表:顺序表、链表、栈、队列、字符串…(顺序表示线性表的字集)
3.线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
4.物理的线性结构:相邻存储的数据是否是在内存上是连续开辟的(数组)。
5.逻辑的线性结构:数据能通过某种方式链接一起,但实际上数据的内存可能不是连续的。
6.我们了解的线性结构一般是从增删查改这几个方面来查看效率的。
分类
静态的顺序表
#define MAX 100//定义的值方便进行修改
typedef int SeqDateType;//同上
struct SeqList
{
SeqDateType arr[MAX];
int size;
//1.表示线性表的当前数据个数
//2.表示线性表的下一个位置的下标
};
图解
动态的顺序表
//动态顺序表
#define AddSize 4
typedef int SeqDateType;
typedef struct SeqList
{
SeqDateType* arr;//管理动态内存开辟空间的那个指针
int size;//当前数据个数和下一个数据的下标
int capacity;//开辟后够存的数据个数
}SL;//将结构体重命名方便书写。
图解:
函数
增
不管怎么增加最后都要考虑增容,不如把增容的函数单独写一份方便使用
增容:
void CheckCapacity(SL* p)
{
//判断是否要进行增容
//1.顺序表的容量为0
//2.当前数据个数等于容量
if ((0 == p->capacity) || (p->size == p->capacity))
{
SeqDateType* tmp = (SeqDateType*)realloc(p->arr, sizeof(SeqDateType) * (p->capacity + AddSize));
if (tmp == NULL)
{
perror("realloc:");
exit(-1);
}
p->arr = tmp;
p->capacity += AddSize;
}
}
头插
当你在顺序表的表头插入数据,需要将数据全部向后移一位,同时要考虑增容的问题
void SeqListPushFront(SL* p, SeqDateType x)
{
CheckCapacity(p);
//这里的移数据有两种方法
//1.将数据从最开始移
//2.将数据从末尾开始移
int end = p->size;
while (end>=0)
{
p->arr[end + 1] = p->arr[end];
end--;
}
//这种方法较为麻烦
//int begin = 0;
//int next = p->arr[begin];
//int cur = p->arr[begin];
//while (begin < p->size)
//{
// next = p->arr[begin + 1];
// p->arr[begin+1] = cur;
// cur = next;
// begin++;
//}
p->arr[0] = x;
p->size++;
}
这里移动数据的时间复杂度为O(N),较为麻烦,同时这也是顺序表的一个缺点
尾插
void SeqListPushBack(SL* p, SeqDateType x)
{
CheckCapacity(p);
p->arr[p->size] = x;
p->size++;
}
在指定下标位置插入
void SeqListInsert(SL* p, SeqDateType x, SeqDateType target)
{
int i = 0;
for (i = 0; i < p->size; i++)
{
if (p->arr[i] == x)
break;
}
int st = -1;
if(i!= p->size)
{
st=i;
}
if (st == -1)
{
printf("%d在顺序表中并不存在", x);
return;
}
//将st之后的的数据向后移动一位
int end = p->size-1;
while (end>=st)
{
p->arr[end + 1] = p->arr[end];
end--;
}
p->arr[st] = target;
p->size++;
}
删
头删
将数据全部往前移一位,同时将size减去一;
这里考虑的问题是:如果没有数据呢?
void SeqListPopFront(SL* p)
{
if (p->size > 0)
{
int begin = 0;
while (begin < p->size - 1)
{
p->arr[begin] = p->arr[begin + 1];
begin++;
}
p->size--;
}
else
{
printf("无数据可删!\n");
}
}
尾删
void SeqListPopBack(SL* p)
{
if (p->size > 0)
p->size--;
else
printf("无数据可删!\n");
//assert(p->size);
// p->size--;
}
指定下标位置删除
void SeqListDel(SL* p, SeqDateType x)
{
int begin = x;
while (begin < p->size - 1)
{
p->arr[begin] = p->arr[begin + 1];
begin++;
}
p->size--;
}
查
int SeqListFind(SL* p, SeqDateType x)
{
int i = 0;
for (i = 0; i < p->size; i++)
{
if (p->arr[i] == x)
return i;
}
return -1;//如果没有找到则返回-1
}
补充
初始化
void SeqListInit(SL* p)
{
p->arr = NULL;
p->capacity = 0;
p->size = 0;
}
释放顺序表
void SeqListDestory(SL* p)
{
free(p->arr);
p->arr = NULL;
}
打印顺序表
void SeqListPrint(SL* p)
{
for (int i = 0; i < p->size; i++)
{
printf("%d ", p->arr[i]);
}
}
总结
顺序表相对来说是在数组的基础上进行优化,但在删除,增加的功能上时间复杂度为O(N),损耗比较大,同时还需要考虑增容的现象一次增容的过多则会导致空间上的浪费。