数据结构(初阶)笔记归纳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值是否有效
  • 要掌握每个功能循环的起始、循环、终止条件
  • 不要忘记对有效数据个数进行修改

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

相关推荐
不会代码的小猴1 分钟前
Linux环境编程第五天笔记
linux·笔记
散峰而望3 分钟前
【算法竞赛】树
java·数据结构·c++·算法·leetcode·贪心算法·推荐算法
鱼很腾apoc6 分钟前
【实战篇】 第14期 算法竞赛_数据结构超详解(下)
c语言·开发语言·数据结构·学习·算法·青少年编程
Gain_chance8 分钟前
32-学习笔记尚硅谷数仓搭建-DWD层首日数据装载脚本及每日数据装载脚本
大数据·数据仓库·hive·笔记·学习
神奇小梵11 分钟前
c语言易错知识点
c语言·开发语言
123_不打狼19 分钟前
AE(自编码器)与 VAE(变分自编码器)核心区别:原理、目标与应用
深度学习·算法·机器学习·vae
Anastasiozzzz22 分钟前
LeetCode hot100 45 跳跃游戏2
算法·leetcode·游戏
近津薪荼23 分钟前
递归专题(3)——反转链表
数据结构·c++·学习·算法·链表
Tisfy26 分钟前
LeetCode 3013.将数组分成最小总代价的子数组 II:两个堆维护k-1小 + 滑动窗口
算法·leetcode·题解·优先队列··有序集合·滑动窗口
学海无涯书山有路28 分钟前
泛型笔记问答
笔记