数据结构之顺序表的实现

本篇目标:

学会顺序表的增删查改的代码实现

一.顺序表

1.动静态顺序表

静态顺序表其实就是用有限大小的空间来存储数据,但是这个空间大了容易浪费空间,小了容易造成空间不够的情况。

动态顺序表利用malloc来从堆上申请空间,如果空间小了可以利用顺序表的接口自动扩容,并且空间浪费比较少,例如:

cpp 复制代码
typedef int SqDataType; 
typedef struct SequenceList
{ 
    SqDataType* arr;                
    int size;                       
    int capacity;                   
}SqList; 

有一个顺序表L=(10,20,30,40,50),当前容量空间为5,空间满了再扩容,则其示意图为:

今天主要是以动态顺序表为主。

2.代码实现

2.1.主体框架

在SqList.h头文件中,我们主要实现对顺序表的接口的声明,如代码所示:

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>

typedef int SqDataType; // 数据元素类型

typedef struct SqList
{
	SqDataType* arr;
	int size;
	int capacity;
}SqList;
// 初始化顺序表
void SqListInit(SqList* ps);

// 销毁顺序表
void SqListDestroy(SqList* ps);

// 返回顺序表中第i个下标位置元素的值
SqDataType GetElem(SqList* ps, int i);

// 返回第⼀个等于x的数据元素的下标,若不存在返回 - 1
int LocateElem(SqList * ps, SqDataType x);

// 在顺序表的第i个位置插⼊元素x
void SqListInsert(SqList* ps, int i, SqDataType x);

// 删除顺序表中第i个元素,并返回删除的值
SqDataType SqListDelete(SqList* ps, int i);

// 打印顺序表中的元素
void SqListPrint(SqList* ps);

// 检测顺序表是否为空,空返回true,否则返回false
bool EmptySqList(SqList* ps);

// 获取顺序表中有效元素个数
int SqListSize(SqList* ps);

// 头插尾插
void SqListPushBack(SqList* ps, SqDataType x);
void SqListPushFront(SqList* ps, SqDataType x);

// 头删尾删
void SqListPopBack(SqList* ps);
void SqListPopFront(SqList* ps);

有人可能会问:为什么要用typedef int****和struct SqList 呢?其实这是为了方便代码维护。如果将来需要存储浮点数,只需修改一处类型定义即可,而tyoedef struct SqList也是为了方便。

2.2.初始化与打印

顺序表的结构体变量创建好后,系统会以随机值对其进行填充,所以在使用前须先进行初始化,步 骤如下:

<1>.先使用malloc申请⼀个默认大小动态数组空间,例如默认大小为4,如果不够,后续可以扩容。

<2>.申请成功后,将有效元素个数初始化为0 ,因为初始化阶段,顺序表中还未存放任何有效元素 <3>.将capacity设置为所申请空间的实际大小

如代码所示:

cpp 复制代码
void SqListInit(SqList* ps)
{
    assert(ps);			//防止对方传个NULL
	ps->arr = (SqDataType*)malloc(sizeof(SqDataType) * 4);
	if (ps->arr == NULL)
	{
		printf("申请空间失败\n");
		exit(1);	//相当于return;
	}
	ps->size = 0;
	ps->capacity = 4;
}

然后为了观察顺序表中的元素是否如预期那般存入,还需要一个打印顺序表中元素的接口,如代码:

cpp 复制代码
void SqListPrint(SqList* ps)
{
	printf("顺序表中的元素:");
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
}

2.3.插入与删除

<1>.插入

插入元素之前,我们首先要考虑一个问题,如果ps->size=ps->capacity此时再插入数据不就超过顺

序表的容量了吗?

所以当顺序表中元素存满时,就需要进⾏扩容,否则无法继续插⼊元素。扩容是⼀个前瞻思维,不

仅仅考虑插⼊当前数据没有空间了,还要考虑后⾯数据插⼊也要空间,所以索性⼀次多扩展⼀些,

⼀ 般的做法是2倍左右扩容,当然有些书上是按固定⼤小扩容,比如每次扩容4个空间。

这里我们扩容使用C的库函数 void* realloc (void* ptr, size_t size) 实现,ptr是旧空间的指针,

size是需要的新空间的字节数,realloc函数有以下⼏种情况:

1.原地扩容:

2.异地扩容:

插入的逻辑:

如上图所示,我们有一个顺序表 1, 2, 3, 4, 6。现在需要在数字2之前插入元素6,这里需要考虑的是:应该从后往前移动数据,还是从前往后移动数据?

正确的做法是从后往前移动数据。如果采用从前往后的移动方式,会导致原有数据被覆盖。具体原因可以通过下方图示来理解:

代码如图所示:

cpp 复制代码
// 在顺序表的第i个位置插⼊元素x
void SqListInsert(SqList* ps, int i, SqDataType x)
{
	assert(ps);
	assert(i >= 0 && i <= ps->size);	//插入的位置应当满足顺序表的数据个数
	if (ps->size == ps->capacity)
	{
		SqDataType* tmp = (SqDataType*)realloc(ps->arr, sizeof(SqDataType)*ps->capacity * 2);
		if (tmp == NULL)
		{
			printf("扩容失败\n");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = 2 * ps->capacity;
	}

	for (int j = ps->size; j > i; j--)
	{
		ps->arr[j] = ps->arr[j-1];
	}
	ps->arr[i] = x;
	ps->size++;
}

插入是我们应该用assert来判断插入的位置是否符合要求。

有了在i位置的插入之后,我们就可以复用这个代码,完成头插和尾插

cpp 复制代码
 // 尾插
 void SqListPushBack(SqList* ps, SqDataType x)
 {
	 SqListInsert(ps, ps->size, x);
 }

//头插
 void SqListPushFront(SqList* ps, SqDataType x)
 {
	 SqListInsert(ps, 0, x);
}
<2>.删除

如上图,我们有个L=(1,5,8,5,3)的顺序表,我要删除下表为1的元素,这里需要考虑的是:应该从

后往前移动数据,还是从前往后移动数据?

从前向后遍历可以避免数据覆盖的问题;如果从后向前遍历,则可能导致数据被覆盖。具体原因可

以通过下图来理解:

代码如图所示:

cpp 复制代码
SqDataType SqListDelete(SqList* ps, int i)
{
	 assert(ps);
	 assert(i >= 0 && i < ps->size);

	 int tmp = ps->arr[i];		//先保存要删除的值
	 for(int j=i;j<ps->size-1;j++)
	 {
		 ps->arr[j] = ps->arr[j + 1];
	 }
	ps->size--;
	return tmp;
}

有了在下标为i的删除后,就可以利用这个代码完成对头删/尾删的操作了,如代码所示:

cpp 复制代码
// 尾删
void SqListPopBack(SqList* ps)
{
	assert(ps);
	SqListDelete(ps, ps->size-1);
}

//头删
void SqListPopFront(SqList* ps)
{
	SqListDelete(ps, 0);
}

2.4.查找

顺序表有两种查找操作,位序查找和按值查找。

位序查找函数原型: SqDataType GetElem(SqList* ps, int i) 第i个位置元素随机访问,在i满足 0

<= i < s.size 时(不满足则报错),直接返回顺序表第i个元素即可,如代码所示:

cpp 复制代码
//返回顺序表中第i个下标位置元素的值
SqDataType GetElem(SqList* ps, int i)
{
	assert(ps);
	assert(i >= 0 && i < ps->size);

	return ps->arr[i];
}

按值查找函数原型: int LocateElem(SqList* ps, SqDataType x) 从前往后逐个查 找,找到第⼀个

相等的就返回其下标,否则返回-1。

cpp 复制代码
int LocateElem(SqList* ps, SqDataType x)
{
	assert(ps);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1;
}

2.5.其他接口

当我们使用顺序表后,理应销毁它,防止内存泄漏,比较简单,如代码所示:

cpp 复制代码
void SqListDestroy(SqList* ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

然后可以判断一下顺序表是否为空和数据个数,如代码所示:

cpp 复制代码
// 检测顺序表是否为空,空返回true,否则返回false
bool EmptySqList(SqList* ps)
{
	assert(ps);
	return ps->size == 0;
}

// 获取顺序表中有效元素个数
int SqListSize(SqList* ps)
{
	assert(ps);
	return ps->size;
}

2.6.测试代码

如图所示:

cpp 复制代码
#include "SqList.h"

void TestSqList1()
{
    SqList L;
    SqListInit(&L);
    SqListInsert(&L, 0, 9);
    SqListInsert(&L, 1, 10);
    SqListInsert(&L, 2, 20);
    SqListInsert(&L, 3, 30);
    SqListInsert(&L, 4, 40);
    SqListInsert(&L, 5, 50);  // 扩容

    SqListPrint(&L);

    // 头插
    SqListInsert(&L, 0, 0);
    SqListPrint(&L);

    // 尾插
    SqListInsert(&L, SqListSize(&L), 60);
    SqListPrint(&L);

    // 中间插入
    SqListInsert(&L, 2, 2);
    SqListPrint(&L);
}

void TestSqList2()
{
    SqList L;
    SqListInit(&L);
    SqListInsert(&L, 0, 9);
    SqListInsert(&L, 1, 10);
    SqListInsert(&L, 2, 20);
    SqListInsert(&L, 3, 30);
    SqListInsert(&L, 4, 40);
    SqListInsert(&L, 5, 50);  // 扩容

    SqListPrint(&L);

    // 删除顺序表第1个位置上的元素
    printf("顺序表中有效元素个数为:%d \n", SqListSize(&L));
    // 删除第一个数据
    printf("删除的元素是:%d \n", SqListDelete(&L, 0));

    // 删除末尾的数据
    printf("删除的元素是:%d \n", SqListDelete(&L, SqListSize(&L) - 1));

    // 删除中间的数据
    printf("删除的元素是:%d \n", SqListDelete(&L, 2));
}

int main()
{
    //TestSqList1();
    TestSqList2();
	return 0;
}
相关推荐
我叫张小白。1 小时前
Redis的缓存雪崩、击穿、穿透和解决方案
数据结构·redis·fastapi·缓存穿透·缓存击穿·雪崩·热点key问题
QiLinkOS1 小时前
发明人与专利价值共生逻辑
c语言·数据结构·c++·人工智能·单片机·嵌入式硬件·算法
南境十里·墨染春水2 小时前
数据结构 ——BST 树
数据结构
江屿风2 小时前
C++图的基本概念流食般投喂-竞赛编
开发语言·数据结构·c++·笔记·算法·图论
Byte不洛2 小时前
哈希表原理 + 冲突解决 + C++实现
数据结构·c++·算法·哈希算法·散列表
_日拱一卒11 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
2401_8685347814 小时前
【无标题】
数据结构·r语言
Mr. zhihao14 小时前
Redis五大高级数据结构:原理-场景-底层-横向对比
数据结构·redis
QiLinkOS14 小时前
【从实验室到商业战场:发明专利如何重塑科技与企业的共生生态】
大数据·c语言·数据结构·c++·人工智能·单片机·算法