【数据结构——顺序表】

目录

一、数据结构

1.数据结构的基本知识

数据结构的定义 :计算机存储、组织数据的方式
数据结构包括:数据逻辑结构和数据存储结构

(1)数据逻辑结构

  • 集合结构
  • 线性结构
  • 树结构
  • 图结构或网状结构

也可以用另一种方式概括:

  • 线性结构: 线性表、栈、队、串、数组、广义表
  • 非线性结构: 集合结构、树、图

(2)数据存储结构

  • 顺序存储结构
  • 链式存储结构

二、顺序表

1.顺序表的引入

(1)顺序表与数组的关系

顺序表的底层是数组

数组:

  • 修改:arr[pos]=x;
  • 插入:通过循环找到数组中已有元素个数后插入数据
  • 删除:通过循环找到数组中已有元素个数后删除数据

申请数组空间:

  1. 定长:int arr[10];
  2. 动态内存开辟:int* arr;

(2)顺序表与线性表

线性表: 具有相同特性的数据结构的集合
线性表 { 物理结构:不一定连续 逻辑结构:连续(肉眼看到的、想象的) 线性表\begin{cases} 物理结构:不一定连续\\ 逻辑结构:连续(肉眼看到的、想象的) \end{cases} 线性表{物理结构:不一定连续逻辑结构:连续(肉眼看到的、想象的)
顺序表: 线性表的一种
顺序表 { 物理结构:连续 逻辑结构:连续 顺序表\begin{cases} 物理结构:连续\\ 逻辑结构:连续 \end{cases} 顺序表{物理结构:连续逻辑结构:连续

2.顺序表的定义

顺序表的底层是数组,数组具有不同的申请内存空间的方式,顺序表也因此分为静态顺序表与动态顺序表

SeqList:Seq是seqence顺序的,List列表

(1)静态顺序表的定义

c 复制代码
#define N 100//方便后续修改
struct SeqList
{
	int arr[N];//底层是100个空间的定长数组
	int size;//记录顺序表当前有效的数据个数(由于不一定立即用完100个空间)
};

缺点: 需要自己指定申请多大的空间

  • 给小了: 空间不够,丢失信息
  • 给大了: 空间浪费,经济损失

(2)动态顺序表的定义

c 复制代码
struct SeqList
{
	int* arr;//数组大小不确定,用指针
	int size;//记录当前有效数据个数
	int capacity;//记录当前申请到的空间大小
};

与静态顺序表相比,动态顺序表更加灵活

(3)定义的改进

使类型更加灵活

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

3.顺序表的初始化

c 复制代码
void SLInit(SL* ps)
{
	ps->arr=NULL;//数组初始情况下置空
	ps->size=ps->capacity=0;//有效的数据个数和空间大小初始化为0
}

测试初始化:

c 复制代码
void SLtest1()
{
	SL sl;
	SLInit(&sl);
}

Tips:

这里SLInit(&sl)不能写为SLInit(sl),上面的初始化也不能直接用ps,这样属于传值调用(拷贝值),会报未初始化错误


正确做法: 传址,用指针来接收

4.顺序表的销毁

c 复制代码
void SLDestory(SL* ps)
{
		if(ps->arr)//如果顺序表里的数组不为空(有空间)
		{
			free(ps->arr);//释放空间
		}
		ps->arr=NULL;//释放完空间后手动置空
		ps->size=ps->capacity=0;//在使用过程中size和capacity也有可能变成其他整数,也要销毁
}

5.顺序表的插入和删除

test.c:

c 复制代码
void SLPushBack(SL* ps,SLDataType x);//头插入
void SLPushFront(SL* ps,SLDataType x);//尾插入
void SLPopBack(SL* ps);//尾删除
void SLPopFront(SL* ps);//头删除

(1)尾插

step1:尾插的思路

先在顺序表的末尾插入数据,然后再size++

c 复制代码
ps->arr[ps->size]=x;//在size的位置插入数据
++ps->size;//插入完成之后size++
//以上两句也可以写为一句 ps->arr[ps->size++]=x;
step2:尾插的增容及空间判断

但是这样会报空间为0错误,所以我们要在前面判断一下是否有空间

if(ps->capacity==ps->size)

当capacity和size相等时,说明空间已满

空间满了之后,要增容,我们采用realloc函数:

Tips:频繁增容会使程序运行效率大大降低

因此,我们需要考虑要申请多大空间(一次增容多大)的问题:

增容通常来说是成倍数增加,一般是2或3倍(数学推理得出)

c 复制代码
ps->arr=(SLDataType*)realloc(ps->arr,ps->capacity*2*sizeof(SLDataType));

到这里我们会发现一个问题:我们在前面将capacity初始化为0了,那么这里乘以capacity结果就会为0,不符合我们的预期,因此前面还要加一个对于capacity的判断并用newCapacity接收:

c 复制代码
int newCapacity=ps->capacity==0?4:2*ps->capacity;

Tips:这里我们不这样写,在初始化的时候直接给capacity一个空间也是可以的,但同时需要也给arr动态分配一个空间

step3:尾插申请内存失败的处理

我们知道realloc申请内存失败返回NULL,这样会改变原来arr的值,使它为空

那么我们需要采取不直接用arr申请,而是先用一个临时变量tmp来申请和判断是否申请成功

c 复制代码
SLDataType* tmp=(SLDataType*)realloc(ps->arr,newCapacity*sizeof(SLDataType));
if(tmp==NULL)//如果为空
{
	perror("realloc fail");
	exit(1);//给一个非零退出码
}
step4:尾插空间申请成功
c 复制代码
ps->arr=tmp;//arr指向tmp申请的空间
ps->capacity=newCapacity;//capacity指向newCapacity申请的空间
step5:尾插插入数据为空指针

如果插入的是空指针,会导致无法对空指针解引用而报错:

SLPushBack(NULL,5);

解决方式:

温柔解法:

c 复制代码
if(ps==NULL)
{
	return;
}

暴力解法:

c 复制代码
assert(ps);//等价于assert(ps!=NULL);

Tips:要加上#include<assert.h>头文件

尾插完整代码
c 复制代码
void SLPushBack(SL* ps,SLDataType x)
{
	//判断插入指针是否为NULL
	if(ps==NULL)
	{
		return;
	}
	//判断是否有空间
	if(ps->capacity==ps->size)
	{
		//增容
		int newCapacity=ps->capacity==0?4:2*ps->capacity;
		SLDataType* tmp=(SLDataType*)realloc(ps->arr,newCapacity*sizeof(SLDataType));
		//空间申请失败
		if(temp==NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		//空间申请成功
		ps->arr=tmp;
		ps->capacity=newCapacity;
	}
	//插入数据并使size++
	ps->arr[ps->size++]=x;
}

(2)头插

step1:头插的思路

先将原有数据整体后移,插入新数据后size++

c 复制代码
for(int i=ps->size;i>0;i--)
{
	ps->arr[i]=ps->arr[i-1];//将原有数据整体后移
}
ps->arr[0]=x;//将arr[0]赋值为x
pa->size++;

结束条件: 由于for循环最后是arr[1]=arr[0],所以最后i=1,由此得出当i=0时循环结束,因此循环结束条件为i>0

step2:头插的增容及空间判断

和尾插法一样,头插法也要进行空间是否足够的判断,那么我们直接将判断空间的代码封装成一个函数;

c 复制代码
void SLCheckCapacity(SL* ps)
{
	if(ps->capacity==ps->size)
	{
		int newCapacity=ps->capacity==0?4:2*ps->capacity;
		ps->arr=(SLDataType*)realloc(ps->arr,newCapacity*2*sizeof(SLDataType));
		SLDataType* tmp=(SLDataType*)realloc(ps->arr,newCapacity*sizeof(SLDataType));
		if(tmp==NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr=tmp;
		ps->capacity=newCapacity;
	}
}
step3:头插插入数据为空指针

同尾插解法

头插完整代码
c 复制代码
void SLPushFront(SL*ps,SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	for(int i=ps->size;i>0;i--)
	{
		ps->arr[i]=ps->arr[i-1];
	}
	ps->arr[0]=x;
	ps->size++;
}

(3)尾删

尾删的思路

先判断顺序表是否为空,不为空可以给size-1一个默认值-1再size--,也可以直接size--

尾删法代码
c 复制代码
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//判断顺序表是否为空
	//ps->arr[ps->size-1]=-1;这个代码有没有都可以,直接size--不影响增删查改
	--ps->size;
}

(4)头删

头删的思路

除下标为0的数以外整体数据前挪一位

头删法代码
c 复制代码
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//整体数据前挪一位
	for(int i=0;i<ps->size-1;i++)
	{
		ps->arr[i]=ps->arr[i+1];
	}
	ps->size--;
}

(5)指定位置插入/删除数据

指定位置插入数据

pos为要插入数据的位置的下标,把pos及之后的数据总体往后挪一位,最后给pos处赋值后size++

c 复制代码
void SLInsert(SL* ps,int pos,SLDataType x)
{
	assert(ps);//顺序表地址不能为空
	assert(pos>=0&&pos<=ps->size);//pos的范围
	//判断插入数据空间是否足够
	SLCheckCapacity(ps);
	//pos及之后的数据整体往后挪一位
	for(int i=ps->size;i>pos;i--)//因为最后一次是i=pos+1,所以循环结束条件是i>pos
	{
		ps->arr[i]=ps->arr[i-1];//最后一次是arr[pos+1]=arr[pos];
	}
	ps->arr[pos]=x;
	ps->size++;
}
在指定位置删除数据

把pos后面的数据整体前挪一位,最后size--

c 复制代码
void SLErase(SL*ps,int pos)
{
	assert(ps);
	//这里也可以加一个顺序表不为空的检验assert(ps->size);
	assert(pos>=0&&pos<ps->size);//由于size位置无有效数据,因此pos不能等于size
	for(int i=pos;i<ps->size-1;i++)
	{
		ps->arr[i]=ps->arr[i+1];//最后arr[size-2]=arr[size-1],因此循环的结束条件为i<ps->size-1
	}
	ps->size--;
}

6.顺序表的查找

c 复制代码
int SLFind(SL* ps,SLDataType x)
{
	assert(ps);
	for(int i=0;i<ps->size;i++)//遍历顺序表里的数据
	{
		if(ps->arr[i]==x)//找到和目标数据相等的数据
		{
			return i;//返回下标
		}
	}
	return -1;//没找到,返回无效值
}

7.顺序表的打印

c 复制代码
void SLPrint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
	printf("\n");
}

8.顺序表完整实现

SeqList.h

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//提取类型,方便后续替换
typedef int SLDataType;
//顺序表的定义
typedef struct SeqList
{
	SLDataType* arr;
	SLDataType size;
	SLDataType capacity;
}SL;

void SLInit(SL* ps);//初始化
void SLDestory(SL* ps);//销毁

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

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

void SLPrint(SL s);//打印
int SLFind(SL* ps, SLDataType x);//查找数据

SeqList.c

c 复制代码
#include"SeqList.h"
//顺序表初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
//顺序表的销毁
void SLDestory(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
//空间判断
void SLCheckCapacity(SL* ps)
{
	if (ps->capacity == ps->size)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}
	SLCheckCapacity(ps);
	ps->arr[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	--ps->size;
}
//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
//在指定位置插入数据
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->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}
//在指定位置删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
//顺序表的查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;
}
相关推荐
智者知已应修善业2 小时前
【51单片机ADC-MAX1241/ADC0832驱动】2023-6-6
c++·经验分享·笔记·算法·51单片机
gumichef2 小时前
*链表OJ
数据结构·链表
Aliex_git2 小时前
Nuxt 学习笔记(一)
前端·笔记·学习
枷锁—sha2 小时前
【CTFshow-pwn系列】03_栈溢出【pwn 072】详解:无字符串环境下的多级 Ret2Syscall 与 BSS 段注入
服务器·网络·汇编·笔记·安全·网络安全
如君愿2 小时前
考研复习 Day 21 | 数据结构与算法--排序(上)
数据结构·考研·排序算法·记录考研
yoona10203 小时前
使用 Auto-Redbook-Skills 自动生成并发布redbook图文笔记
笔记·小红书·skills·redbook
草履虫君3 小时前
我们用纯命令行方式,给openclaw配置minimax2.7
数据库·经验分享·功能测试·ai
hnjzsyjyj3 小时前
全排列问题DFS实现执行示意图
数据结构·dfs
U盘失踪了3 小时前
Python 的 urljoin:告别手动拼接 URL 的烦恼
笔记·学习