顺序表 -->增、删、查、改等详细操作

个人主页流年如梦

专栏《C语言》 《数据结构》

文章目录

Ladies and gentlemen,本篇文章先了解一下线性表和顺序表,其中主要学习动态顺序表(重点);全程高能,不容错过!!!

前言

顺序表是数据结构中最基础、最常用的线性表结构 ,它以一段物理连续的内存空间 存储数据,是数组的封装与升级。本篇文章会实现动态顺序表的初始化、增删查改、扩容、销毁等核心接口,并通过测试代码验证功能;为后续学习链表、栈、队列等数据结构打下扎实基础

一.线性表

线性表是n个具有相同特性的数据元素的有限序列 ,是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串等

线性表又分为逻辑结构和物理结构,其中:

逻辑结构 --> 线性结构,连续一条直线
物理结构 --> 不一定连续

(物理储存 通常是以数组(顺序表)链式结构(链表)这两种方式)

二.顺序表

2.1概念与结构

顺序表是用一段物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

顺序表和数组的区别👇

数组 --> 原生存储空间
顺序表 --> 对数组的封装,实现增、删、改、查等完整接口

可以理解为👇

数组 --> 基础原料
顺序表 --> 封装好的成品结构(在基础原料的基础上)

2.2静态顺序表

使用定长数组存储元素:

c 复制代码
#define N 7
typedef struct SeqList
{
    SLDataType a[N];
    int size;
}SL;

分析:其中a[N]为定长数组,size为有效数据个数;但缺点是空间给少了不够用,给多了造成空间浪费

2.3动态顺序表

2.3.1动态顺序表结构体

动态顺序表按需申请、释放空间,更灵活

c 复制代码
typedef struct SeqList
{
    SLDataType* a;
    int size;
    int capacity;
}SL;

分析 :其中SLDataType* a为指向动态开辟的数组,size为有效数据个数,capacity为空间容量

2.3.2头文件声明 --> SeqList.h

参考代码如下:

c 复制代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//初始容量
#define INIT_CAPACITY 4

//顺序表存储的数据类型
typedef int SLDataType;//SLDataType为int类型

//动态顺序表结构体
typedef struct SeqList
{
	SLDataType* a;
	int size;
	int capacity;
}SL;

//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印
void SLPrint(SL* ps);

//扩容判断
void SLCheckCapacity(SL* ps);

//头部插入删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
//尾部插入删除
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);

//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置之前删除数据
void SLErase(SL* ps, int pos);

//查找
int SLFind(SL* ps, SLDataType x);

2.3.3源文件实现 --> SeqList.c

2.3.3.1初始化

对动态顺序表进行初始化

c 复制代码
void SLInit(SL* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

🧐分析 :把数组指针置空,有效数据个数和容量都清0,assert防止传进来空指针

2.3.3.2销毁

销毁动态顺序表,释放内存

c 复制代码
void SLDestroy(SL* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

🧐分析 :必须free掉动态开辟的数组,避免内存泄漏;指针置空、数据置0,防止野指针

2.3.3.3打印

遍历打印顺序表中所有有效数据

c 复制代码
void SLPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
2.3.3.4扩容检查

判断空间是否满了,满了就扩容(一般扩 2 倍,没了再扩)

c 复制代码
void SLCheckCapacity(SL* ps)
{
	assert(ps);

	if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? INIT_CAPACITY : ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));

		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		ps->a = tmp;
		ps->capacity = newCapacity;
	}
}

🧐分析 :第一次扩容给INIT_CAPACITY(4),之后每次扩2倍,用realloc更高效;👉所有插入操作前都要调用它

2.3.3.5尾插尾删

尾插(在顺序表最后插入一个数据):

c 复制代码
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);

	ps->a[ps->size] = x;
	ps->size++;
}

🧐分析插入操作先检查扩容 ,再把数据放到size位置;时间复杂度为O(1)

尾删(删除顺序表最后一个数据):

c 复制代码
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);

	ps->size--;
}

🧐分析 :不用真正删除,只需size--,逻辑删除;使用之前必须断言size>0,防止删空表导致程序崩溃

2.3.3.6头插头删

头插(在顺序表最前面插入数据):

c 复制代码
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);

	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}

	ps->a[0] = x;
	ps->size++;
}

🧐分析插入操作先检查扩容 ,所有数据先向后挪一位,再把数据放在下标为0的位置;时间复杂度为O(N),效率低

头删(删除第一个数据):

c 复制代码
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);

	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}

	ps->size--;
}

🧐分析 :后面所有数据向前覆盖;时间复杂度为O(N),效率低

2.3.3.7指定位置插入与删除

插入(在下标pos前插入数据x):

c 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);

	for (int i = ps->size; i > pos; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}

	ps->a[pos] = x;
	ps->size++;
}

🧐分析pos位置及之后的数据整体后移;必须检查pos合法性 --> 0 ≤ pos ≤ size

删除(删除下标pos位置的数据):

c 复制代码
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}

	ps->size--;
}

🧐分析pos后面的数据整体前移覆盖并且注意pos范围为 0 ≤ pos < size

2.3.3.8查找

查找x,找到返回下标,没找到则返回-1:

c 复制代码
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}

	return -1;
}

🧐分析 :顺序遍历,时间复杂度为O(N);常用于修改、删除前先定位位置

2.3.4主函数 --> test.c

参考代码如下:

c 复制代码
#include "SeqList.h"

void TestSeqList()
{
	SL sl;
	//初始化
	SLInit(&sl);

	//尾插
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	printf("尾插 1 2 3 4:");
	SLPrint(&sl);

	//头插
	SLPushFront(&sl, 0);
	printf("头插 0:");
	SLPrint(&sl);

	//指定位置插入
	SLInsert(&sl, 3, 99);
	printf("在下标3插入99:");
	SLPrint(&sl);

	//查找
	int pos = SLFind(&sl, 3);
	if (pos != -1)
	{
		printf("找到 3,下标是:%d\n", pos);
	}

	//删除指定位置
	SLErase(&sl, 3);
	printf("删除下标3:");
	SLPrint(&sl);

	//头删
	SLPopFront(&sl);
	printf("头删:");
	SLPrint(&sl);

	//尾删
	SLPopBack(&sl);
	printf("尾删:");
	SLPrint(&sl);

	//销毁
	SLDestroy(&sl);
	printf("销毁成功\n");
}

int main()
{
	TestSeqList();//调用函数

	return 0;
}

运行结果

🎯总结(动态顺序表)

  1. 动态顺序表 是对数组的封装,用连续物理内存 存储数据,支持动态扩容
  2. 结构包含三要素:数据指针a、有效数据size、容量capacity
  3. 核心操作:尾插与尾删、头插与头删、指定位置插入删除、查找
  4. 所有接口按模块化实现:.h声明、.c实现、test.c测试
  5. 扩容规则:初始空间容量为4,满了2倍扩容 ,需要用realloc
  6. 尾插尾删 时间复杂度为O(1)头插头删、中间插入删除 的时间复杂度为O(N)(因为需要挪动数据)

⚠️易错点

  1. 忘记断言(assert),空指针访问导致崩溃
  2. 扩容判断错误 ,未判断size == capacity就扩容
  3. realloc直接赋值,不用临时变量(tmp)接收,易丢失数据
  4. 插入或删除的循环方向或边界写错,造成数据覆盖或越界
  5. 不检查pos合法性,插入删除越界
  6. 空表执行删除size变负,逻辑异常
  7. 销毁后未置空(NULL)free后不置NULL产生野指针
  8. 扩容后不更新capacity,导致容量逻辑混乱

👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞 鼓励原创,让优质内容被更多人看见
⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅
💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步

相关推荐
HABuo1 小时前
【linux(四)】套接字编程--socket套接字及其接口认识
linux·运维·服务器·c语言·c++·ubuntu·centos
我不是懒洋洋1 小时前
手写一个布隆过滤器:从原理到工业级实现
c语言
wilbertzhou1 小时前
华为4A架构中的信息架构设计方法:从数据资源到战略资产的治理之道
数据结构·togaf·企业架构·4a架构
小年糕是糕手1 小时前
【C/C++刷题集】栈、stack、队列、queue核心精讲
c语言·开发语言·数据结构·数据库·c++·算法·蓝桥杯
Fanfanaas1 小时前
Linux 系统编程 进程篇 (六)
linux·服务器·c语言·开发语言
小年糕是糕手1 小时前
【C/C++刷题集】顺序表、vector、链表、list核心精讲
c语言·开发语言·数据结构·c++·算法·leetcode·蓝桥杯
会编程的土豆2 小时前
从 C/C++ 视角快速上手 Go 语言:核心差异与避坑指南
c语言·开发语言·c++·后端·golang
yuanyuan2o22 小时前
GDB 调试指南
c语言·c++·算法
流年如夢2 小时前
算法效率:复杂度原理解析
c语言·数据结构·算法