数据结构之顺序表(代码片段)

桐原亮司. 桐原亮司.     2022-12-25     238

关键词:

顺序表概念

线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为顺序表(Sequential List)。其特点是,逻辑上相邻的数据元素,其物理次序也是相邻的
线性表的顺序存储结构是一种随机存取的存储结构,一般情况下采用数组存储。
解释下黑体字:假设现在已经实现了每个元素类型为int型的顺序表,并且插入了1、2、3、4、5五个元素。

64位平台下一个整型(int)占4个字节,元素1的后面是2,2的后面是3,我们可以清晰的看到元素1的地址后面就是2的地址,2的地址后面就是3的地址,每两个地址之间就相差它们自身占用空间字节数。
就像这样:

顺序表实现

静态顺序表的结构定义

#define N 500
typedef int SQDataType;

typedef struct SeqList

	SQDataType a[N];
	int size;
SLT;

静态顺序表在实际中是非常不适用的,空间给小了不够用,给多了可能会浪费,所以最好定义成动态的。

动态顺序表的结构定义

typedef int SQDatatype;

typedef struct SeqList

	SQDatatype* a;		//指向动态开辟数组的指针
	int size;			//有效数据的个数
	int capacity;		//容量空间大小
SLT;

typedef的作用是可以为某一类型自定义名称。编译器会将带有typedef关键字的SQDatatype解释成一个类型的标识符,即int型。这样做的好处是当顺序表中的存储元素是char类型时,只需要将int改为char即可,提高了程序的灵活性。
接下来定义一个结构体,结构体当中有三个成员。
第一个结构体成员为一个指向数组的指针,因为顺序表一般情况下采用数组存储。
第二个结构体成员变量size为数组中有效数据的个数。
假设数组动态开辟了4个整型空间,先插入1,2两个元素,此时有效数据2个,size = 2。

再插入3,4两个元素。

这时会发现我们再想插入数据时数组就越界了,空间不够了,所以需要第三个变量capacity,用来表示容量空间的大小。当有效数据个数size < 容量capacity时,随便插入数据,因为数组还有空间;当size == capacity时说明空间满了,再想插入数据需要扩容。

顺序表初始化

void SeqListInit(SLT* psl)

	assert(psl);	//psl不可能为NULL,对其进行断言
	psl->a = NULL;
	psl->size = psl->capacity = 0;

将指针a赋值为NULL,有效数据个数size和容量capacity都赋值为0即可。
为什么函数参数是结构体指针呢?
先看一个简单的例子:

void Swap1(int x, int y)

	int temp = x;
	x = y;
	y = temp;


int main(void)

	int a = 10;      //变量a
	int b = 20;      //变量b
	Swap1(a, b);      //Swap()函数将两个数字交换
	printf("a = %d, b = %d\\n", a, b);
	return 0;


可以看到打印在屏幕上的结果,两个变量的值并没有交换。

调试发现形参x和y确实交换了,但是a和b的值没有变化,为什么呢?

void Swap2(int* x, int* y)

	int temp = *x;
	*x = *y;
	*y = temp;


int main(void)

	int a = 10;
	int b = 20;
	Swap2(&a, &b);
	printf("a = %d, b = %d\\n", a, b);
	return 0;


这样a和b的值才会交换。
因为形参只是实参的一份临时拷贝,可以比较一下两次变量的地址。
这是Swap1()函数实参和形参的地址:

可以清晰的看到实参a,b与形参x,y使用的不是同一空间。
这是Swap2()函数实参和形参的地址:

发现实参a,b和形参x,y所用的是同一块空间,所以解引用取到x和y的值并且交换,自然也就交换了a和b的值。
上面改变了两个整型的值,需要传地址参数,结构体也一样,想改变结构体中的内容需要传递结构体的地址,用一个结构体指针接收。
断言<assert.h>
ANSI C实现了一个assert宏,当它被执行时,这个宏对表达式参数进行测试。如果它的值为假(零),它就向标准错误打印一条诊断信息并终止程序。
这条信息的格式是由编译器定义的,它将包含这个表达式和源文件的名字以及断言所在的行号。如果表达式为真(非零),将不会打印任何东西,程序继续执行。
初始化函数参数为结构体指针,指向结构体地址,这个地址不可能为空,所以可以对函数参数psl进行断言,如果psl为NULL,就会出现下面的结果:

顺序表打印

void SeqListPrint(SLT* psl)

	assert(psl);
	for (int i = 0; i < psl->size; ++i)
	
		printf("%d ", psl->a[i]);
	
	printf("\\n");

用一个for循环输出数组中每一个有效数据。

检查空间进行扩容

void CheckCapacity(SLT* psl)

	assert(psl);
	if (psl->size == psl->capacity)
	
		size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SQDatatype* temp = (SQDatatype*)realloc(psl->a, sizeof(SQDatatype) * newCapacity);
		if (temp == NULL)		//如果temp为NULL说明开辟空间失败
		
			printf("realloc fail");
			exit(-1);		//退出程序
		
		psl->a = temp;
		psl->capacity = newCapacity;
	

有效数据个数size和容量capacity相等需要扩容,如果容量capacity等于0,为其开辟4个整型空间,否则容量变为原来的二倍。
将新空间赋值给a,新容量赋值给capacity。

顺序表尾插

void SeqListPushBack(SLT* psl, SQDatatype x)

	assert(psl);
	CheckCapacity(psl);		//检查是否需要扩容
	psl->a[psl->size] = x;
	psl->size++;	//有效数据个数加一

要在顺序表尾部进行插入操作,插入元素下标的值和顺序表有效数据个数的值是相等的。

此时有效数据size为2,在2后面尾插个3,那么3的下标就是2,就是size的值。所以psl->a[psl->size] = x,插入完成后有效数据个数变为3个,让size的值加1。

顺序表尾删

void SeqListPopBack(SLT* psl)

	assert(psl);
	assert(psl->size > 0);
	psl->size--;

顺序表有元素的时候才可以进行删除操作,所以对顺序表的有效数据个数进行断言。如果顺序表中有元素,让有效数据个数减一即可。

顺序表头插

void SeqListPushFront(SLT* psl, SQDatatype x)

	assert(psl);
	CheckCapacity(psl);		//检查是否需要扩容
	for (int i = psl->size; i > 0; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[0] = x;		//将元素插入数组第一个位置
	psl->size++;		//有效数据个数加一

假设顺序表有1、2、3、4四个元素,此时头插一个x的值为6。

此时size == capacity,需要扩容。
扩容后:

将数组中的每个元素从最后一个开始赋值个后面一个。
即a[4] = a[3],a[3] = a[2] … … …… 直至跳出循环。
循环结束后:

将数组下标为0的位置赋值为6,此时size = 5,capacity = 8。

顺序表头删

void SeqListPopFront(SLT* psl)

	assert(psl);		//psl不可能为NULL,对其进行断言
	assert(psl->size > 0);		//存在有效数据才进行删除
	for (int i = 0; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;		//有效数据个数减一

从数组下标为1的元素开始,每个元素都赋值给前一个元素位置,让有效数据个数减一。

顺序表查找

查找顺序表中是否存在某一元素,若存在返回该元素下标,否则返回-1。

int SeqListFind(SLT* psl, SQDatatype x)

	assert(psl);		//psl不可能为NULL,对其进行断言
	for (int i = 0; i < psl->size; ++i)
	
		if (x == psl->a[i])
			return i;
	
	return -1;

在任意位置插入

void SeqListInsert(SLT* psl, size_t pos, SQDatatype x)

	assert(psl);
	assert(pos >= 0 && pos <= psl->size); //对插入位置pos取值进行断言
	CheckCapacity(psl);
	for (int i = psl->size; i > pos; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[pos] = x;		//将x赋值给下标为pos位置
	psl->size++;			//有效数据个数加一

假设要在下标为pos的位置插入一个元素,pos的值一定在0和有效数据个数之间,即pos的取值范围[0,size],pos值为0即头插,pos值为size即尾插。
将pos及其后面的元素向后挪动一位,将要插入的数据x赋值给下标为pos的位置。最后让有效数据的个数加一。

在任意位置删除

void SeqListErase(SLT* psl, size_t pos)

	assert(psl);		//对psl进行断言
	assert(pos >= 0 && pos < psl->size);	//对pos取值进行断言
	for (int i = pos; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;	//有效数据个数减一

想在任意位置删除时,pos的取值在[0,size)之间,因为数组最后一个元素的下标为size-1,假如删除size位置,数组越界了,所以这里是开区间。
让pos后面的位置都向前挪动一位,最后让有效数据的个数减一。

顺序表销毁

void SeqListDestory(SLT* psl)

	assert(psl);
	if (psl->a)
	
		free(psl->a);
		psl->a = NULL;
	
	psl->size = psl->capacity = 0;

将数组指针置空,并且将有效数据个数size和容量capacity也置空。

头文件

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

typedef int SQDatatype;

typedef struct SeqList

	SQDatatype* a;	
	int size;			//有效数据的个数
	int capacity;		//容量
SLT;

//初始化
void SeqListInit(SLT* psl);
//销毁
void SeqListDestory(SLT* psl);
//打印
void SeqListPrint(SLT* psl);
//扩容
void CheckCapacity(SLT* psl);
//尾插
void SeqListPushBack(SLT* psl, SQDatatype x);
//尾删
void SeqListPopBack(SLT* psl);
//头插
void SeqListPushFront(SLT* psl, SQDatatype x);
//头删
void SeqListPopFront(SLT* psl);
//查找
int SeqListFind(SLT* psl, SQDatatype x);
//任意位置插入
void SeqListInsert(SLT* psl, size_t pos, SQDatatype x);
//任意位置删除
void SeqListErase(SLT* psl, size_t pos);

源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"

void SeqListInit(SLT* psl)		//初始化

	assert(psl);
	psl->a = NULL;
	psl->size = psl->capacity = 0;


void SeqListDestory(SLT* psl)	//销毁

	assert(psl);
	if (psl->a)
	
		free(psl->a);
		psl->a = NULL;
	
	psl->size = psl->capacity = 0;


void SeqListPrint(SLT* psl)		//打印

	assert(psl);
	for (int i = 0; i < psl->size; ++i)
	
		printf("%d ", psl->a[i]);
	
	printf("\\n");


void CheckCapacity(SLT* psl)		//检查空间进行扩容

	assert(psl);
	if (psl->size == psl->capacity)
	
		size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SQDatatype* temp = (SQDatatype*)realloc(psl->a, sizeof(SQDatatype) * newCapacity);
		if (temp == NULL)
		
			printf("realloc fail");
			exit(-1);
		
		psl->a = temp;
		psl->capacity = newCapacity;
	


void SeqListPushBack(SLT* psl, SQDatatype x)	//尾插

	assert(psl);
	CheckCapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;


void SeqListPopBack(SLT* psl)		//尾删

	assert(psl);
	assert(psl->size > 0);
	psl->size--;


void SeqListPushFront(SLT* psl, SQDatatype x)	//头插

	assert(psl);
	CheckCapacity(psl);
	for (int i = psl->size; i > 0; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[0] = x;
	psl->size++;


void SeqListPopFront(SLT* psl)		//头删

	assert(psl);
	assert(psl->size > 0);
	for (int i = 0; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;


int SeqListFind(SLT* psl, SQDatatype x)		//查找

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


void SeqListInsert(SLT* psl, size_t pos, SQDatatype x)	//pos位置插入

	assert(psl);
	assert(pos >= 0 && pos <= psl->size);
	CheckCapacity(psl);
	for (int i = psl->size; i > pos; --i)
	
		psl->a[i] = psl->a[i - 1];
	
	psl->a[pos] = x;
	psl->size++;


void SeqListErase(SLT* psl, size_t pos)		//pos位置删除

	assert(psl);
	assert(pos >= 0 && pos < psl->size);
	for (int i = pos; i < psl->size - 1; ++i)
	
		psl->a[i] = psl->a[i + 1];
	
	psl->size--;

数据结构之顺序表(代码片段)

目录顺序表概念顺序表实现静态顺序表的结构定义动态顺序表的结构定义顺序表初始化顺序表打印检查空间进行扩容顺序表尾插顺序表尾删顺序表头插顺序表头删顺序表查找在任意位置插入在任意位置删除顺序表销毁头文件源文... 查看详情

数据结构之顺序表(代码片段)

...数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…线性表在逻辑上是 查看详情

数据结构之顺序线性表(代码片段)

数据结构之顺序表理解以数组的形式构造顺序线性表相邻元素之间有物理联系(地址有关系)与链式表相比,删除、插入比较麻烦与链式表相比,查找位置的元素的话很快,查找元素的位置一样都得循环可以从末尾向前循环,也... 查看详情

数据结构之动态顺序表(含游戏菜单)(代码片段)

   上一篇文章我们谈了数据结构的静态顺序表,以及实现了静态顺序表,具体可以看我的上一篇文章->>>数据结构之静态顺序表.  我们可以知道,静态顺序表的缺点是: 因为使用的是定长数组,所以空间给少了不够... 查看详情

线性表之顺序存储结构(顺序表)(代码片段)

目录1.线性表2.顺序存储结构(顺序表)2.1顺序表一般可以分为: 1.静态顺序表:使用定长数组存储元素 1.1静态顺序表:顺序表的大小是确定的,容量是固定的.    结论:静态顺序表不是很常量,了解就行.2.动态顺序表:... 查看详情

数据结构线性表之顺序表arraylist(代码片段)

...ArrayList。为什么我们称其为顺序表呢?原因顾名思义是该数据结构在逻辑上和物理结构上的存储顺序均是连续的。下面,我们就以一张图来说明什么是顺序表。 这个图代表逻辑上的9个元素,每一个位置均存储一个数据,数... 查看详情

数据结构之单链表(代码片段)

前言:  之前的文章,我们了解了顺序表,包括静态顺序表和动态顺序表。具体可以看我的上两篇文章1.静态顺序表知识及代码实现:静态顺序表2.动态顺序表知识及代码实现:动态顺序表  既然顺序表已经... 查看详情

数据结构之顺序表的增删查改等操作详解(代码片段)

顺序表的增删查改文章目录顺序表的增删查改顺序表静态顺序表:使用定长数组存储元素动态顺序表:使用动态开辟的数组存储顺序表的初始化顺序表的销毁打印顺序表数据顺序表尾插数据顺序表头插数据顺序表头删数... 查看详情

数据结构之顺序表+常见面试题(代码片段)

数据结构分为两种1、物理结构(内存中如何存储的)2、逻辑结构(是我们想象出来的)线性表(逻辑上连续)包括顺序表、链表、栈、队列。顺序表在物理上和逻辑上都是连续的(其实就是数组)... 查看详情

数据结构之静态顺序表(含游戏菜单)(代码片段)

目录一.什么是顺序表?二.静态顺序表和动态顺序表的不同点三.什么是静态顺序表四:函数接口实现1.初始化结构体2.打印数据3.头插数据4.尾插数据5.头删数据6.尾删数据附:原码链接一.什么是顺序表? 顺序表是... 查看详情

数据结构之线性表(顺序表,单链表)——图书管理系统(代码片段)

顺序表:代码如下:1#include<iostream>2#include<fstream>3#include<string>4#include<iomanip>5usingnamespacestd;6#defineOK17#defineERROR08#defineOVERFLOW-29typedefintStatus;10typedefintEle 查看详情

数据结构之顺序表的实现(详解)(代码片段)

...数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…线性表在**逻辑上是线性结构,也就说是连续的一条直线。**但是在物理结构上并不一定是连续... 查看详情

数据结构学习总结线性表之顺序表(代码片段)

    通过前面的学习知道,具有“一对一”逻辑关系的数据,最佳的存储方式是使用线性表。那么,什么是线性表呢?线性表,全名为线性存储结构。使用线性表存储数据的方式可以这样理解,即“把所有数据用一... 查看详情

数据结构与算法之线性表(超详细顺序表链表)(代码片段)

前言通过前面数据结构与算法基础知识我么知道了数据结构的一些概念和重要性,那么我们今天总结下线性表相关的内容。当然,我用自己的理解解分享给大家。其实说实话,可能很多人依然分不清线性表,顺序表,和链表之间... 查看详情

数据结构之线性表之顺序存储java实现(代码片段)

文章目录线性表的定义线性表的基本运算线性表的存储之顺序存储定义线性表添加元素查找元素删除元素打印线性表实现的完整代码测试一下线性表的定义线性表的逻辑特征:①有且仅有一个称为开始元素的a1,她没有前... 查看详情

c++学习(二十九)(c语言部分)之顺序表(代码片段)

一、数据结构组织存放数据的方式精心选择的数据结构可以提升效率数据结构1、逻辑结构一对多关系父与子一对一关系排队中多对多关系两地的路线2、存储结构 数据存放的位置关系 顺序存储数据一个挨着一个的存储(数组)... 查看详情

数据结构之线性顺序表实现(代码片段)

线性表是最常用且最简单的一种数据结构。简言之,一个线性表是n个数据元素的有限序列。线性结构的特点是:在数据元素的非空有限集中,(1)存在唯一的一个被称作“第一个”的数据元素;(2)存在唯一的一个被称作“最... 查看详情

数据结构与算法之顺序表(代码片段)

⭐️前面的话⭐️本篇文章带大家认识数据结构与算法基础,顺序表(动态),所谓的顺序表本质就是数组,它的特点是物理结构与逻辑结构都是连续的,最大的优点就是能够随机访问,最大的缺点是... 查看详情