C语言数据结构-动态顺序表

文章目录

  • 1.线性表的概念
  • 2.顺序表的分类
    • 2.1 静态顺序表
    • 2.2 动态顺序表
  • 3 动态顺序表的实现
    • 3.0 完整代码
    • 3.1 解决方案
    • 3.2 初始化顺序表与销毁
    • 3.3 检查扩容
    • 3.4 尾插和头插
    • 3.5 顺序表是否为空
    • 3.6 尾删和头删
    • 3.7 打印数据
    • 3.8 定点插入与删除
    • 3.9 查找数据
    • 3.10 调试


1.线性表的概念

线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
线性表在逻辑上是线性结构,就是连续的一条直线。但是在物理上并不一定是线序的,线性表在物理上存储是,通常以数组和链式结构的形式存储。

2.顺序表的分类

2.1 静态顺序表

概念:使用定长数组存储元素

静态顺序表的缺陷:空间少了不够用,空间多了浪费。

//静态顺序表
typedef int SLDataType;
#define N 7
typedef struct SeqList
{
	SLDataType a[N];	//定长数组
	int size;	//有效数据个数
}SL;

C语言数据结构-动态顺序表_第1张图片

2.2 动态顺序表

使用动态开辟的数组存储

//动态顺序表
typedef struct SeqList
{
	SLDataType* a;	//指向动态数组的指针
	int size;		//有效数据个数
	int capacity;	//空间容量
};

C语言数据结构-动态顺序表_第2张图片

3 动态顺序表的实现

3.0 完整代码

笔者的注释部分含有草稿笔记,不清楚
Seqlist.h部分

#pragma once  //防止头文件被多次包含
#include
#include
#include
#include

//动态顺序表
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* a;	//动态指针数组
	int size;	// 顺序表中有效的个数
	int 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);

//打印SL
void SLprint(SL* ps);

//判断SL是否为空
bool SLIsEmpty(SL* ps);

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

//删除指定位置的数据
void SLErase(SL* ps, int pos);

//查找数据
bool SLFind(SL* ps, SLDataType x);

SeqList.c部分

#include"SeqList.h"

void SLInit(SL* ps)   //传过来顺序表,对它进行初始化
{
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

void SLDestory(SL* ps)
{
	if(ps->a)
		free(ps->a);
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

void SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		//空间已经满了,需要扩容
		int newCapcity = ps->capacity == 0 ? 2 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapcity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail.\n");
			//return 1;//这个会有弱警告,但不妨碍.warning C4098: “SLCheckCapacity”:“void”函数返回值.因此所以用exit(1);退出
			exit(1);
		}
		ps->a = tmp;
		ps->capacity = newCapcity;
	}
}

void SLPushBack(SL* ps, SLDataType x)
{
	//暴力的方式
	assert(ps); 	//assert(ps != NUll);简化一下,()内放ps就行了
	//if (ps == NULL)
	//{
	//	return;
	//}

	//(1)空间足够,在后面尾插
	//(2)空间不够,需要扩容
	SLCheckCapacity(ps);


	//直接插入数据
	ps->a[ps->size++] = x;
}

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	//判断空间是否足够,不够就扩容
	SLCheckCapacity(ps);
	
	//数据后移
	for (size_t i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;
	ps->size++;
}

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(!SLIsEmpty(ps));//非空

	ps->size--;//ps->a[ps->size-1]=0;这样显然没有必要,直接让他有效个数减一就行了,即ps->size--;
}

//头删  
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(!SLIsEmpty(ps));
	//让后面的数据往前挪动一位
	for (size_t i = 0; i < ps->size-1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

//打印
void SLprint(SL* ps)
{
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

//判断SL是否为空
bool SLIsEmpty(SL* ps)
{
	assert(ps);

	return ps->size == 0;
}

//在指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)	//对pos限制范围
{
	assert(ps);
	// 对pos限制范围  边界0是可以的
	assert(pos >= 0 && pos<=ps->size);
	
	//扩容
	SLCheckCapacity(ps);
	//把pos位置及以后的数据往后挪动一位
	方法一
	//for (size_t i = ps->size; i > pos; i--)
	//{
	//	ps->a[i] = ps->a[i - 1];
	//}
	方法一
	for (size_t i = ps->size-1; i > pos-1; i--)
	{
		ps->a[i+1] = ps->a[i];
	}
	ps->a[pos] = x;
	ps->size++;
}

//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(!SLIsEmpty(ps));
	assert(pos >= 0 && pos < ps->size);
	for (size_t i = pos; i < ps->size-1; i++)
	{
		//最后一个进来的i是ps->size-2,  ps->a[ps->size-2]=ps->a[ps->size-1]
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

//查找数据
bool SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (size_t i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			//找到了
			return true;
		}
	}
}

test.c部分

#include"SeqList.h"

void SLtest()
{
	SL sl;
	SLInit(&sl);
	//一下顺序表的操作
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);//1,2,3,4

	SLprint(&sl);
	//头插
	SLPushFront(&sl, 5);//5,1,2,3,4
	SLPushFront(&sl, 6);//6,5,1,2,3,4
	SLPushFront(&sl, 7);//7,6,5,1,2,3,4

	SLprint(&sl);

	//尾删
	SLPopBack(&sl);		//7,6,5,1,2,3
	SLprint(&sl);
	SLPopBack(&sl);		//7,6,5,1,2
	SLprint(&sl);

	//头删
	SLPopFront(&sl);	//6,5,1,2
	SLprint(&sl);
	SLPopFront(&sl);	//5,1,2
	SLprint(&sl);
	//SLPopFront(&sl);	//1,2
	//SLprint(&sl);
	//SLPopFront(&sl);	//2
	//SLprint(&sl);
	到这里就没有了
	//SLPopFront(&sl);	//NULL
	//SLprint(&sl);


	测试bool SLIsEmpty(SL* ps) 出现报错	assert(!SLIsEmpty(ps));成功
	//SLPopFront(&sl);	//1,2
	//SLprint(&sl);		//Assertion failed: !SLIsEmpty(ps),


	//在指定位置之前插入数据
	//SLInsert(&sl, 100, 12);//报错,范围太大

	SLInsert(&sl, 2, 12); //5, 1, 12,2
	SLprint(&sl);
	SLInsert(&sl, sl.size, 9);//5, 1, 12,2,9
	SLprint(&sl);

	//删除指定位置的数据   pos指的是索引
	//SLErase(&sl, 20);//报错,assert成功
	//SLprint(&sl);
	SLErase(&sl, 0);//1, 12,2,9
	SLprint(&sl);
	SLErase(&sl, 0);//12,2,9
	SLprint(&sl);
	SLErase(&sl, 2);//12,2
	SLprint(&sl);

	bool RetFind = SLFind(&sl, 2);
	if (RetFind)
	{
		printf("找到了\n");
	}
	else
		printf("没找到\n");


	//以上顺序表的操作

	SLDestory(&sl);
}
int main()
{
	SLtest();
	return 0;
}

这就是完整代码,下面我们简单分析
C语言数据结构-动态顺序表_第3张图片

3.1 解决方案

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口

(1)我们增删改查接口放在头文件SeqList.h中,源文件放SeqList.c实现接口函数,test.c测试代码
SeqList.h引用我们需要的头文件,
#include,#include,#include,#include
(2)我们再SeqList.h声明函数void SLInit(SL* ps) ;,在SelList.c定义函数void SLInit(SL* ps) { }
(3)在test.c测试中,我们使用指针方式调用成员,所以函数的声明中,都传递的地址,例如void SLInit(SL* ps);
C语言数据结构-动态顺序表_第4张图片

//动态顺序表
typedef int SLDataType; //SL = struct SeqList	方便使用
typedef struct SeqList
{
	SLDataType* a;
	int size;	// 顺序表中有效的个数
	int capacity;	//顺序表当前的空间大小
}SL;	//简化结构体名称,方便后续书写与修改

3.2 初始化顺序表与销毁

(1) 初始化顺序表

ps->a=NULL 指针数组置空
ps->size = ps->capacity = 0 有效个数,空间置空

void SLInit(SL* ps)   //传过来顺序表,对它进行初始化
{
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

(2) 销毁顺序表

有初始化就有销毁,使用free()释放开辟的动态数组,首先保证当前数组a不为空,所以进行一次if判断.
若不释放空间,会出现野指针的情况.

void SLDestory(SL* ps)
{
	if(ps->a)
		free(ps->a);
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

3.3 检查扩容

判断顺序表是否有足够的空间,若空间足够,直接插入,否则扩容.而每一次增加数据,增加空间,若频繁扩容,会降低程序的性能.于是有人提出,采用原数据1.5倍或者2倍扩容.
我们采用realloc扩容,可以进行大小调整,给顺序表扩容,数据类型是(SLDataType*),realloc的第二参数是size,我们拿空间*数据类型大小,newCapcity * sizeof(SLDataType)
newCapcity判断capacity是否为0,若为0,初始化为2,这样避免0乘2后依旧为0
判断是否能扩容成功,用tmp接收创建的内容.判断tmp是否为空,然后就可以接收了,ps->a = tmp;ps->capacity = newCapcity;

void SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		//空间已经满了,需要扩容
		//三目表达式,如果capacity为0,令它空间为2,不够的话,2倍递增
		int newCapcity = ps->capacity == 0 ? 2 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapcity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail.\n");
			exit(1);//给状态码1,退出
		}
		ps->a = tmp;
		ps->capacity = newCapcity;
	}
}

3.4 尾插和头插

(1) 尾插

C语言数据结构-动态顺序表_第5张图片

判断ps是否为空,传入NULL就坏了,我们采用assert,较暴力简洁.
if(ps->NULL){
return ;
}
if判断不简洁

void SLPushBack(SL* ps, SLDataType x)
{
	//暴力的方式
	assert(ps); 	//assert(ps != NUll);简化一下,()内放ps就行了
	//扩容
	SLCheckCapacity(ps);
	
	//直接插入数据
	//ps->a[ps->size] = x;
	//size++;
	ps->a[ps->size++] = x;//上面两行代码使用后置++,简化
}

(2) 头插

for循环实现从下标1开始数据后移,下标0插入x,ps->a[0] = x;,记得size++,不然后续数据都是下标1的元素,ps->size++;

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	//判断空间是否足够,不够就扩容
	SLCheckCapacity(ps);
	//数据后移
	for (size_t i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;
	ps->size++;
}

C语言数据结构-动态顺序表_第6张图片

3.5 顺序表是否为空

顺序表为空,顺序表没有一个有效的数据,那么有效数据size的值是0,return ps->size == 0;返回’0’或’1’

bool SLIsEmpty(SL* ps)
{
	assert(ps);

	return ps->size == 0;
}

3.6 尾删和头删

(1) 尾删

void SLPopBack(SL* ps)
{
	assert(ps);
	assert(!SLIsEmpty(ps));//非空

	ps->size--;//这样显然没有必要,直接让他有效个数减一就行了	ps->a[ps->size - 1] = 0;
}

C语言数据结构-动态顺序表_第7张图片

(2) 头删

采用for直接覆盖的方式

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(!SLIsEmpty(ps));//不能一直删
	//让后面的数据往前挪动一位
	for (size_t i = 0; i < ps->size-1; i++)//最后一个有效数据的下标是ps->size-1
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

C语言数据结构-动态顺序表_第8张图片

3.7 打印数据

这个函数早就在前面使用了,由于思路的先后顺序,提前写了头尾增删,而这个函数的实现也比简单,也可用过调试查看,所有笔者就把它顺位靠后了

void SLprint(SL* ps)
{
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

3.8 定点插入与删除

(1) 定点插入

void SLInsert(SL* ps, int pos, SLDataType x)	//对pos限制范围
{
	assert(ps);
	// 对pos限制范围  边界0是可以的
	assert(pos >= 0 && pos<=ps->size);
	
	//扩容
	SLCheckCapacity(ps);
	//把pos位置及以后的数据往后挪动一位
	方法一
	//for (size_t i = ps->size; i > pos; i--)
	//{
	//	ps->a[i] = ps->a[i - 1];
	//}
	方法二
	for (size_t i = ps->size-1; i > pos-1; i--)
	{
		ps->a[i+1] = ps->a[i];
	}
	ps->a[pos] = x;
	ps->size++;
}

C语言数据结构-动态顺序表_第9张图片

(2)定点删除

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(!SLIsEmpty(ps));
	assert(pos >= 0 && pos < ps->size);
	for (size_t i = pos; i < ps->size-1; i++)
	{
		//最后一个进来的i是ps->size-2,  ps->a[ps->size-2]=ps->a[ps->size-1]
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

C语言数据结构-动态顺序表_第10张图片

3.9 查找数据

bool SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (size_t i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			//找到了
			return true;
		}
	}
}

3.10 调试

一定要亲自调式!!!
一定要亲自调式!!!
一定要亲自调式!!!

test.c函数中反复调试,打印.
test.c在目录3.0 完整代码那边
这里贴张警报图,大家也去调试调试
C语言数据结构-动态顺序表_第11张图片

终于学会了,看了三遍视频,第一遍是懵的,第二遍理解了,第三遍结合视频写笔记.
之后手写一遍,错误不断,那就不断结合课件调试

你可能感兴趣的:(C语言笔记,c语言,数据结构,开发语言)