当前位置:   article > 正文

【数据结构】顺序表的实现——超级无敌详细

顺序表的实现

1. 线性表

线性表(liner list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中有广泛引用的一种数据结构。
常见的线性表有:顺序表、链表、栈、队列、字符串……
线性表在逻辑上是线性结构,也就是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常是以数组和链式结构的形式存储的。
顺序表也就相当于是一个数组。

在这里插入图片描述

2. 顺序表

2.1 顺序表的概念

顺序表使用一段连续地址空间的存储单元一次存储数据元素的线性结构,一般情况下采用数组的形式进行存储。在数组上完成增、删、改、查……
顺序表:可动态增长的数组,要求数组是连续存储的

2.2 顺序表的分类

1.静态顺序表

  • 缺点:无法确定数组的大小,给小了不够用,给大了浪费,不够灵活
#define MAX_SIZE 100
typedef int SQDataType;

//顺序表的静态存储
//问题:给少了不够了,给大了浪费,不够灵活
typedef struct SeqList
{
	SQDataType arry[MAX_SIZE];//定长数组
	int size;//有效数据的个数
}SL;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.动态顺序表

  • 使用指针的形式指向动态开辟的数组,可以很灵活控制数组的大小
  • size——有效数据的个数,顺序表中真正有效数据的个数
  • capacity——容量的大小,顺序表可以最大存储多少个数据
//顺序表的动态存储
typedef struct SeqList
{
	SQDataType* a;//指向动态开辟的数组
	int size;//有效数据的个数
	int capacity;//容量空间的大小
}SL;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.3 顺序表的接口函数

我们主要对顺序表实现增删改查等功能

在我们写接口函数之前,先把我们的工程文件写好

在这里插入图片描述

  • SeqList.h:所需要包含的头文件、顺序表的定义、接口函数的声明等
  • SeqList.c:顺序表接口函数的主要实现内容
  • test.c:主函数、测试顺序表接口函数的功能

SeqList.h头文件代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//#define MAX_SIZE 100
typedef int SQDataType;

顺序表的静态存储
问题:给少了不够了,给大了浪费,不够灵活
//typedef struct SeqList
//{
//	SQDataType arry[MAX_SIZE];//定长数组
//	int size;//有效数据的个数
//}SL;

//顺序表的动态存储
typedef struct SeqList
{
	SQDataType* a;//指向动态开辟的数组
	int size;//有效数据的个数
	int capacity;//容量空间的大小
}SL;

//增删改查等接口函数
void SeqListInit(SL* ps);//初始化顺序表
void SeqListPrint(SL* ps);//打印顺序表
void SeqListDestory(SL* ps);//销毁顺序表,释放空间内存

void SeqListPushBack(SL* ps, SQDataType x);//尾插
void SeqListPushFront(SL* ps, SQDataType x);//头插
void SeqListPopBack(SL* ps);//尾删
void SeqListPopFront(SL* ps);//头删

void SeqListInsert(SL* ps, int pos, SQDataType x);//指定位置插入
void SeqListErase(SL* ps, int pos);//指定位置删除
int SeqListFind(SL* ps, SQDataType x);//查找每个数据,若找到,返回数据的下标,若没有找到,返回-1
void SeqListModify(SL* ps, int pos, SQDataType x);//修改指定位置的数据

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

我们主要要学会的是 SeqList.c 接口函数是如何实现的

2.3.1 初始化顺序表

记得断言,防止传进来的指针为空

//初始化顺序表
void SeqListInit(SL* ps)
{
	assert(ps != NULL);//断言,防止传进来空指针

	ps->a = NULL;//初始顺序表为空,也就是没有给顺序表分配空间内存
	ps->size = 0;//初始的有效数据个数为0
	ps->capacity = 0;//初始的容量为0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
2.3.2 销毁顺序表

记得断言,防止传进来的指针为空

//销毁顺序表,释放空间内存
void SeqListDestory(SL* ps)
{
	assert(ps != NULL);//防止传进来空指针

	free(ps->a);//释放动态内存开辟的数组
	ps->a  = NULL;//指针置空
	ps->size = 0;//顺序表的有效数据个数为0
	ps->capacity = 0;//顺序表的容量为0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
2.3.3 打印顺序表

遍历一遍顺序表,打印顺序表

//打印顺序表
void SeqListPrint(SL* ps)
{
	assert(ps != NULL);//断言
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);//遍历顺序表打印
	}
	printf("\n");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
2.3.4 判断是否需要增容

在当我们每想插入一个数据的时候,我们都要对顺序表进行判断,容量 capacity 是否和 size 相等,如果相等的话,那么,顺序表的空间就不够我们进行插入数据了。每次进行头插、尾插等接口函数时,都要进行判断是否需要增容,所以我们可以来写一个判断是否需要增容的函数来封装。
当然我们不能需要插入一个数据就开辟一个数据的大小,这样的话,付出的代价很大,我们可以每次增容 2 倍的capacity大小的容量,或者4倍,2倍是比较好的了。

//判断是否需要增容
void SeqListCheckCapacity(SL* ps)
{
	assert(ps != NULL);//断言
	//满了就要扩容
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//三目操作符
		//如果capacity还是初始值0就给他赋4个大小的空间,如果capacity是已经满了的话,就再给他扩2倍的大小
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));
		//使用临时变量tmp就是防止realloc失败,如果realloc成功的话,再把tmp的地址赋给数组a
		if (tmp == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		else
		{
			ps->a = tmp;//realloc开辟成功,把tmp的地址赋给数组a
			ps->capacity = newcapacity;//把增容后新的容量大小再赋给capacity
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
2.3.5 顺序表尾插

尾插比较简单

//尾插
void SeqListPushBack(SL* ps, SQDataType x)
{
	assert(ps != NULL);//断言
	SeqListCheckCapacity(ps);//检查是否需要增容
	//有空间
	ps->a[ps->size] = x;//尾插数据
	ps->size++;//顺序表的有效数据+1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

测试一下尾插效果
在这里插入图片描述

2.3.6 顺序表尾删

在我们想把顺表表最后一个数据删除时,不能直接赋为0,第一,我们不知道SLDataType到底是什么数据类型,第二,顺序表的最后一个数据有可能就是0,没有意义。

//尾删
void SeqListPopBack(SL* ps)
{
	assert(ps != NULL);//断言
	assert(ps->size != 0);//断言。顺序表不能为空,也就是不能没有数据

	//ps->a[ps->size - 1] = 0;//我们也不知道末尾数据是不是0,这样给他赋为0是不对的
	ps->size--;//这样就行了,有效数据直接-1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

测试一下效果
在这里插入图片描述

2.3.7 顺序表头插

因为顺序表是连续存储的,所以头插时要挪动数据

//头插
void SeqListPushFront(SL* ps, SQDataType x)
{
	assert(ps);//断言
	SeqListCheckCapacity(ps);//判断是否需要增容
	//1.初始条件
	//2.结束条件
	//3.迭代过程
	int end = ps->size - 1;
	//end指向顺序表的最后一个数据,因为我们要从最后一个数据慢慢往前移一个
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];//从后往前移数据
		--end;
	}
	ps->a[0] = x;//头插
	ps->size++;//顺序表的有效数据+1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

测试一下头插

在这里插入图片描述

2.3.8 顺序表头删

因为顺序表是连续的,所以头删时需要挪动数据

//头删
void SeqListPopFront(SL* ps)
{
	assert(ps != NULL);//断言
	assert(ps->size > 0);//断言,顺序表不能为空

	int start = 0;//start指向顺序表的首个元素
	//从后往前依次把后面的一个数据赋值给前面的一个数据
	while (start < ps->size)
	{
		ps->a[start] = ps->a[start + 1];
		++start;
	}
	ps->size--;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

测试一下头删

在这里插入图片描述

2.3.9 在顺序表中插入数据在指定位置
//指定位置插入
void SeqListInsert(SL* ps, int pos, SQDataType x)
{
	assert(ps != NULL);//断言
	assert(pos > 0 && pos <= ps->size);//断言,pos必须在有效范围内
	SeqListCheckCapacity(ps);//判断是否需要增容
	int end = ps->size - 1;//顺序表的最后一个数据
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];//在pos位置即pos之后的数据,都要往后移一个
		--end;
	}
	ps->a[pos] = x;//在指定位置插入数据
	ps->size++;//顺序表有效数据+1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里插入图片描述

2.3.10 在顺序表中删除指定位置的数据
//指定位置删除
void SeqListErase(SL* ps, int pos)
{
	assert(ps != NULL);//断言
	assert(ps->size > 0);//顺序表不能为空
	assert(pos >= 0 && pos < ps->size);//pos下标要合法性
	int start = pos + 1;//从pos的下一个数据开始,把这个数据都赋给前一个数据
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		++start;
	}
	ps->size--;//顺序表的有效数据-1
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里插入图片描述

2.3.11 在顺序表中查找指定值
//查找某个数据,若找到,返回数据的下标,若没有找到,返回-1
int SeqListFind(SL* ps, SQDataType x)
{
	assert(ps != NULL);//断言,顺序表必须存在

	//遍历一遍顺序表查找指定值
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;//如果找到,则返回对应值的下标
		}
	}
	return -1;//如果顺序表没有该值,则发返回-1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

测试一下该代码:

在这里插入图片描述

2.3.12 在顺表表中修改指定下标位置的数据
//修改指定位置的数据
void SeqListModify(SL* ps, int pos, SQDataType x)
{
	assert(ps != NULL);//断言
	assert(ps->size > 0);//顺序表不能为空
	assert(pos >= 0 && pos < ps->size);//pos的合法性
	ps->a[pos] = x;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

测试一下代码:

在这里插入图片描述

2.4 完整代码展示

  • SeqList.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//#define MAX_SIZE 100
typedef int SQDataType;

顺序表的静态存储
问题:给少了不够了,给大了浪费,不够灵活
//typedef struct SeqList
//{
//	SQDataType arry[MAX_SIZE];//定长数组
//	int size;//有效数据的个数
//}SL;

//顺序表的动态存储
typedef struct SeqList
{
	SQDataType* a;//指向动态开辟的数组
	int size;//有效数据的个数
	int capacity;//容量空间的大小
}SL;

//增删改查等接口函数
void SeqListInit(SL* ps);//初始化顺序表
void SeqListPrint(SL* ps);//打印顺序表
void SeqListDestory(SL* ps);//销毁顺序表,释放空间内存

void SeqListPushBack(SL* ps, SQDataType x);//尾插
void SeqListPushFront(SL* ps, SQDataType x);//头插
void SeqListPopBack(SL* ps);//尾删
void SeqListPopFront(SL* ps);//头删

void SeqListInsert(SL* ps, int pos, SQDataType x);//指定位置插入
void SeqListErase(SL* ps, int pos);//指定位置删除
int SeqListFind(SL* ps, SQDataType x);//查找某个数据,若找到,返回数据的下标,若没有找到,返回-1
void SeqListModify(SL* ps, int pos, SQDataType x);//修改指定位置的数据

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • SeqList.c
#include "SeqList.h"

//初始化顺序表
void SeqListInit(SL* ps)
{
	assert(ps != NULL);//断言,防止传进来空指针

	ps->a = NULL;//初始顺序表为空,也就是没有给顺序表分配空间内存
	ps->size = 0;//初始的有效数据个数为0
	ps->capacity = 0;//初始的容量为0
}

//销毁顺序表,释放空间内存
void SeqListDestory(SL* ps)
{
	assert(ps != NULL);//防止传进来空指针

	free(ps->a);//释放动态内存开辟的数组
	ps->a  = NULL;//指针置空
	ps->size = 0;//顺序表的有效数据个数为0
	ps->capacity = 0;//顺序表的容量为0
}


//打印顺序表
void SeqListPrint(SL* ps)
{
	assert(ps != NULL);//断言
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);//遍历顺序表打印
	}
	printf("\n");
}

//判断是否需要增容
void SeqListCheckCapacity(SL* ps)
{
	assert(ps != NULL);//断言
	//满了就要扩容
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//三目操作符
		//如果capacity还是初始值0就给他赋4个大小的空间,如果capacity是已经满了的话,就再给他扩2倍的大小
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));
		//使用临时变量tmp就是防止realloc失败,如果realloc成功的话,再把tmp的地址赋给数组a
		if (tmp == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		else
		{
			ps->a = tmp;//realloc开辟成功,把tmp的地址赋给数组a
			ps->capacity = newcapacity;//把增容后新的容量大小再赋给capacity
		}
	}
}

//尾插
void SeqListPushBack(SL* ps, SQDataType x)
{
	assert(ps != NULL);//断言
	SeqListCheckCapacity(ps);//检查是否需要增容
	//有空间
	ps->a[ps->size] = x;//尾插数据
	ps->size++;//顺序表的有效数据+1
}

//尾删
void SeqListPopBack(SL* ps)
{
	assert(ps != NULL);//断言
	assert(ps->size > 0);//断言。顺序表不能为空,也就是不能没有数据

	//ps->a[ps->size - 1] = 0;//我们也不知道末尾数据是不是0,这样给他赋为0是不对的
	ps->size--;//这样就行了,有效数据直接-1
}

//头插
void SeqListPushFront(SL* ps, SQDataType x)
{
	assert(ps);//断言
	SeqListCheckCapacity(ps);//判断是否需要增容
	//1.初始条件
	//2.结束条件
	//3.迭代过程
	int end = ps->size - 1;
	//end指向顺序表的最后一个数据,因为我们要从最后一个数据慢慢往前移一个
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];//从后往前移数据
		--end;
	}
	ps->a[0] = x;//头插
	ps->size++;//顺序表的有效数据+1
}

//头删
void SeqListPopFront(SL* ps)
{
	assert(ps != NULL);//断言
	assert(ps->size > 0);//断言,顺序表不能为空

	int start = 0;//start指向顺序表的首个元素
	//从后往前依次把后面的一个数据赋值给前面的一个数据
	while (start < ps->size)
	{
		ps->a[start] = ps->a[start + 1];
		++start;
	}
	ps->size--;
}


//指定位置插入
void SeqListInsert(SL* ps, int pos, SQDataType x)
{
	assert(ps != NULL);//断言
	assert(pos > 0 && pos <= ps->size);//断言,pos必须在有效范围内
	SeqListCheckCapacity(ps);//判断是否需要增容
	int end = ps->size - 1;//顺序表的最后一个数据
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];//在pos位置即pos之后的数据,都要往后移一个
		--end;
	}
	ps->a[pos] = x;//在指定位置插入数据
	ps->size++;//顺序表有效数据+1
}

//指定位置删除
void SeqListErase(SL* ps, int pos)
{
	assert(ps != NULL);//断言
	assert(ps->size > 0);//顺序表不能为空
	assert(pos >= 0 && pos < ps->size);//pos下标要合法性
	int start = pos + 1;//从pos的下一个数据开始,把这个数据都赋给前一个数据
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		++start;
	}
	ps->size--;//顺序表的有效数据-1
}

//查找某个数据,若找到,返回数据的下标,若没有找到,返回-1
int SeqListFind(SL* ps, SQDataType x)
{
	assert(ps != NULL);//断言,顺序表必须存在

	//遍历一遍顺序表查找指定值
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;//如果找到,则返回对应值的下标
		}
	}
	return -1;//如果顺序表没有该值,则发返回-1
}

//修改指定位置的数据
void SeqListModify(SL* ps, int pos, SQDataType x)
{
	assert(ps != NULL);//断言
	assert(ps->size > 0);//顺序表不能为空
	assert(pos >= 0 && pos < ps->size);//pos的合法性
	ps->a[pos] = x;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • test.c
#include "SeqList.h"

void test01()
{ 
	SL s;//创建一个顺序表对象s
	SeqListInit(&s);//初始化顺序表

	//尾插几个数据
	SeqListPushBack(&s, 1);
	SeqListPushBack(&s, 2);
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);
	SeqListPrint(&s);//打印顺序表

	//把下标3的元素改为10
	SeqListModify(&s, 3, 10);
	SeqListPrint(&s);

	查找指定元素
	//int ret = SeqListFind(&s, 3);
	//if (ret == -1)
	//{
	//	printf("改顺序表中没有该元素!\n");
	//}
	//else
	//{
	//	printf("该顺表表有该元素,下标为:%d\n", ret);
	//}


	删除pos=2的位置
	//SeqListErase(&s, 2);

	头插
	//SeqListPushFront(&s, 4);
	//SeqListPushFront(&s, 5);
	//SeqListPrint(&s);//打印顺序表

	头删
	//SeqListPopFront(&s);
	//SeqListPrint(&s);//打印顺序表
	SeqListDestory(&s);//销毁顺序表
}

int main()
{
	test01();

	system("pause");
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号