初阶数据结构之顺序表详解

目录

一:数据结构概论

1.什么是数据结构?

2.学习数据结构的作用

二:什么是线性表?

三:顺序表的概念

1.什么是顺序表?

2.顺序表的作用

3.顺序表的分类

四:动态顺序表的实现

1.动态顺序表的创建

2.顺序表的初始化

3.顺序表的销毁

3.顺序表打印

4.顺序表扩容

5.顺序表表尾插入

6.顺序表表头插入

7.顺序表表尾删除

8.顺序表表头删除

9.顺序表指定位置插入

10.顺序表指定位置删除数据

11.顺序表查找数据所在下标

12.代码最终展示


在学习顺序表之前,先来了解一下什么是数据结构、什么是线性表。

一:数据结构概论

1.什么是数据结构?

数据结构是计算机用于存储和组织数据的一种方法。它涉及到数据的逻辑结构和物理结构,以及这两者之间的相互关系。

数据的逻辑结构描述了数据元素之间的逻辑关系,而物理结构则是指这些数据元素在计算机中的实际存储方式。

数据结构的研究还涉及到为这种结构定义适当的运算,并设计出相应的算法,确保经过这些运算后得到的新结构仍保持原来的结构类型。

常见的数据结构包括顺序表、链表、栈、队列、树和图等。这些结构各有特点,适用于不同的应用场景。

2.学习数据结构的作用

  • 提高算法的效率
  • 优化程序执行速度
  • 更好地解决实际问题
  • 应用于各个领域

二:什么是线性表?

线性表是一种数据结构,它由具有相同特性的数据元素有限序列组成。

这些数据元素之间的关系是一对一的,这意味着除了第一个和最后一个数据元素外,其他数据元素都只有一个前驱和一个后继。线性表的逻辑结构简单,便于实现和操作,因此在计算机科学中应用广泛。线性表的长度,即所含元素的个数,用n表示,且n可以等于零,表示线性表是一个空表。此外,线性表可以是有序的,也可以是无序的。

知道了线性表后,我们来说本章主要的顺序表。

三:顺序表的概念

1.什么是顺序表?

顺序表的底层是数组,是对数组的封装,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素物理结构上也相邻,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。

2.顺序表的作用

  • 顺序表的主要作用包括存储数据、管理数据和操作数据。
  • 顺序表支持随机访问和顺序访问,逻辑顺序与物理顺序一致,每个元素都有唯一的位置,可以通过索引直接访问。
  • 顺序表的基本操作包括插入、删除、查找、修改、排序、合并和返回元素个数等。

3.顺序表的分类

顺序表分为静态顺序表和动态顺序表。

  • 静态顺序表:其主要特点是其在初始化时分配了固定的存储空间,之后不能动态地增加或减少存储空间。这种表使用定长数组来存储元素,适用于在编程时可以预先估计所需存储数据量的情况。如果预先估计的数据量恰好等于实际所需,那么使用静态顺序表可以非常高效,因为它避免了动态内存分配的开销。然而,如果实际数据量远超预估值,静态顺序表可能会导致存储空间不足;反之,如果预估过大,则会造成存储空间的浪费。
  • 动态顺序表:其特点是使用动态分配的内存数组进行存储,在程序执行过程中根据需要动态地分配或扩充存储空间。动态顺序表的优势在于其灵活性,不需要预先分配固定的存储空间,避免了空间浪费,适用于数据元素数量不确定或需要频繁增删的场景。

在日常的程序编写中,大多使用的是动态顺序表,因此本章主要讲解动态顺序表。

四:动态顺序表的实现

实现动态顺序表的实现之前我们需要创建2个源文件分别进行顺序表功能的实现和功能的测试,和一个头文件用于函数的声明和顺序表的创建。(vs2022)

我们在写的过程中也要遵循先声明再实现最后测试。(声明——实现——测试)

1.动态顺序表的创建

我们把动态顺序表的创建放在头文件" SeqList.h "中,用于声明。

1.什么是声明?

在C语言中,"声明"是指对变量、函数、或其他程序实体进行提前声明或告知编译器它们的类型和名称,以便编译器在后续代码中正确解释和使用这些实体。声明是一种介绍或引导编译器,告诉编译器在程序中会有一些实体,以及这些实体的类型和名称。

例如:int m;  //变量声明,告诉编译器会有一个整数类型的变量 m

int add(int a, int b); // 函数声明,告诉编译器会有一个名为 add 的函数,接收两个整数参数并返回一个整数

2.声明的主要目的

是为了避免编译器在遇到变量或函数时产生错误,因为在使用它们之前,编译器可能并不知道这些实体的具体类型和名称。通过提前声明,编译器能够正确地处理这些实体的引用。

另外,声明也允许将程序的不同部分分开编译,因为在一个文件中声明了一个变量或函数后,在其他文件中可以使用这个声明而无需重新定义。这有助于实现模块化的程序设计。

3.头文件.h的作用

声明函数和变量, 头文件通常包含对函数、变量、结构体等的声明。这样,其他源文件在包含这个头文件时就能知道这些函数、变量的存在和类型,而无需了解其具体实现。

代码示例:

#include<stdio.h>//写上在实现顺序表的各种操作时需要用到的头文件
#include<stdlib.h>
#include<assert.h>//断言

#define CAPACITY 4//可以预定数组大小为4
typedef int SLDataType;//把数据类型重命名为SLDataType
typedef struct SeqList//重命名结构体数据为SL
{
	SLDataType* a;//指向动态开辟的数组
	int size;//数组中的有效变量
	int capacity;//数组的长度

}SL;

创建完成。

2.顺序表的初始化

在实现初始化操作之前,我们先在头文件"SeqList.h"进行函数的声明

void SLInit(SL* pa);//初始化声明

再在源文件"顺序表的增删改查.c"中进行实现。

#include "SeqList.h"
//顺序表初始化
void SLInit(SL* pa)
{
	assert(pa);//断言
	pa->a = (SLDataType*)malloc(sizeof(SLDataType) * CAPACITY);//用malloc动态开辟一块空间用来                                                             
                                                                 储存SLDataType类型的数据
    ps->size = 0;//有效数据为0个
	ps->capacity = CAPACITY;//数组长度为预定长度
}

最后在"测试.c"文件中进行测试。

#include"SeqList.h"
int main()
{
	SL s;
	SLInit(&s);//初始化测试
    return 0;
}

3.顺序表的销毁

因为是动态开辟的,所以如果空间不用我们就需要销毁掉。如果不销毁会存在内存泄漏的风险,所以与之对应的我们写一个销毁的接口函数。

先在头文件声明

void SLDestory(SL* pa);//销毁声明

再在源文件"顺序表的增删改查.c"中进行实现。

//顺序表的销毁
void SLDestory(SL* pa)
{
	assert(pa);//断言
	free(pa->a);//释放掉开辟的动态空间
    pa->a=NULL;
	pa->size =pa->capacity = 0;//有效数据和数组长度设为0
}

最后测试

SLDestory(&s);//销毁测试

3.顺序表打印

头文件声明

void SLPrint(SL* pa);//打印声明

源文件实现

void SLPrint(SL* pa)
{
	assert(pa);//断言
	for (int i = 0; i <pa->size ; i++)//把数组中的有效数字循环打印出来
	{
		printf("%d", pa->a[i]);
	}
	printf("\n");
}

测试

SLPrint(&s);//打印测试

4.顺序表扩容

头文件声明

void SLCheakCapacity(SL* pa);//扩容

源文件实现

//顺序表扩容
void SLCheakCapacity(SL* pa)
{
	assert(pa);//断言
	if(pa->size==pa->capacity)//判断有效数据和数组长度是否相等
	{
		SLDataType* pb = NULL;//创建变量pb
		pb= (SLDataType*)realloc(pa->a, sizeof(SLDataType) * pa->capacity * 2);//pb用来接受扩容后空间的地址,扩容后的空间为原来的2倍
		if (pb == NULL)//如果pb接收到的是NULL,则说明开辟失败
		{
			perror("realloc");
			exit(1);
		}
		free(pa->a);//释放掉原来空间的内存
		pa->a = pb;//把新开辟后空间的地址赋给旧的空间,将旧指针指向新空间
        pa->a=NULL;
		pa->capacity *= 2;//最后将数组长度调整为原来的2倍
	}
}

功能测试

SLCheakCapacity(&s);//扩容测试

5.顺序表表尾插入

头文件声明

//顺序表表尾插入
void SLPushBack(SL* pa, SLDataType x)pa为数组地址,x为插入的元素

源文件实现

//顺序表表尾插入
void SLPushBack(SL* pa, SLDataType x)
{
	assert(pa);//断言
	SLCheakCapacity(pa);//检查内存大小,调整动态内存
	pa->a[pa->size++] = x;//在表尾插入数据后,有效数据+1

}

功能测试

SLPushBack(&s, m);//表尾插入测试

6.顺序表表头插入

头文件声明

void SLPushFront(SL* pa, SLDataType x);//表头插入声明

 源文件实现

//顺序表表头插入
void SLPushFront(SL* pa, SLDataType x)
{
	assert(pa);//断言
	SLCheakCapacity(pa);//检查空间容量,调整动态内存
	for (int i =size-1;i>=0 ; i--)//从前往后依次后侧1=撤一个单位
	{
		pa->a[i+1] = pa->a[i];
	}
	pa->a[0] = x;//最后表头空出来,然后把x给表头

}

功能测试

SLPushFront(&s, m);//表头插入测试

7.顺序表表尾删除

头文件声明

void SLPopBack(SL* pa);//表尾删除声明

源文件实现

即删除最后一个元素,大部分人所想也许是把最后一个元素置为0或者-1,这是可行的,但如果最后一个数就是0呢?

其实我们这里只需要元素数量减去一个就好了,即size--,这样我们就无法访问最后一个元素了,它便是无效的数据。

//顺序表表尾删除
void SLPopBack(SL* pa)
{
	assert(pa->size>0);//有效数据>0就正常运行
	pa->size--;

}

功能测试

SLPopBack(&s);//表尾删除测试

8.顺序表表头删除

头文件声明

void SLPopFront(SL* Pa);//表头删除声明

源文件实现

//顺序表头删除
void SLPopFront(SL* pa)
{
	assert(pa->size > 0);//size>0程序就正常运行
	for (int i = 0; i <pa->size; i++)//从最后一个有效数据开始,,往前面覆盖
	{
		pa->a[i] = pa->a[i + i];
	}
	pa->size--;//最后有效数字-1
}

功能测试

SLPopFront(&s);//表头删除测试

9.顺序表指定位置插入

头文件声明

void SLInsert(SL* pa,int pos,SLDataType x);//指定位置插入声明

源文件实现

//顺序表指定位置插入
void SLInsert(SL* pa, int pos, SLDataType x)//pos指定下标位置,x为插入的数据
{
	assert(pa);//断言
	assert(pos >= 0 && pos <= pa->size);//下标>=0并且下标<=size则程序正常运行
	SLCheakCapacity(pa);//检查并扩容
	int end = pa->size - 1;
	while (end >= pos)//程序的具体实现
	{
		pa->a[end + 1] = pa->a[end];
		end--;
	}
	pa->a[pos] = x;//插入
	pa->size++;//有效数据加1

}

功能测试

SLInsert(&s, n, c);//指定位置插入测试

10.顺序表指定位置删除数据

头文件声明

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

源文件实现

//删除指定位置数据
void SLErase(SL* pa, int pos)
{
	assert(pa);
	assert(pos >= 0 && pos < pa->size);//下标>=0并且下标<=size则程序正常运行
	for(int i=pos;i<size-1;i++)
	{
		pa->a[i]=p->a[i+1];
	}
	pa->size--;//有效数据-1

}

功能测试

SLErase(&s, n);//删除指定位置数据

11.顺序表查找数据所在下标

头文件声明

int  SLFind(SL* pa, SLDataType x);//查找某个数据的位置

源文件实现

//查找某个数据的位置
int SLFind(SL* pa, SLDataType x)
{
	assert(pa);
	for (int i = 0; i < pa->size; i++)
	{
		if (pa->a[i] == x)
			return i;
	}
	return -1;没有找到返回-1
}

函数功能测试

int ret=SLFind(&s, n);//查找数据在哪个位置

12.代码最终展示

(SeqList.h)

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

#define CAPACITY 4
typedef int SLDataType;//把数组类型类型重命名为SLdatatype
typedef struct SeqList
{
	SLDataType* a;//数组指针
	int size;//数组中的有效变量
	int capacity;//数组的大小

}SL;

void SLInit(SL* pa);//初始化声明
void SLDestory(SL* pa);//销毁声明
void SLPrint(SL* pa);//打印声明
void SLCheakCapacity(SL* pa);//扩容
void SLPushBack(SL* pa, SLDataType x);//表尾插入声明
void SLPushFront(SL* pa, SLDataType x);//表头插入声明
void SLPopBack(SL* pa);//表尾删除声明
void SLPopFront(SL* Pa);//表头删除声明
void SLInsert(SL* pa,int pos,SLDataType x);//指定位置插入声明
void SLErase(SL* pa, int pos);//删除指定位置数据 
int  SLFind(SL* pa, SLDataType x);//查找某个数据的位置

(顺序表的增删改查.c)

#include "SeqList.h"
//顺序表初始化
void SLInit(SL* pa)
{
	assert(pa);
	pa->a = (SLDataType*)malloc(sizeof(SLDataType) * CAPACITY);
	pa->size = 0;
	pa->capacity = CAPACITY;
}
//顺序表的销毁
void SLDestory(SL* pa)
{
	assert(pa);
	free(pa->a);
	pa->a= NULL;
	pa->size =pa->capacity = 0;
}
//顺序表打印
void SLPrint(SL* pa)
{
	assert(pa);
	for (int i = 0; i <pa->size ; i++)
	{
		printf("%d", pa->a[i]);
	}
	printf("\n");
}
//顺序表扩容
void SLCheakCapacity(SL* pa)
{
	assert(pa);
	if(pa->size==pa->capacity)
	{
		SLDataType* pb = NULL;
		pb= (SLDataType*)realloc(pa->a, sizeof(SLDataType) * pa->capacity * 2);
		if (pb == NULL)
		{
			perror("realloc");
			exit(1);
		}
		free(pa->a);
		pa->a = pb;
        pa->a=NULL;
		pa->capacity *= 2;
	}
}
//顺序表表尾插入
void SLPushBack(SL* pa, SLDataType x)
{
	assert(pa);
	SLCheakCapacity(pa);
	pa->a[pa->size++] = x;

}
//顺序表表头插入
void SLPushFront(SL* pa, SLDataType x)
{
	assert(pa);
	SLCheakCapacity(pa);
	for (int i = pa->size-1;i>=0 ; i++)
	{
		pa->a[i+1] = pa->a[i];
	}
	pa->a[0] = x;

}
//顺序表表尾删除
void SLPopBack(SL* pa)
{
	assert(pa->size>0);
	pa->size--;

}
//顺序表头删除
void SLPopFront(SL* pa)
{
	assert(pa->size > 0);
	for (int i = 0; i <pa->size-1; i++)
	{
		pa->a[i] = pa->a[i + i];
	}
	pa->size--;
}
//顺序表指定位置插入
void SLInsert(SL* pa, int pos, SLDataType x)
{
	assert(pa);
	assert(pos >= 0 && pos <= pa->size);
	SLCheakCapacity(pa);
	int end = pa->size - 1;
	while (end >= pos)
	{
		pa->a[end + 1] = pa->a[end];
		end--;
	}
	pa->a[pos] = x;
	pa->size++;

}
//删除指定位置数据
void SLErase(SL* pa, int pos)
{
	assert(pa);
	assert(pos >= 0 && pos < pa->size);
	for(int i=pos;i<size-1;i++)
	{
		pa->a[i]=pa->a[i+1]
	}
	pa->size--;

}
//查找某个数据的位置
int SLFind(SL* pa, SLDataType x)
{
	assert(pa);
	for (int i = 0; i < pa->size; i++)
	{
		if (pa->a[i] == x)
			return i;
	}
	return 0;
}

(测试.c)

#include"SeqList.h"
int main()
{
	SL s;
	SLDataType m;
	int n; int c;
	SLInit(&s);//初始化测试
	SLDestory(&s);//销毁测试
	SLPrint(&s);//打印测试
	SLCheakCapacity(&s);//扩容
	SLPushBack(&s, m);//表尾插入测试
	SLPushFront(&s, m);//表头插入测试
	SLPopBack(&s);//表尾删除测试
	SLPopFront(&s);//表头删除测试
	SLInsert(&s, n, c);//指定位置插入测试
	SLErase(&s, n);//删除指定位置数据
	int SLFind(&s, n);//查找数据在哪个位置

	return 0;
}

至此,初阶数据结构之顺序表已讲解完毕,制作不易,如果对你有帮助的话不妨留个赞再走。

完结撒花。

  • 32
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值