数据结构之顺序表

前面我们已经掌握了c语言基础,下面我们继续学习用c语言实现数据结构。那么数据结构的优点又是什么呢?下面我们一起探究。

文章目录

  • 一、什么是数据结构
  • 二、顺序表的概念和结构
  • 三、动态顺序表的实现
    • [3.1 定义顺序表结构](#3.1 定义顺序表结构)
    • [3.2 初始化顺序表](#3.2 初始化顺序表)
    • [3.3 销毁顺序表](#3.3 销毁顺序表)
    • [3.4 打印](#3.4 打印)
    • [3.3 顺序表插入数据](#3.3 顺序表插入数据)
      • [3.3.1 变容](#3.3.1 变容)
      • [3.3.2 尾部插入](#3.3.2 尾部插入)
      • [3.3.3 头部插入](#3.3.3 头部插入)
      • [3.3.4 尾部删除](#3.3.4 尾部删除)
      • [3.3.5 头部删除](#3.3.5 头部删除)
      • [3.3.6 指定位置插入数据](#3.3.6 指定位置插入数据)
      • [3.3.7 删除指定位置数据](#3.3.7 删除指定位置数据)
      • [3.3.8 查找数据](#3.3.8 查找数据)

一、什么是数据结构

当我们想要使用大量同一类型数据时,通过手动定义大量的独立的变量对于程序员来说可读性非常差。我们可以借用数组这样的数据结构将大量的数据组织在一起,结构也可以理解为组织数据的方式。

概念:数据结构是计算机存储、组织数据的⽅式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由那部分构成,以什么⽅式构成,以及数据元素之间呈现的结构。

优点:

  1. 组织和管理数据
  2. 提高算法效率
  3. 节省内存空间
  4. 简化代码
  5. 提高代码的可读性和可维护性
  6. 支持抽象数据类型(ADT)
  7. 适应不同的应用场景

其实最基础的的数据结构是数组

下面我们要实现的顺序表就是基于数组完成的。

二、顺序表的概念和结构

1 线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构 ,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。


注意:顺序表是线性表的一种。

但是顺序表的特征是:物理结构书连续的,逻辑结构一定是连续的。

2 顺序表

2.1顺序表和数组的区别

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

2.2顺序表的分类

2.2.1 静态顺序表

c 复制代码
struct SeqList
{
	int arr[100];//定长数组
	int size;//数组当前有效的数据个数;
};

在静态顺序表中,我们给的是一个定长数组。如果像上面我们申请了100个空间,那么这个空间就固定了。数组大小给小了,空间不够用。数组大小给大了,空间就浪费了。

2.2.2 动态顺序表

c 复制代码
struct SeqList
{	
	int * arr;
	int size; //有效的数据个数
	int capacity;//空间大小
};

在动态顺序表中,我们可以看到在结构体中多了一个结构体成员。capacity其实就是空间容量,通过capacity可以动态增容。

三、动态顺序表的实现

我们要先创建头文件(.h)和源文件(.c)。也可以把测试文件创建出来(test.c)。


用这种方式可以让代码更清晰。头文件就是声明顺序表的方法就像书的目录一样,源文件就是实现顺序表的方法。

3.1 定义顺序表结构

c 复制代码
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* arr;
	int size;
	int capacity;
}SL;

我们要先在头文件中定义顺序表结构,我们用的是动态顺序表。

在顺序表中我们不一定只存放int一种类型的数组,所以我们给这个类型起个别名typedef int SLDataType,在用到其他类型的时候再进行更改。为了下面方便调用结构体,我们也给这个结构体起个别名叫SL。

3.2 初始化顺序表

在头文件中函数声明

c 复制代码
void SLInit(SL* ps);

在源文件中写具体的函数实现

c 复制代码
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

在初始化阶段还没有给结构体中的arr成员分配内存空间,表面该指针具备任何的内存地址。

size和capacity成员在结构体中为整形变量所以在初始化是将其置为0。

3.3 销毁顺序表

在头文件中函数声明

c 复制代码
void SLDestroy(SL* ps);

在源文件中写具体的函数实现

c 复制代码
void SLDestroy(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

在动态顺序表中我们申请的空间是在堆上申请的,所以在创建完顺序表后销毁。

3.4 打印

在头文件中函数声明

c 复制代码
void SLPaint(SL s);

在源文件中写具体的函数实现

c 复制代码
void SLPaint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
	printf("\n");
}

3.3 顺序表插入数据

3.3.1 变容

动态顺序表在插入数据之前,如果size和capacity大小相同我们要先扩容,也就是改变capacity的容量。这就是动态顺序表的精髓所在。

增容通常来说是成倍数增加,一般是原空间的2倍或者3倍 。

c 复制代码
SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, capacity * 2 * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

那么这里还有一个问题,如果capacity本身就为0怎么办?所以这里还需要判断一下。创建一个newCapacity如果capacity为0就给4个空间大小,如果不为0就把空间大小置为原来的2倍。

扩容正确代码如下:

c 复制代码
SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * 2 * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

3.3.2 尾部插入

在头文件中函数声明

c 复制代码
void SLPushBack(SL* ps, SLDataType x);

在源文件中写具体的函数实现

c 复制代码
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);//ps不能为NULL
	SLCheckCapacity(ps);//扩容
	ps->arr[ps->size++] = x;
}

因为是尾插,假设x为5,size的下标为4。

尾插一个5,size向后移一个位置。

3.3.3 头部插入

在头文件中函数声明

c 复制代码
void SLPushFront(SL* ps, SLDataType x)

在源文件中写具体的函数实现

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++;
}

首先扩容

我们用for循环,从size的位置开始,i--,判断条件为i>0。

如下图所示把ii-1位置的值给i位置,这样就把下标为0的位置空出来了。

把x给下标为0的位置。不要忘了让size++。

3.3.4 尾部删除

在头文件中函数声明

c 复制代码
void SLPopBack(SL* ps);

在源文件中写具体的函数实现

c 复制代码
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	--ps->size;
}

尾删不需要扩容,但是size不能为空。直接从后面往前删,使size--就可以了。

3.3.5 头部删除

在头文件中函数声明

c 复制代码
void SLPopFront(SL* ps);

在源文件中写具体的函数实现

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];
	}
}


从下标为0的位置开始,遍历到size-1。如图从1开始让下一个位置的值覆盖这一位置的值。这样也就实现了头部删除。

3.3.6 指定位置插入数据

在头文件中函数声明

c 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)

在源文件中写具体的函数实现

c 复制代码
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++;

}

assert(pos >= 0 && pos <= ps->size) ,要使指定插入的位置>=0并且是指定插入位置<=size位置。

从下标为pos位置开始,整体往后移动。再把要插入的值插入pos位置。

3.3.7 删除指定位置数据

在头文件中函数声明

c 复制代码
void SLErase(SL* ps, int pos);

在源文件中写具体的函数实现

c 复制代码
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	SLCheckCapacity(ps);
	for (int i = pos; i<ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
}

从下标为pos位置开始,遍历到size-1位置。通过ps->arr[i] = ps->arr[i + 1];让pos后面数据整体往前覆盖一个


3.3.8 查找数据

在头文件中函数声明

c 复制代码
int SLFind(SL* ps, SLDataType x);

在源文件中写具体的函数实现

c 复制代码
int SLFind(SL* ps, SLDataType x)
{
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}

	}
	return -1;
}

遍历数组,通过一个if语句判断,如果可以在数组查找到要找的数据返回下标。

到这里顺序表的实现基本就完了,谢谢大家观看。

相关推荐
wjs20244 分钟前
基于Java的Nacos云原生动态服务发现、配置和服务管理平台设计源码
java·nacos·服务发现·配置管理·云原生平台
JXH_1236 分钟前
ms-hot29 解码方法
c++·算法·leetcode
chyun201137 分钟前
代码精简之路-责任链模式
java·设计模式
Light6037 分钟前
剖析前后端 API 接口参数设计:JSON 数据结构化全攻略
数据结构·分布式·微服务·架构·json
IT 青年39 分钟前
数据结构 (14)数组的定义与运算
数据结构
luochen330x41 分钟前
day19 C语言收尾及数据结构
c语言·数据结构·算法·c#
孟林洁1 小时前
Jenkins流水线 & Allure & JUnit5 自动化测试
java·运维·jenkins
埃菲尔铁塔_CV算法1 小时前
条件数:概念、矩阵中的应用及实际工业场景应用
人工智能·python·线性代数·算法·matlab·矩阵
h0l10w1 小时前
A*(A-star)算法
c++·算法·图论