数据结构(初阶)笔记归纳2:顺序表的实现

顺序表的实现

目录

顺序表的实现

一、顺序表的概念及结构

1.1.顺序表的概念

1.2.顺序表的特性

1.3.顺序表的分类

1.3.1.静态顺序表:

1.3.2.动态顺序表

[1.3.3.静态顺序表 VS 动态顺序表](#1.3.3.静态顺序表 VS 动态顺序表)

二、顺序表的实现

2.1.顺序表文件结构

2.2.头文件编写(SeqList.h)

2.2.1.头文件包含

2.2.2.数组元素类型重定义

2.2.3.顺序表结构定义

2.2.4.顺序表的初始化

2.2.5.顺序表的销毁

2.2.6.顺序表的尾插

2.2.7.顺序表的头插

2.2.8.顺序表的尾删

2.2.9.顺序表的头删

2.2.10.顺序表的打印

2.2.11.指定位置前插入数据

2.2.12.删除指定位置的数据

2.2.13.顺序表的查找

2.3.源文件编写(SeqList.c)

2.3.1.头文件包含

2.3.2.顺序表的初始化

2.3.3.顺序表的销毁

2.3.4.顺序表的尾插

2.3.5.顺序表的头插

2.3.5.1内存检测函数

2.3.6.顺序表的尾删

2.3.7.顺序表的头删

2.3.8.顺序表的打印

2.3.9.指定位置前插入数据

2.3.10.删除指定位置的数据

2.3.11.顺序表的查找

2.4.测试文件编写(test.c)

2.4.1.头文件包含

2.4.2.测试方法01

2.4.3.测试方法02

三、总结:


一、顺序表的概念及结构

1.1.顺序表的概念

顺序表是线性表的一种,顺序表的底层结构为数组,

但实现了增删查改等功能,就变成了一个数据结构

1.2.顺序表的特性

**线性表:**相同数据类型的数据元素组成的有限数列

关键特性:

  • 物理结构:不一定为线性的
  • 逻辑结构:线性的

**常见的线性表:**顺序表、链表、栈、队列、字符串......

顺序表的特性:

  • 物理结构:线性的
  • 逻辑结构:线性的

**注:**物理结构指数据在内存中的实际存储方式,逻辑结构指数据元素之间的抽象关系

1.3.顺序表的分类

1.3.1.静态顺序表:
cpp 复制代码
struct SeqList
{
	int arr[100];//定长数组
	int size;//有效数据个数
};
1.3.2.动态顺序表
cpp 复制代码
struct SeqList
{
	int* arr;//动态内存开辟
	int size;//有效数据个数
	int capacity;//空间大小
};
1.3.3.静态顺序表 VS 动态顺序表

静态顺序表:

  1. 空间容量固定
  2. 容量不够时,数据就会丢失
  3. 容量过大时,造成空间浪费

动态顺序表:

  1. 可以实现内存动态的增容
  2. 能够按需要调整存储空间
  3. 避免空间浪费与数据丢失

优先选择动态顺序表

二、顺序表的实现

2.1.顺序表文件结构

  • 头文件(SeqList.h):顺序表的结构创建,顺序表的方法声明
  • 源文件(SeqList.c):顺序表的方法实现
  • 测试文件(test.c):测试数据结构的方法

**注:**SeqList是"sequence list"的缩写,表示顺序列表

2.2.头文件编写(SeqList.h)

2.2.1.头文件包含
cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
2.2.2.数组元素类型重定义
cpp 复制代码
typedef int SLDataType;

**注:**使用类型别名便于后续维护修改

2.2.3.顺序表结构定义

1.静态顺序表(不建议使用):

cpp 复制代码
用宏定义常量,便于参数修改
#define N 100

struct SeqList
{
	SLDataType arr[N];
	int size;//有效数据个数
};

2.动态顺序表:

cpp 复制代码
结构体重命名,便于后续使用
typedef struct SeqList
{
	SLDataType* arr;
	int size;//有效数据个数
	int capacity;//空间大小
}SL;
2.2.4.顺序表的初始化
cpp 复制代码
void SLInit(SL* ps);
2.2.5.顺序表的销毁
cpp 复制代码
void SLDestroy(SL* ps);
2.2.6.顺序表的尾插
cpp 复制代码
void SLPushBack(SL* ps, SLDataType x);
2.2.7.顺序表的头插
cpp 复制代码
void SLPushFront(SL* ps, SLDataType x);
2.2.8.顺序表的尾删
cpp 复制代码
void SLPopBack(SL* ps);
2.2.9.顺序表的头删
cpp 复制代码
void SLPopFront(SL* ps);
2.2.10.顺序表的打印
cpp 复制代码
void SLPrint(SL s);
2.2.11.指定位置前插入数据
cpp 复制代码
void SLInsert(SL* ps, int pos, SLDataType x);
2.2.12.删除指定位置的数据
cpp 复制代码
void SLErase(SL* ps, int pos);
2.2.13.顺序表的查找
cpp 复制代码
int SLFind(SL* ps, SLDataType x);

2.3.源文件编写(SeqList.c)

2.3.1.头文件包含
cpp 复制代码
#include "SeqList.h"
2.3.2.顺序表的初始化
cpp 复制代码
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

解析:

  • 将顺序表的指针变量设置为NULL
  • 顺序表的有效数据个数和空间大小设置为0
2.3.3.顺序表的销毁
cpp 复制代码
void SLDestroy(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}

	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

解析:

  • 判断动态内存分配的空间是否存在,如果存在则释放该内存空间
  • 将顺序表的指针变量设置为NULL,顺序表的有效数据个数和空间大小设置为0
2.3.4.顺序表的尾插
cpp 复制代码
void SLPushBack(SL* ps, SLDataType x)
{
    assert(ps);

    if (ps->capacity == ps->size)
    {
	  int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

	  SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));                                        
 
	  if (tmp == NULL)
	  {
		 perror("realloc fail!");
		 exit(1);//直接退出程序,不再继续执行
	  }

      ps->arr = tmp;
      ps->capacity = newCapacity;

    }

	ps->arr[ps->size++] = x;
}

解析:

  • 将元素x存入有效数据的下一位(size)
  • 有效数据个数加一

注:

  • 增强代码的健壮性:为了避免函数传参时传NULL,导致程序报错,可以使用assert断言,并加上头文件<assert.h>
  • 判断空间是否足够:在插入数据之前要先确保有足够的空间,通过当前顺序表的空间大小是否等于有效数据个数来判断,如果相等则说明空间不足
  • 判断空间是否存在:顺序表的指针变量初始值为NULL,增容前要先用三目表达式来判断,如果没有空间就先申请4字节空间,如果有就将原空间加倍,并将增容后的空间大小赋给新的空间大小变量newCapacity
  • 动态申请内存空间:使用realloc函数动态申请内存空间,并且创建一个临时变量tmp来接收该空间,因为如果空间申请失败,会导致原空间数据的丢失
  • 判断空间申请成功:如果tmp为NULL,表示空间申请失败,程序直接退出,不再执行
  • 保存已申请的空间:如果空间申请成功,将tmp赋给arr,newCapacity赋给capacity
2.3.5.顺序表的头插
cpp 复制代码
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++;

}

解析:

  • 断言判断:判断函数参数是否为NULL
  • 空间检测:对顺序表进行空间检测
  • 位移元素:for循环将每一个元素往后移一位
  • 初值设置:i初始值为size,表示有效数据的下一位
  • 操作设置:将后一位【i】赋值为前一位【i - 1】的数据 → arr[size] = arr[size-1]
  • 终止条件:最后一次循环中,后一位【1】赋值为前一位【0】的数据,此时i = 1,得出终止条件:i > 0
  • 插入数据:将x存入首元素,有效数据个数加一

注:

由于头插和尾插都需要判断空间是否足够,可以将这段代码封装成一个函数,专门解决这个问题

代码如下:

2.3.5.1内存检测函数
cpp 复制代码
void SLCheckCapacity(SL * ps)
{

	if (ps->capacity == ps->size)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));

		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}

		ps->arr = tmp;
		ps->capacity = newCapacity;

	}
}
2.3.6.顺序表的尾删
cpp 复制代码
void SLPopBack(SL* ps)
{
	assert(ps);

	assert(ps->arr);

	--ps->size;
}

解析:

  • 断言判断:判断函数参数是否为NULL,顺序表是否为NULL
  • 删除数据:有效数据个数减一,既尾删数据,又不影响后续的增删查改
2.3.7.顺序表的头删
cpp 复制代码
void SLPopFront(SL* ps)
{
	assert(ps);

	assert(ps->arr);

	for (int i = 0; i < size - 1; i++)
	{
		arr[i] = arr[i + 1];
	}

	ps->size--;

}

解析:

  • 断言判断:判断函数参数是否为NULL,顺序表是否为NULL
  • 位移元素:for循环将每一个元素往前移一位
  • 初值设置:i初始值为0,表示首元素
  • 赋值设置:将前一位【i】赋值为后一位【i + 1】的数据 → arr[0] = arr[1]
  • 终止设置:最后一次循环中,前一位【size - 2】赋值为后一位【size - 1】的数据,此时i = size-2,得出终止条件:i < size - 1
  • 删除数据:有效数据个数减一
2.3.8.顺序表的打印
cpp 复制代码
void SLPrint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}

	printf("\n");
}
2.3.9.指定位置前插入数据
cpp 复制代码
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++;
}

解析:

  • 断言判断:判断函数参数是否为NULL,pos值是否为有效值
  • 空间检测:对顺序表进行空间检测
  • 位移元素:for循环将pos位后的元素(包括pos)往后移一位
  • 初值设置:i初始值为size,表示有效数据的下一位
  • 赋值设置:将后一位【i】赋值为前一位【i - 1】的数据 → arr[size] = arr[size-1]
  • 终止设置:最后一次循环中,后一位【pos + 1】赋值为前一位【pos】的数据,此时i = pos+1,得出终止条件:i > pos
  • 插入数据:将x存入pos位,有效数据个数加一
2.3.10.删除指定位置的数据
cpp 复制代码
void SLErase(SL* ps, int pos)
{
	assert(ps);

	assert(pos >= 0 && pos < ps->size);

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

	ps->size--;
}

解析:

  • 断言判断:判断函数参数是否为NULL,pos值是否为有效值
  • 位移元素:for循环将pos位后的元素(不包括pos)往前移一位
  • 初值设置:i初始值为pos,表示pos位的数据
  • 赋值设置:将前一位【i】赋值为后一位【i + 1】的数据 → arr[pos] = arr[pos+1]
  • 终止设置:最后一次循环中,前一位【size - 2】赋值为后一位【size - 1】的数据,此时i = size-2,得出终止条件:i < size-1
  • 删除数据:有效数据个数减一
2.3.11.顺序表的查找
cpp 复制代码
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);

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

	return -1;
}

2.4.测试文件编写(test.c)

2.4.1.头文件包含
cpp 复制代码
#include "SeqList.h"
2.4.2.测试方法01
cpp 复制代码
void SLTest01()
{
    顺序表结构声明
	SL sl;

    顺序表的初始化
	SLInit(&sl);//传送的是sl的地址而非值
    
    测试尾插
    SLPushBack(&sl,1);
    SLPushBack(&sl,2);
    SLPushBack(&sl,3);
    SLPushBack(&sl,4);

    测试头插
    SLPushFront(&sl,5);
    SLPushFront(&sl,6);
    SLPushFront(&sl,7);
    SLPushFront(&sl,8);

    测试尾删
    SLPopBack(&sl);

    测试头删
    SLPopFront(&sl);

    顺序表的打印
    SLPrint(sl);

    顺序表的销毁
    SLDestroy(&sl);
}

int main()
{
    SLTest01();
	return 0;
}

**注:**由于sl变量还没有初始化,所以SLInit函数需要接收的是sl的地址而非值

2.4.3.测试方法02
cpp 复制代码
void SLTest02()
{
    顺序表结构声明
	SL sl;

    顺序表的初始化
	SLInit(&sl);

    顺序表的尾插
    SLPushBack(&sl,1);
    SLPushBack(&sl,2);
    SLPushBack(&sl,3);
    SLPushBack(&sl,4);

    测试在指定位置之前插入数据
    SLInsert(&sl,0,99);
    SLInsert(&sl,sl.size,99);

    测试删除指定位置的数据
    SLErase(&sl,1);

    测试顺序表的查找
    int find = SLFind(&sl,4);
    if(find < 0)
    {
        printf("没有找到!\n");
    }
    else
    {
        printf("找到了,下标为%d\n",find);
    }

    顺序表的打印
    SLPrint(sl);

    顺序表的销毁
    SLDestroy(&sl);
}

int main()
{
    SLTest02()
	return 0;
}

三、总结

  • 想要改变地址对应的数据就要传送地址,传送地址后先要判断是否为NULL
  • 插入数据前要进行空间检测
  • 不要忘记判断pos值是否有效
  • 要掌握每个功能循环的起始、循环、终止条件
  • 不要忘记对有效数据个数进行修改

本篇博客是数据结构中顺序表知识点的整理归纳,后续还会更新顺序表的应用等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~

相关推荐
寻星探路2 小时前
【算法进阶】滑动窗口与前缀和:从“和为 K”到“最小覆盖子串”的极限挑战
java·开发语言·c++·人工智能·python·算法·ai
不穿格子的程序员2 小时前
从零开始刷算法——二叉树篇:层序遍历 + 有序数组转二叉搜索树
算法
Moonquakes5402 小时前
嵌入式基础学习笔记(51)
笔记·单片机·学习
kaikaile19952 小时前
同伦算法求解非线性方程组的MATLAB实现与优化
开发语言·算法·matlab
cici158742 小时前
计算四连杆机构的运动学
线性代数·算法·机器学习
南烟斋..2 小时前
Linux系统编程核心知识指南
linux·算法
小鱼23332 小时前
STM32中的中断机制与应用
c语言·stm32·单片机·嵌入式硬件·mcu
Jerryhut2 小时前
光流估计从原理到实战:基于 Lucas-Kanade 算法与 OpenCV 实现
人工智能·opencv·算法
数据大魔方2 小时前
【期货量化实战】豆粕期货量化交易策略(Python完整代码)
开发语言·数据库·python·算法·github·程序员创富