目录
前言
一、线性表是什么?
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
二、顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1). 静态顺序表:使用定长数组存储元素。
// 问题:开小了,不够用。开大了,存在浪费。
#define N 10000
struct SeqList
{
int a[N];
int size; // 记录存储了多少个数据
};
2).动态顺序表:使用动态开辟的数组存储。
空间不够的时候进行增容即可,不存在开大开小的问题,但要注意记得释放,以免造成内存泄露。
typedef int SLDataType;
// 动态的顺序表
typedef struct SeqList
{
SLDataType* a;
int size; // 存储数据个数
int capacity; // 存储空间大小
}SeqList;
2.2 顺序表的实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
同样首先VS2019工程创建:
顺序表实现的功能如SeqList.h中所示如下:
SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 要求:存储的数据从0开始,依次连续存储
// 静态的顺序表
// 问题:开小了,不够用。开大了,存在浪费。
//#define N 10000
//struct SeqList
//{
// int a[N];
// int size; // 记录存储了多少个数据
//};
#define Initial_Size 4
typedef int SLDataType;
// 动态的顺序表
typedef struct SeqList
{
SLDataType* a;
int size; // 存储数据个数
int capacity; // 存储空间大小
}SeqList;
//打印顺序表
void SeqListPrint(SeqList* psl);
//顺序表的初始化
void SeqListInit(SeqList* psl);
//销毁空间
void SeqListDestroy(SeqList* psl);
//检查是否需要扩容
void SeqListCheckCapacity(SeqList* psl);
// 时间复杂度是O(1)
//顺序表的尾插尾删
void SeqListPushBack(SeqList* psl, SLDataType x);
void SeqListPopBack(SeqList* psl);
// 时间复杂度是O(N)
//顺序表的头插头删
void SeqListPushFront(SeqList* psl, SLDataType x);
void SeqListPopFront(SeqList* psl);
// 在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 删除pos位置的数据
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x);
2.4 需要注意的点
结构体传址:如果是SeqList s;SeqListInit(s);那么sl和s为两个栈帧里的两个变量,形参是实参的临时拷贝,形参的改变不会影响实参。还可以减少拷贝。
三、顺序表的具体函数实现
3.1 顺序表初始化
void SeqListInit(SeqList* psl)
{
assert(psl);
psl->a = NULL;
psl->size = psl->capacity = 0;
}
3.2 顺序表的销毁
void SeqListDestroy(SeqList* psl)
{
assert(psl);
free(psl->a);
psl->a = NULL;
psl->capacity = psl->size = 0;
}
3.3 检查是否需要扩容
void SeqListCheckCapacity(SeqList* psl)
{
assert(psl);
if (psl->size == psl->capacity)
{
size_t newCapacity = (psl->capacity == 0) ? Initial_Size : psl->capacity * 2;//顺序表初始大小
SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc error\n");
return;
}
else
{
psl->a = tmp;
psl->capacity = newCapacity;
}
}
}
3.4 顺序表的尾插与尾删
void SeqListPushBack(SeqList* psl, SLDataType x)
{
assert(psl);
SeqListCheckCapacity(psl);
//如果容量够,就不扩容
psl->a[psl->size] = x;
psl->size++;
}
void SeqListPopBack(SeqList* psl)
{
assert(psl);
if (psl->size > 0)
{
psl->size--;
}
}
3.5 顺序表的头插与头删
void SeqListPushFront(SeqList* psl, SLDataType x)
{
assert(psl);
SeqListCheckCapacity(psl);//防止越界
int end = psl->size - 1;//记录最后一个元素的下标
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[end] = x;
psl->size++;
}
void SeqListPopFront(SeqList* psl)
{
assert(psl);
int begin = 0;
int end = psl->size - 1;
while (end > begin)
{
psl->a[end - 1] = psl->a[end];
end--;
}
psl->size--;
}
3.6 在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
assert(psl);
if (pos > psl->size)
{
printf("pos 越界\n");
return;
}
SeqListCheckCapacity(psl);
/*int end = psl->size - 1;
while (end >= pos)
{
psl->a[end + 1] = psl->a[end];
end--;
}*/
/*上面这种写法。当顺序表为空 size = 0 时,会导致 end = -1,size_t的比较会把-1认为无符号,很大的正数*/
size_t end = psl->size;
while (end > pos)
{
psl->a[end] = psl->a[end - 1];
--end;
}
psl->a[pos] = x;
psl->size++;
}
3.7 删除pos位置的数据
void SeqListErase(SeqList* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos + 1;
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
begin++;
}
psl->size--;
}
3.8 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
总结
顺序表,在自我能写出通讯录后还是很好理解的!加油!