数据结构初阶表现----动态顺序表

概述:

    相信大多数代码小白在学完c语言,进入数据结构的学习后,都会有疑惑,不清楚数据结构究竟是在学什么,学这个到底有什么用。简而言之,数据结构就是对数据的管理,大部分是动态的。其实这个概念还是抽象的,所以今天带大家走进数据结构初级的一个具体表现---->顺序表

1.文件创建:

      养成好习惯,创建三个文件Seqlist.h  Seqlist.c  test.c

Seqlist.h: 头文件,放入结构体和函数的声明。

Seqlist.c:函数接口文件,用来存放函数的定义。

test.c: 测试文件,在写代码过程中用来测试函数的可行性。

数据结构初阶表现----动态顺序表_第1张图片

2.结构体的定义:

    不难发现,如果数据的内存空间是静态的,也就是开辟一次后空间大小将不可修改,这就有一个难以逃避的问题,如果空间开少了,数据就放不下,如果空间开大了,就造成了空间浪费。

    所以动态的顺序表可以很好地缓解这个问题,因为动态的顺序表可以随着数据的多少来改变空间的大小(但还是会有空间浪费的问题,下一期的链表可以很好地解决这个问题)。

    既然是动态的顺序表,那么结构体定义时,不仅要有数据,还要记录数据的个数和空间的容量:

typedef struct Seqlist
{
	SeqDatetype* a;//指向动态数组的指针
	int size;//数组内的元素个数
	int capacity;//数组的容量
}Seqlist;

为了方便以后数据类型的修改,我们重定义一下数据的类型,这次的数据类型我用int来演示:

typedef int SeqDatetype;

3.初始化函数:

    顺序表刚创建时肯定需要初始化,只需将*a置为NULL,并且将size和capacity赋值为0就行,

void SeqlistInit(Seqlist* s1)//初始化数组
{
	assert(s1);//防止s1为空指针
	s1->a = NULL;
	s1->capacity = 0;
	s1->size = 0;
}

这里要先用assert暴力检查一下防止s1为空指针。

特别注意:这里一定要用传址调用,因为这里需要对数据修改,如果传值的话,形参只是实参的一份临时拷贝,对形参的修改无法对实参产生影响。

4.容量检查函数:

    当容量不足时,需要扩容,这里一般扩容到原来的两倍,注意如果原来的容量为0不能直接把容量×2哦,如果容量为0可以先把容量改为4

    说起扩容,就很容易想起我们的内存函数realloc,这里我们在www.cplusplus.com查询一下它的基本用法:

数据结构初阶表现----动态顺序表_第2张图片

realloc:传入需要扩容数据的起始地址和需要扩容到的内存大小(这个起始地址指向的空间必须是malloc或者calloc开辟的空间

void SeqCheckcapacity(Seqlist* s1)//容量检查,容量不足则扩容
{
	assert(s1);
	if (s1->size == s1->capacity)
	{
		SeqDatetype newcapacity = s1->capacity == 0 ? 4 : s1->capacity * 2;
		SeqDatetype* tmp1 = (SeqDatetype*)realloc(s1->a, sizeof(SeqDatetype) * newcapacity);
		if (tmp1 == NULL)
		{
			perror("realloc");
			return;
		}
		s1->a = tmp1;
		s1->capacity = newcapacity;
	}
}

特别注意:realloc开辟空间可能失败,如果失败返回的就是NULL,所以先创建一个临时指针变量tmp1来存放realloc的返回值,再进行判断来决定是否赋值给a。

5.尾插函数:

    尾插即为在数据的尾部插入数据:

数据结构初阶表现----动态顺序表_第3张图片

不难发现,其实就是在下标为size的位置插入数据,这样代码就好写了。

注意插入之前要用容量检查函数检查一下容量,容量不足时要先扩容。

void Seqinsertback(Seqlist* s1,SeqDatetype x)//尾插函数实现
{
	assert(s1);
	SeqCheckcapacity(s1);//先检查容量,容量不足时需要先扩容
	s1->a[s1->size] = x;
	s1->size++;
}

6.打印函数:

    数据插入成功后,我们可以写一个打印函数检查一哈,用for循环遍历一遍就行:

void Seqlistprintf(Seqlist* s1)//打印顺序表
{
	assert(s1);
	int i = 0;
	for (i = 0; i < s1->size; i++)
	{
		printf("%d ", s1->a[i]);
	}
	printf("\n");
}

这时我们就可以在test.c中检验一下我们的尾插函数是否可行:

数据结构初阶表现----动态顺序表_第4张图片

完美实现!

7.头插函数:

    头插即为在顺序表头部插入数据,可把所有数据右移一位,然后在下标为0处插入数据:

数据结构初阶表现----动态顺序表_第5张图片

代码实现:

void Seqinserthead(Seqlist* s1, SeqDatetype x)//头插
{
	assert(s1);
	SeqCheckcapacity(s1);//先检查容量,容量不足时需要先扩容
	int i = 0;
	for (i = 0;i < s1->size; i++)//整体右移一位,把第一位空出来,放入x
	{
		s1->a[s1->size - i] = s1->a[s1->size - 1 - i];
	}
	s1->a[0] = x;
	s1->size++;
}

放入test.c中检查一下:

数据结构初阶表现----动态顺序表_第6张图片

完美实现!

8.尾删函数:

    尾删即为删除尾部的数据,其实只需将size-1,因为我们在顺序表中的数据只能访问下标size以内的,下标size以外的都是无效数据,这样虽然没有将尾部数据修改,但下次插入数据时会自动将这个数据覆盖。

void Seqdelback(Seqlist* s1)//尾删函数
{
	assert(s1);
	s1->size--;
}

同样在test.c中检查一哈:

数据结构初阶表现----动态顺序表_第7张图片

完美实现!

特别注意:这里不要想着用free释放这个尾部数据的空间,是为malloc和calloc开辟的内存空间不支持分期付款,不能只释放一部分。

9.头删函数:

    删除头部数据的函数,其实只要从第二个数据开始用for循环将整体数据向左移动一位,这样头部数据就会被第二个数据覆盖:

void Seqdelhead(Seqlist* s1)//头删函数
{
	assert(s1);
	int i = 0;
	for (i = 0; i < s1->size - 1; i++)
	{
		s1->a[i] = s1->a[i + 1];
	}
	s1->size--;
}

同样放入test.c中测试:

数据结构初阶表现----动态顺序表_第8张图片

10.指定位置插入函数:

    在下标为x处添加数据x1,只需将下标从x到size-1的数据全部右移一位,再在下标为x处赋值x1就行,但要注意移动要从后往前移动:

void Seqinsertpoint(Seqlist* s1, SeqDatetype x, SeqDatetype x1)//在下标为x处添加数据x1
{
	assert(s1);
	SeqCheckcapacity(s1);//先检查容量,容量不足时需要先扩容
	int j = 0;
	for (j = s1->size; j > x; j--)
	{
		s1->a[j] = s1->a[j - 1];
	}
	s1->a[x] = x1;
	s1->size++;
}

测试:

数据结构初阶表现----动态顺序表_第9张图片

完美实现!

11.指定位置删函数:

    同理,只需将下标为x处到size-1的数据向左移动一位就能将下标为x处的数据覆盖:

void Seqdelpoint(Seqlist* s1, SeqDatetype x)//在下标为x处删除数据
{
	int i = 0;
	for (i = x; i < s1->size-x+1; i++)
	{
		s1->a[i] = s1->a[i+1];
	}
	s1->size--;
}

测试:

数据结构初阶表现----动态顺序表_第10张图片

完美实现!

12.查找函数:

    在顺序表中查找一个数据,如果存在返回这个数据所在的下表,如果不存在提示不存在,只需用for循环遍历一遍数据表即可:

void SeqDatefind(Seqlist* s1, SeqDatetype x)//搜索数据输出下标
{
	assert(s1);
	int i = 0;
	int a = 0;
	for (i = 0; i < s1->size; i++)
	{
		if (s1->a[i] == x)
		{
			a++;
			printf("找到了,下标为%d\n", i);
		}
	}
	if (a == 0)
	{
		printf("没有这个数据\n");
	}
}

测试:

数据结构初阶表现----动态顺序表_第11张图片

完美实现!

13.排序函数(冒泡排序):

    数据管理最少不了的就是对数据进行排序,这里我用冒泡排序来实现:

void Seqsort(Seqlist* s1)//冒泡排序小到大
{
	int i = 0;
	for (i = 0; i < s1->size - 1; i++)
	{
		int j = 0;
		for (j = 0;j < s1->size - 1; j++)
		{
			if (s1->a[j] > s1->a[j + 1])
			{
				int tmp = 0;
				tmp = s1->a[j];
				s1->a[j] = s1->a[j + 1];
				s1->a[j + 1] = tmp;
			}
		}
	}
}

测试:

数据结构初阶表现----动态顺序表_第12张图片

这样就整个顺序表基本功能完成了。

不难发现这个动态顺序表只能缓解空间浪费的问题,并不能完全避免,而且里面像头插这些函数时间复杂度为O(n),想要得到一个更好的数据管理,既没有空间浪费,时间复杂度也不高,关注我,下期带大家一起学习链表!

全部代码如下:

Seqlist.h:

#include
#include
#include
#include
typedef int SeqDatetype;
typedef struct Seqlist
{
	SeqDatetype* a;//指向动态数组的指针
	int size;//数组内的元素个数
	int capacity;//数组的容量
}Seqlist;
void SeqlistInit(Seqlist* s1);//初始化函数
void SeqlistDestory(Seqlist* s1);//摧毁顺序表函数
void Seqlistprintf(Seqlist* s1);//打印顺序表的函数
void SeqCheckcapacity(Seqlist* s1);//容量检查函数
void Seqinsertback(Seqlist* s1,SeqDatetype x);//尾插函数
void Seqinserthead(Seqlist* s1, SeqDatetype x);//头插函数
void Seqdelback(Seqlist* s1);//尾删函数
void Seqdelhead(Seqlist* s1);//头删函数
void Seqinsertpoint(Seqlist* s1, SeqDatetype x, SeqDatetype x1);//在指定位置插入数据函数
void Seqdelpoint(Seqlist* s1, SeqDatetype x);//在指定位置删除数据
void SeqDatefind(Seqlist* s1, SeqDatetype x);//搜索数据输出下标
void Seqsort(Seqlist* s1);//排序函数(冒泡排序)

Seqlist.c:

#include"Seqlist.h"
void SeqlistInit(Seqlist* s1)//初始化数组
{
	assert(s1);//防止s1为空指针
	s1->a = NULL;
	s1->capacity = 0;
	s1->size = 0;
}
void SeqlistDestory(Seqlist* s1)//摧毁顺序表
{
	assert(s1);
	if (s1 != NULL)
	{
		free(s1->a);
		s1->a = NULL;
		s1->capacity = 0;
		s1->size = 0;
	}
}
void Seqlistprintf(Seqlist* s1)//打印顺序表
{
	assert(s1);
	int i = 0;
	for (i = 0; i < s1->size; i++)
	{
		printf("%d ", s1->a[i]);
	}
	printf("\n");
}
void SeqCheckcapacity(Seqlist* s1)//容量检查,容量不足则扩容
{
	assert(s1);
	if (s1->size == s1->capacity)
	{
		SeqDatetype newcapacity = s1->capacity == 0 ? 4 : s1->capacity * 2;
		SeqDatetype* tmp1 = (SeqDatetype*)realloc(s1->a, sizeof(SeqDatetype) * newcapacity);
		if (tmp1 == NULL)
		{
			perror("realloc");
			return;
		}
		s1->a = tmp1;
		s1->capacity = newcapacity;
	}
}
void Seqinsertback(Seqlist* s1,SeqDatetype x)//尾插函数实现
{
	assert(s1);
	SeqCheckcapacity(s1);//先检查容量,容量不足时需要先扩容
	s1->a[s1->size] = x;
	s1->size++;
}
void Seqinserthead(Seqlist* s1, SeqDatetype x)//头插
{
	assert(s1);
	SeqCheckcapacity(s1);//先检查容量,容量不足时需要先扩容
	int i = 0;
	for (i = 0;i < s1->size; i++)//整体右移一位,把第一位空出来,放入x
	{
		s1->a[s1->size - i] = s1->a[s1->size - 1 - i];
	}
	s1->a[0] = x;
	s1->size++;
}
void Seqdelback(Seqlist* s1)//尾删函数
{
	assert(s1);
	s1->size--;
}
void Seqdelhead(Seqlist* s1)//头删函数
{
	assert(s1);
	int i = 0;
	for (i = 0; i < s1->size - 1; i++)
	{
		s1->a[i] = s1->a[i + 1];
	}
	s1->size--;
}
void Seqinsertpoint(Seqlist* s1, SeqDatetype x, SeqDatetype x1)//在下标为x处添加数据x1
{
	assert(s1);
	SeqCheckcapacity(s1);//先检查容量,容量不足时需要先扩容
	int j = 0;
	for (j = s1->size; j > x; j--)
	{
		s1->a[j] = s1->a[j - 1];
	}
	s1->a[x] = x1;
	s1->size++;
}
void Seqdelpoint(Seqlist* s1, SeqDatetype x)//在下标为x处删除数据
{
	int i = 0;
	for (i = x; i < s1->size-x+1; i++)
	{
		s1->a[i] = s1->a[i+1];
	}
	s1->size--;
}
void SeqDatefind(Seqlist* s1, SeqDatetype x)//搜索数据输出下标
{
	assert(s1);
	int i = 0;
	int a = 0;
	for (i = 0; i < s1->size; i++)
	{
		if (s1->a[i] == x)
		{
			a++;
			printf("找到了,下标为%d\n", i);
		}
	}
	if (a == 0)
	{
		printf("没有这个数据\n");
	}
}
void Seqsort(Seqlist* s1)//冒泡排序小到大
{
	int i = 0;
	for (i = 0; i < s1->size - 1; i++)
	{
		int j = 0;
		for (j = 0;j < s1->size - 1; j++)
		{
			if (s1->a[j] > s1->a[j + 1])
			{
				int tmp = 0;
				tmp = s1->a[j];
				s1->a[j] = s1->a[j + 1];
				s1->a[j + 1] = tmp;
			}
		}
	}
}

快快动手自己实践一下吧!

下期带大家一起学习链表!有什么建议或者补充欢迎留在评论区!

你可能感兴趣的:(数据结构,数据结构)