目录
一、数据结构
1.数据结构的基本知识
数据结构的定义 :计算机存储、组织数据的方式
数据结构包括:数据逻辑结构和数据存储结构
(1)数据逻辑结构
- 集合结构
- 线性结构
- 树结构
- 图结构或网状结构
也可以用另一种方式概括:
- 线性结构: 线性表、栈、队、串、数组、广义表
- 非线性结构: 集合结构、树、图
(2)数据存储结构
- 顺序存储结构
- 链式存储结构
二、顺序表
1.顺序表的引入
(1)顺序表与数组的关系
顺序表的底层是数组

数组:
- 修改:arr[pos]=x;
- 插入:通过循环找到数组中已有元素个数后插入数据
- 删除:通过循环找到数组中已有元素个数后删除数据
申请数组空间:
- 定长:int arr[10];
- 动态内存开辟: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;
}