【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)

欢迎来sobercq的博客喔,本期系列为【数据结构和算法】第四篇动态顺序表的实现

图文讲解动态顺序表,带大家理解顺序表的每个部分,最后还会有源码分享,感谢观看,支持的可以给个赞哇。

目录

 一、顺序表文件分类

二、顺序表结构

三、空间结构的初始化和销毁

测试部分:

四、顺序表数据的处理

(1)数据的插入和删除

1.尾插

2.头插

3.尾删

4.头删

5.任意位置的数据插入和删除

(2)数据的查找

五、顺序表问题


 一、顺序表文件分类

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第1张图片

SeqList用来存放头文件的声明

SeqList用来实现主体部分,数据的插入删除,还有查找数据

test.c就用来测试我们的代码,在每次写完一个部分的时候我们都需要测试一次

二、顺序表结构

typedef int SLDataType;

typedef struct SeqList
{
    SLDataType *a;
    size_t size;     //有效数据个数
    size_t capacity; //空间大小
}SL;

首先是顺序表的空间结构,我们用size记录有效数据个数,capacity用来存储空间大小,a是array数组缩写,typedef的重命名是为了以后方便修改数据类型,因为我们的数据元素可能是double类型,float类型,也可能是字符类型。所以我们重命名为SLDataType(SeqList Data Type顺序表的数据类型)。

三、空间结构的初始化和销毁

既然有了顺序表的空间结构那在使用之前我们就需要将顺序表空间初始化,在使用后我们就需要将其销毁

头文件里我们写入以下两行代码,

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第2张图片

在头文件中我们声明一下数据初始化的函数SLInit,这个函数具体呢,我们要把指针a的空间赋值成空指针,表示空表,size的值赋值成0,capacity也是0.但是在其他方法中,可能空间不一定是零,比如说开局赋值个四也是可以的。

void SLInit(SL *psl)
{
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}

数据的销毁呢,就是我们要把已创建的空间释放了,把指针赋值成空指针,防止a成为野指针,将size和capacity重置为0。

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

测试部分:

 在每个部分写完之后都需要测试一下,所以我们在test.c文件中写如下代码

void test1(SL *psl)
{
	SLInit(&psl);

	SLDestroy(&psl);
}

运行完之后,就能正常看到结束的标志了,那说明我们运行的没问题。 

四、顺序表数据的处理

(1)数据的插入和删除

1.尾插

 尾插(push back),就是将数据从顺序表的最后一个位置插入

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第3张图片

分情况讨论,当空间不够的时候我们如果要从尾部插入数据的话,这个时候就要扩容了,我们将扩容写到一个函数里,因为会常用,所以我们写一个函数,用来检查空间。但是在插入之前我们需要检查指针,指针不能为空。(同理在数据的初始和销毁中同样也要补充上)

头文件放:

//检查空间
void SLCheckCapacity(SL* ps);

SeqList.c中写:

void SLCheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		size_t newCapacity = ps->capacity == 0 ? 4 :ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
}

 解析:这里我们用三目运算符?:来判断psl顺序表结构空间大小,如果为0则把4赋值给newcapacity,如果不是则把capacity空间扩大两倍。(扩大倍数不一定只有两倍可以是1.5也可以是三倍或者四倍),其次我们用tmp接受realloc返回的地址。最后将tmp赋给a,新空间newcapacity给a中的capacity。

检查完空间,当空间足够的话,直接将数据写进空间就好了。

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第4张图片

数据插入后,结构中有效数据加1。 

//头文件
void SLPrint(SL*ps);
//SeqList.c文件
void SLPrint(SL* ps)
{
    assert(ps);
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d", ps->a[i]);
	}
	printf("\n");
}

测试部分 

 我们写了这么多,写一个打印函数来测试看看我们的代码是否能跑吧

//test.c中的测试部分
void test1(SL *psl)
{
	SLInit(&psl);
	//尾插的测试
	SLPushBack(&psl, 1);
	SLPushBack(&psl, 2);
	SLPushBack(&psl, 3);
	SLPushBack(&psl, 4);
	SLPushBack(&psl, 5);
	SLPushBack(&psl, 6);
	SLPushBack(&psl, 7);
	SLPushBack(&psl, 8);
	SLPushBack(&psl, 9);
	SLPushBack(&psl, 10);
	
	SLPrint(&psl);
	SLDestroy(&psl);
}

结果我们可以看到1-10的数字都被打印了出来,那么这部分就结束了。 

2.头插

头插(Push Front),从第一个位置插入数据,注意扩容是从尾部扩充的(时间复杂度O(N),如果插入N个数据就是O(N^2))

        【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第5张图片

首先我们要从头部插入的话,第一步仍然是判断指针是否为空,和空间是否足够,那扩容是从后面开始扩的,如果我们需要从头部插入,我们就需要做一个数据挪动,将所有的有效数据直接向后移动一位,空出第一个位置进行数据存放。(因为需要做数据挪动,所以尽量减少使用头插)

//头文件
void SLPushFront(SL* ps, SLDataType x);
//SeqList.c
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	//移动数据
	for (int i = ps->size - 1; i >=0; i--)
	{
		ps->a[i+1] = ps->a[i];
	}

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

 测试部分 

void test1(SL *psl)
{
	SLInit(&psl);
	//尾插的测试
	SLPushBack(&psl, 1);
	SLPushBack(&psl, 2);
	SLPushBack(&psl, 3);
	SLPushBack(&psl, 4);
	SLPushBack(&psl, 5);
	SLPushBack(&psl, 6);
	SLPushBack(&psl, 7);
	SLPushBack(&psl, 8);
	SLPushBack(&psl, 9);
	SLPushBack(&psl, 10);
	
	//头插的测试
	SLPushFront(&psl, 11);
	SLPushFront(&psl, 12);
	SLPushFront(&psl, 13);
	SLPushFront(&psl, 14);
	SLPushFront(&psl, 15);

	SLPrint(&psl);
	SLDestroy(&psl);
}

 

在测试完后我们可以看到11-15都被插入到空间中了,那么头插的这部分也结束了。 

3.尾删

尾删(pop back),尾部删除就是将末尾的数据删去。

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第6张图片

在尾部删除的时候我们可以只做size--,将空出来的一个空间不做操作,下次进行数据插入的时候,会将该空间的数据进行覆盖,在表面上我们就算“删除”了这个数据。

在尾部删除的时候我们一定要注意空间,size是不能减到负数的,也就是它的最小值只有0;这里我们可以用assert来判断size的值。

//头文件
void SLPopBack(SL* ps);
//SeqList.c文件
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);

	ps->size--;
}

测试部分

void test2(SL* psl)
{
	SLInit(&psl);
	//尾插
	SLPushBack(&psl, 1);
	SLPushBack(&psl, 2);
	SLPushBack(&psl, 3);
	SLPushBack(&psl, 4);
	SLPushBack(&psl, 5);
    //尾删
	SLPopBack(&psl);

	SLPrint(&psl);
	SLDestroy(&psl);
}

那根据结果来看肯定是这个1,2,3,4。

 

4.头删

头删,就是说头部删除,也就是从第一个位置开始删除

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第7张图片

头部删除只能从前往后移动,删除后有效数据的值减少1。

//头文件
void SLPopFront(SL* ps);
//SeqList.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--;
}

测试部分 

void test3(SL *psl)
{
	SLInit(&psl);
	//尾插
	SLPushBack(&psl, 1);
	SLPushBack(&psl, 2);
	SLPushBack(&psl, 3);
	SLPushBack(&psl, 4);
	SLPushBack(&psl, 5);
	//头部删除
	SLPopFront(&psl);

	SLPrint(&psl);
	SLDestroy(&psl);
}

 根据程序来看,这个程序会输出2,3,4,5.

5.任意位置的数据插入和删除

任意位置的插入:最主要是注意pos的范围,pos只能在0到size的区间内,但注意pos可以等于size,等于size的时候就相当于尾插。

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第8张图片

插入的时候我们要找到pos的位置,其实pos无论是看作下标还是元素位置都一样。数组从0开始是为了实现自洽体系,和指针什么的联系起来。

//头文件
void SLInsert(SL* ps, int pos, SLDataType x);
//SeqList.c
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);

	SLCheckCapacity(ps);
	//数据移动
	for (int i = ps->size - 1; i >= pos; i--)
	{
		ps->a[i+1] = ps->a[i];
	}

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

 测试部分

void test4(SL* psl)
{
	SLInit(&psl);
	//尾插
	SLPushBack(&psl,1);
	SLPushBack(&psl,2);
	SLPushBack(&psl,3);
	SLPushBack(&psl,4);
	SLPushBack(&psl,5);
	//任意位置插入
	SLInsert(&psl,3, 60);

	SLPrint(&psl);
	SLDestroy(&psl);
}

 输出结果:

任意位置的数据删除:

【数据结构和算法】4.超详细解析动态顺序表的实现(图文解析,附带源码)_第9张图片

//头文件
void SLErase(SL* ps, int pos);
//SeqList
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);

	//数据移动
	for (int i = pos; i size-1; i++)
	{
		ps->a[i] = ps->a[i+1];
	}

	ps->size--;
}

 测试部分

void test4(SL* psl)
{
	SLInit(&psl);
	//尾插
	SLPushBack(&psl,1);
	SLPushBack(&psl,2);
	SLPushBack(&psl,3);
	SLPushBack(&psl,4);
	SLPushBack(&psl,5);
	//任意位置插入
	SLInsert(&psl,3, 60);
	SLPrint(&psl);
	//任意位置的删除
	SLErase(&psl, 2);

	SLPrint(&psl);
	SLDestroy(&psl);
}

输出结果:

(2)数据的查找

数据的查找就比较容易,pos是我们查找的对象,我们使用一个循环遍历一下空间,如果相等我们就返回这个数据的下标。

//头文件
int SLFind(SL* psl, SLDataType x);
//SeqList.c
int SLFind(SL* psl, SLDataType x)
{
	assert(psl);

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

	return -1;
}

测试部分

void test4(SL* psl)
{
	SLInit(&psl);
	//尾插
	SLPushBack(&psl,1);
	SLPushBack(&psl,2);
	SLPushBack(&psl,3);
	SLPushBack(&psl,4);
	SLPushBack(&psl,5);
	//任意位置插入
	SLInsert(&psl,3, 60);
	SLPrint(&psl);
	//任意位置的删除
	SLErase(&psl, 2);
	//查找
	printf("%d\n",SLFind(&psl, 60));

	SLPrint(&psl);
	SLDestroy(&psl);
}

 输出结果:

五、顺序表问题

顺序表的问题
1、尾部插入效率还不错,头部或者中间插入删除,需要挪动数据,效率低下
2、满了以后只能扩容。扩容是有一定的消耗的,扩容一般是存在一定的空间浪费,扩得越多,可能浪费越多,扩的少了,那么可能会频繁扩容. 

所以也没有方法能解决这个问题呢? 

下期预告:

数据结构和算法 5.单链表的介绍和实现 

源码分享:

头文件:

#define _CRT_SECURE_NO_WARNINGS 1

#include
#include

typedef int SLDataType;

typedef struct SeqList
{
    SLDataType* a;
    size_t size;
    size_t 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 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);
//查找数据
int SLFind(SL* psl, SLDataType x);

SeqList.c

#include"SeqList.h"
//空间初始化
void SLInit(SL *psl)
{
	assert(psl);
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}
//空间销毁
void SLDestroy(SL* psl)
{
	assert(psl);
	if (psl->a != NULL)
	{
		free(psl->a);
		psl->a = NULL;
		psl->size = 0;
		psl->capacity = 0;
	}
}
//尾部插入
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);

	ps->a[ps->size] = x;
	ps->size++;
}
//头部插入
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	//移动数据
	for (int i = ps->size - 1; i >=0; i--)
	{
		ps->a[i+1] = ps->a[i];
	}

	ps->a[0] = x;
	ps->size++;
}
//尾部删除
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);

	ps->size--;
}
//头部删除
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--;
}
//任意位置的插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);

	SLCheckCapacity(ps);
	//数据移动
	for (int i = ps->size - 1; i >= pos; i--)
	{
		ps->a[i+1] = ps->a[i];
	}

	ps->a[pos] = x;
	ps->size++;
}
//任意位置的删除
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);
	//数据移动
	for (int i = pos; i size-1; i++)
	{
		ps->a[i] = ps->a[i+1];
	}

	ps->size--;
}
//查找数据
int SLFind(SL* psl, SLDataType x)
{
	assert(psl);

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

	return -1;
}
//打印
void SLPrint(SL* ps)
{
	assert(ps);
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
//检查空间
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		size_t newCapacity = ps->capacity == 0 ? 4 :ps->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
}

 test.c

#include"SeqList.h"

void test1(SL *psl)
{
	SLInit(&psl);
	//尾插的测试
	SLPushBack(&psl, 1);
	SLPushBack(&psl, 2);
	SLPushBack(&psl, 3);
	SLPushBack(&psl, 4);
	SLPushBack(&psl, 5);
	SLPushBack(&psl, 6);
	SLPushBack(&psl, 7);
	SLPushBack(&psl, 8);
	SLPushBack(&psl, 9);
	SLPushBack(&psl, 10);
	
	//头插的测试
	SLPushFront(&psl, 11);
	SLPushFront(&psl, 12);
	SLPushFront(&psl, 13);
	SLPushFront(&psl, 14);
	SLPushFront(&psl, 15);

	SLPrint(&psl);
	SLDestroy(&psl);
}

void test2(SL* psl)
{
	SLInit(&psl);
	//尾插的测试
	SLPushBack(&psl, 1);
	SLPushBack(&psl, 2);
	SLPushBack(&psl, 3);
	SLPushBack(&psl, 4);
	SLPushBack(&psl, 5);
	//尾部删除
	SLPopBack(&psl);
	
	SLPrint(&psl);
	SLDestroy(&psl);
}

void test3(SL *psl)
{
	SLInit(&psl);
	//尾插
	SLPushBack(&psl, 1);
	SLPushBack(&psl, 2);
	SLPushBack(&psl, 3);
	SLPushBack(&psl, 4);
	SLPushBack(&psl, 5);
	//头部删除
	SLPopFront(&psl);

	SLPrint(&psl);
	SLDestroy(&psl);
}
void test4(SL* psl)
{
	SLInit(&psl);
	//尾插
	SLPushBack(&psl,1);
	SLPushBack(&psl,2);
	SLPushBack(&psl,3);
	SLPushBack(&psl,4);
	SLPushBack(&psl,5);
	//任意位置插入
	SLInsert(&psl,3, 60);
	SLPrint(&psl);
	//任意位置的删除
	SLErase(&psl, 2);
	//查找
	printf("%d\n",SLFind(&psl, 60));

	SLPrint(&psl);
	SLDestroy(&psl);
}
int main()
{
	SL sl;
	//test1(&sl);
	//test2(&sl);
	//test3(&sl);
	test4(&sl);
	return 0;
}

 

你可能感兴趣的:(数据结构和算法,数据结构,算法,c语言)