线性表
线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构。
常见的线性表:顺序表、链表、栈、队列、字符串等等
顺序表
顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般采用数组存储。顺序表一般分为:静态顺序表【数组大小确定】和动态顺序表【数组可以扩容】。
静态顺序表
// 静态顺序表
typedef int SLDataType; // 类型替换
#define N1 10 // 容量定义为10
#define N2 1000000 //容量定义为1000000
struct SeqList
{
// 元素
SLDataType a[N];
size_t size;
// ......
};
// 函数
// ......
缺点:容量小不够用,容量造成浪费
动态顺序表
// 动态顺序表
typedef int SLDataType; // 类型替换
#define INIT_CAPACITY 5
typedef struct SeqList
{
SLDataType *a; // 指针,指向动态开辟的数组
size_t size; // 有效元素个数
unsigned capacity; // 空间容量
}SL;
// 函数
特点:按需申请,进行动态开辟堆区进行扩容。
下面主要介绍动态顺序表:
初始化顺序表
// 初始化
void SeqInit(SL *s)
{
s->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
if (s->a == NULL)
{
perror("malloc fail");
return;
}
s->size = 0;
s->capacity = INIT_CAPACITY;
}
这里初始化需要传递指针,而非值。如何是值初始化,实参传递给形参会进行拷贝一份,而进行初始化的时候,函数调用结束,就会丢下这个形参,会造成内存泄漏。
销毁顺序表
// 销毁
void SeqDestroy(SL *s)
{
free(s->a);
s->a = NULL;
s->size = s->capacity = 0;
}
注意需要释放堆区数据
顺序表容量扩容
// 扩容
void SeqreSize(SL* s)
{
SLDataType* sNew = (SLDataType*)realloc(s->a, sizeof(SLDataType) * s->capacity * 2);
if (sNew == NULL)
{
perror("realloc fail!");
return;
}
s->a = sNew;
s->capacity *= 2;
sNew = NULL;
}
扩容需要使用C语言中realloc关键字。
同时还需要注意要将新创建的指针赋值给形参传进来的指针。
顺序表尾部插入数据
// 尾插
void SeqPushBack(SL* s, SLDataType x)
{
// 注意容量不足,需要扩容
if (s->size == s->capacity)
{
SeqreSize(&s);
}
s->a[s->size] = x;
++s->size;
}
尾部插入数据,一定需要考虑当插入数据大于容量大小的时候,需要对容量进行扩容。
顺序表尾部删除数据
// 尾删
void SeqPopBack(SL* s)
{
// 第一种,当删除过多时,返回
//if (!s->size)
// return;
// 第二种,直接报错
assert(s->size);
// 这里直接size-1即可
--s->size;
}
在进行尾删的时候,需要判定当删除个数大于size的大小时候,需要对这种情况进行判定。
assert();当条件为真,继续执行下面的语句,但当条件为假,直接报错。
可以在每一个函数下面都指向assert(); 检查是否传入的指针为空指针。
顺序表头部插入数据
// 头插
void SeqPushFront(SL* s, SLDataType x)
{
if (s->size == s->capacity)
{
SeqreSize(&s);
}
size_t end = s->size;
while (end)
{
s->a[end] = s->a[end - 1];
--end;
}
s->a[0] = x;
头插需要将所有的元素都向后移动一位。
通过对比头部插入和尾部插入,尾插直接访问元素,对元素进行填写即可;而头插还需要将所有的数据向后移动一位,时间复杂度从O(1)直接变成了O(N),所有一般情况下,不建议使用头插和头删。
顺序表头部删除数据
// 头删
void SeqPopFront(SL* s)
{
// 元素为空,直接返回
if (!s->size) return;
// 头部删除
int begin = 1;
while (begin < s->size)
{
s->a[begin - 1] = s->a[begin];
begin++;
}
s->size--;
}
尾部删除的时候除了需要判断size为空,还需要考虑begin和size的比较。
当begin==size时,不能将a[begin-1]=a[begin],所以begin必须小于size
同样的头删也需要遍历整个数组,提高时间复杂度。
顺序表中间插入数据
// 中间插入数据
void SeqInsert(SL* s, size_t pos, SLDataType x)
{
// 检查数据
assert(s);
assert(pos >= 0 && pos < s->size);
// 插入数据
// 检查是否需要扩容
if (s->size == s->capacity)
{
SeqreSize(&s);
}
// 这里需要将pos位置给挪出来
size_t end = s->size;
while (end > pos)
{
s->a[end] = s->a[end - 1];
end--;
}
s->a[pos] = x;
s->size++;
}
中间插入数据的时候,需要检查
(1)指针是否为空;
(2)插入的位置是否在size范围内
(3)当size等于capacity的时候,进行扩容
完成中间插入数据的函数之后,就可以不用完成头插代码,直接引入中间删除的函数即可。
// 头插
void SeqPushFront(SL* s, SLDataType x)
{
//if (s->size == s->capacity)
//{
// SeqreSize(&s);
//}
//size_t end = s->size;
//while (end)
//{
// s->a[end] = s->a[end - 1];
// --end;
//}
//s->a[0] = x;
//s->size++;
SeqInsert(&s, 0, x);
}
顺序表中间删除数据
// 中间删除数据
void SeqErase(SL* s, size_t pos)
{
assert(s);
assert(pos >= 0 && pos < s->size);
int begin = pos;
while (begin < s->size - 1)
{
s->a[pos] = s->a[pos + 1];
}
s->size--;
}
中间删除数据的时候,需要检查
(1)指针是否为空
(2)删除的位置是否在size范围之内,注意删除的时候不能等于size,而插入的时候可以等于size
完成中间删除的操作后,可以用中间删除的函数完成头部删除的代码
// 头删
void SeqPopFront(SL* s)
{
//// 元素为空,直接返回
//if (!s->size) return;
//// 头部删除
//int begin = 1;
//while (begin < s->size)
//{
// s->a[begin - 1] = s->a[begin];
// begin++;
//}
//s->size--;
SeqErase(s, 0);
}
顺序表查找元素
// 查找该元素是否存在,返回下标
int SeqFind(SL* s, SLDataType x)
{
assert(s);
for (size_t i = 0; i < s->size; ++i)
{
if (s->a[i] == x)
return i;
}
return -1;
}
查找元素注意考虑没有找到元素的情况,需要返回-1
顺序表修改元素
// 修改pos位置上的元素
void SeqChangePos(SL* s, size_t pos, SLDataType x)
{
assert(s);
assert(pos >= 0 && pos < s->size);
s->a[pos] = x;
}
顺序表完整代码
SeqList.h头文件
#ifndef SEQLIST_H_
#define SEQLIST_H_
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//// 静态顺序表
//typedef int SLDataType; // 类型替换
//#define N1 10 // 容量定义为10
//#define N2 1000000 //容量定义为1000000
//struct SeqList
//{
// // 元素
// SLDataType a[N];
// size_t size;
// // ......
//
// // 函数
// // ......
//};
// 动态顺序表
typedef int SLDataType; // 类型替换
#define INIT_CAPACITY 5
typedef struct SeqList
{
SLDataType *a; // 指针,指向动态开辟的数组
size_t size; // 有效元素个数
unsigned capacity; // 空间容量
}SL;
// 函数
// 初始化
void SeqInit(SL *s);
// 销毁
void SeqDestroy(SL *s);
// 扩容
void SeqreSize(SL* s);
// 尾插
void SeqPushBack(SL *s, SLDataType x);
// 尾删
void SeqPopBack(SL* s);
// 头插
void SeqPushFront(SL* s, SLDataType x);
// 头删
void SeqPopFront(SL* s);
// 打印数据
void PPrint(SL* s);
// 中间插入数据
void SeqInsert(SL* s, size_t pos, SLDataType x);
// 中间删除数据
void SeqErase(SL* s, size_t pos);
// 查找该元素是否存在,返回下标
int SeqFind(SL* s, SLDataType x);
// 修改pos位置上的元素
void SeqChangePos(SL* s, size_t pos, SLDataType x);
#endif
SeqList.c源文件
#include"SeqList.h"
// 初始化
void SeqInit(SL *s)
{
s->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
if (s->a == NULL)
{
perror("malloc fail");
return;
}
s->size = 0;
s->capacity = INIT_CAPACITY;
}
// 销毁
void SeqDestroy(SL *s)
{
free(s->a);
s->a = NULL;
s->size = s->capacity = 0;
}
// 扩容
void SeqreSize(SL* s)
{
SLDataType* sNew = (SLDataType*)realloc(s->a, sizeof(SLDataType) * s->capacity * 2);
if (sNew == NULL)
{
perror("realloc fail!");
return;
}
s->a = sNew;
s->capacity *= 2;
sNew = NULL;
}
// 尾插
void SeqPushBack(SL* s, SLDataType x)
{
// 注意容量不足,需要扩容
if (s->size == s->capacity)
{
SeqreSize(&s);
}
s->a[s->size] = x;
++s->size;
}
// 尾删
void SeqPopBack(SL* s)
{
// 第一种,当删除过多时,返回
//if (!s->size)
// return;
// 第二种,直接报错
assert(s->size);
// 这里直接size-1即可
--s->size;
}
// 头插
void SeqPushFront(SL* s, SLDataType x)
{
//if (s->size == s->capacity)
//{
// SeqreSize(&s);
//}
//size_t end = s->size;
//while (end)
//{
// s->a[end] = s->a[end - 1];
// --end;
//}
//s->a[0] = x;
//s->size++;
SeqInsert(&s, 0, x);
}
// 头删
void SeqPopFront(SL* s)
{
//// 元素为空,直接返回
//if (!s->size) return;
//// 头部删除
//int begin = 1;
//while (begin < s->size)
//{
// s->a[begin - 1] = s->a[begin];
// begin++;
//}
//s->size--;
SeqErase(s, 0);
}
// 打印数据
void PPrint(SL* s)
{
for (size_t i = 0; i < s->size; ++i)
{
printf("%d ", s->a[i]);
}
printf("\n");
}
// 中间插入数据
void SeqInsert(SL* s, size_t pos, SLDataType x)
{
// 检查数据
assert(s);
assert(pos >= 0 && pos < s->size);
// 插入数据
// 检查是否需要扩容
if (s->size == s->capacity)
{
SeqreSize(&s);
}
// 这里需要将pos位置给挪出来
size_t end = s->size;
while (end > pos)
{
s->a[end] = s->a[end - 1];
end--;
}
s->a[pos] = x;
s->size++;
}
// 中间删除数据
void SeqErase(SL* s, size_t pos)
{
assert(s);
assert(pos >= 0 && pos < s->size);
int begin = pos;
while (begin < s->size - 1)
{
s->a[pos] = s->a[pos + 1];
}
s->size--;
}
// 查找该元素是否存在,返回下标
int SeqFind(SL* s, SLDataType x)
{
assert(s);
for (size_t i = 0; i < s->size; ++i)
{
if (s->a[i] == x)
return i;
}
return -1;
}
// 修改pos位置上的元素
void SeqChangePos(SL* s, size_t pos, SLDataType x)
{
assert(s);
assert(pos >= 0 && pos < s->size);
s->a[pos] = x;
}
顺序表优点与缺点总结
缺点
- 中间、头部插入删除数据,需要移动数据,提高时间复杂度,效率底下
- 空间不足时,需要扩容,扩容会消耗效率,且浪费空间。
优点
- 元素是连续存储的,由元素的下标来计算地址是非常快速的。



被折叠的 条评论
为什么被折叠?



