引言:数据结构是计算机科学中研究数据组织、存储和操作方式的一门学科,它关注如何高效地管理数据,以支持各种常见的操作(如插入、删除、查找、排序等)。无论是软件开发、算法设计还是人工智能等领域,数据结构都是核心基础。本专题陆续更新初阶数据结构,并附有简单实现代码(C语言版),帮助理解。
首先,C语言基础不牢的建议打牢C语言基础。
其次,结构体部分理解不深的可以看一下我的另一篇文章:自定义类型: 结 构 体 -CSDN博客
线性表
- 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结 构,常见的线性表:顺序表、链表、栈、队列、字符串...
- 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。
顺序表
概念及结构
- 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
- 顺序表是一种线性数据结构,它用一段连续的内存空间依次存储数据元素,是数组的抽象实现(静态数组或动态扩容数组均可视为顺序表)。其核心特点是元素的逻辑顺序与物理存储顺序一致,通过下标(索引) 可直接访问元素。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素。
- 定义时指定固定长度(如
int arr[10]
),内存空间在编译时分配。 - 缺点:长度固定,元素数量超过容量时会溢出,灵活性差。

#define MAXSIZE 100
typedef struct {
int data[MAXSIZE];
int length;
} SqList;
静态顺序表的核心缺点源于其容量固定和连续存储的特性,这使其在灵活性、内存利用率和动态操作效率上存在明显不足。实际开发中,静态顺序表更适合数据量固定、访问频繁但增删较少的场景(如存储常量数据、固定大小的配置信息等)
2. 动态顺序表:使用动态开辟的数组存储。
- 初始分配一定内存,当元素满时自动扩容(如翻倍申请新空间,复制旧元素后释放原空间)。
- 优点:长度动态调整,避免溢出,更贴合实际需求(如 Java 的
ArrayList
、Python 的list
本质都是动态顺序表)。
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪 费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
主要实现增删查改操作:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//简单实现--顺序表
//顺序表和链表作为初学的两个最基础的数据结构
//实现一个顺序表需要的基本操作
/* 1.初始化
2.销毁
3.打印
4.检查顺序表的容量,是否需要扩容
5.尾插
6.尾删
7.头插
8.头删
9.pos位置插入
10.pos位置删除
11.查找
*/
typedef int SLDataType;
#define INIT_CAPACITY 4
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDataType* a; //指向一块连续的空间
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
// 增删查改
//1.顺序表初始化
void SLInit(SL* ps)
{
ps->a = (SLDataType*)malloc(sizeof(SLDataType)*INIT_CAPACITY);
if(ps->a == NULL)
{
perror("malloc fail");
return;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
}
//2.顺序表销毁
void SLDestroy(SL* ps)
{
assert(ps);
free(ps);
ps->a =NULL;
ps->size = ps->capacity = 0;
}
//3.打印
void SLPrint(SL* ps)
{
assert(ps);
int i = 0;
for(i = 0;i < ps->size;i++)
{
printf("%d ",ps->a[i]);
}
}
//4.扩容
void SLCheckCapacity(SL* ps)
{
assert(ps);
if(ps->capacity == ps->size)
{
SLDataType* tmp = (SLDataType*)realloc(ps->a,sizeof(SLDataType)*ps->capacity*2);
if(tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
}
//5.尾插
void SLPushBack(SL* ps, SLDataType x)
{
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//6.尾删
void SLPopBack(SL* ps)
{
assert(ps);
ps->a[ps->size-1] = 0;
ps->size--;
}
//7.头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
int end = ps->size-1;
while(end>=0)
{
ps->a[end+1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
//8.头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
//9.在pos位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
//10.pos位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
//11.查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
for(i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
int main()
{
SL sl;
//现在我们来实现一下 整个顺序表
//首先尾插5个数据
SLPushBack(&sl,1);
SLPushBack(&sl,2);
SLPushBack(&sl,3);
SLPushBack(&sl,4);
SLPushBack(&sl,5);
//打印出来看一下
SLPrint(&sl);
printf("\n");
//头插5个数据
SLPushFront(&sl,5);
SLPushFront(&sl,6);
SLPushFront(&sl,7);
SLPushFront(&sl,8);
SLPushFront(&sl,9);
//打印出来看一下
SLPrint(&sl);
printf("\n");
//头删两个数据
SLPopFront(&sl);
SLPopFront(&sl);
//尾删三个数据
SLPopBack(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPrint(&sl);
printf("\n");
//销毁顺序表
// SLDestroy(&sl);
return 0;
}
应用场景
顺序表适用于:
元素数量相对固定
需要频繁随机访问
插入和删除操作较少
时间复杂度总结
操作 | 时间复杂度 |
---|---|
访问元素 | O(1) |
插入元素 | O(n) |
删除元素 | O(n) |
按值查找 | O(n) |
按位查找 | O(1) |