目录
[1.1 线性表的定义:零个或多个数据元素的有限序列。](#1.1 线性表的定义:零个或多个数据元素的有限序列。)
[1.3 线性表的抽象数据类型](#1.3 线性表的抽象数据类型)
[2.1 顺序表的定义和存储方式](#2.1 顺序表的定义和存储方式)
[2.2.1 静态顺序表的使用](#2.2.1 静态顺序表的使用)
[2.2.3 为什么我们要使用typedef呢?](#2.2.3 为什么我们要使用typedef呢?)
[2.2.4 为什么我们要使用结构体来封装这三个变量?](#2.2.4 为什么我们要使用结构体来封装这三个变量?)
[2.2.5 静态数据表的缺点](#2.2.5 静态数据表的缺点)
[2.3.2 顺序表的增、删、查、改](#2.3.2 顺序表的增、删、查、改)
1.线性表
1.1 线性表的定义:零个或多个数据元素的有限序列。
1.2深度解析
- 序列:表示元素和元素是有顺序的,若有多个元素,则第一个数据无前驱,最后一个数据无后继,其他元素有且仅有一个前驱和后继。
- 有限:在计算机中处理对象都是有限的,不像在数学概念中,有无限个元素的数列。
1.3 线性表的抽象数据类型


线性表的存储结构主要有两种:顺序存储结构和链式存储结构。顺序存储结构是将线性表中的元素存储在一段连续的内存空间中,而链式存储结构则是通过指针将各个元素连接起来,不一定占用连续的内存空间。不同的存储结构会影响线性表操作的效率,例如在顺序表中插入或删除元素可能需要移动大量元素,而在链表中这些操作通常更高效。
2.顺序表
顺序表就是一种顺序存储结构
2.1 顺序表的定义和存储方式
顺序表:用一段地址连续的存储单元依次存储数据元素的线性结构。
存储方式:在内存中找一块空地,通过占位的方式,把一定的存储空间占用,然后将相同的数据类型元素依次放在这块空地中,那么,既然存储的元素都是相同的数据类型,那么在c语言中我们就可以用一维数组来实现顺序结构的存储。
2.2静态顺序表
静态顺序表:使用定长数组存储元素。
2.2.1 静态顺序表的使用
cpp
//静态顺序表
typedef int SLDataType; //方便后续该数据类型,如果不这么设置,想要修改数据类型的话,会产生很多步骤
#define N 20
typedefy struct SeqList
{
SLDataType a[N]; //存储空间的起始位置
int size; //有效数据的个数
int capacity; //空间容量大小
}SL;
由上述代码可得,顺序表需要三个变量
存储空间的起始地址:a
有效数据的个数:size
空间容量的大小:capacity
2.2.3 为什么我们要使用typedef呢?
在顺序表中使用
typedef
主要是为了提高代码的可读性和可维护性。typedef
是C和C++编程语言中的一个关键字,它允许为现有的数据类型(如基本数据类型int
,float
,char
等)或自定义的数据类型(如结构体、枚举等)创建一个新的名字。这种做法可以使代码更加简洁,并且能够更好地表达数据的用途和含义typedef int SLDataType; //方便后续该数据类型,如果不这么设置,想要修改数据类型的话,会产生很多步骤
在结构体那里使用简化了名称,方便后续结构体
2.2.4 为什么我们要使用结构体来封装这三个变量?
- 明确数据关系:结构体允许我们将相关的数据项组合在一起,形成一个数据集合。这样做的好处是能够明确数据之间的关系,使得代码更加清晰和易于理解。例如,在顺序表中,我们可以使用结构体来封装数组和记录数组长度的变量,从而更直观地表示顺序表的状态。
- 简化对数据块的操作:通过结构体,我们可以将一系列相关的数据操作封装在一起,形成一个数据块。这样,当我们需要对这些数据进行操作时,只需要操作这个数据块即可,而不需要逐个处理每个数据项。这大大简化了对数据块的操作,提高了代码的效率和可读性。
- 简化参数列表:在函数调用时,如果需要传递多个相关的数据项,使用结构体可以简化参数列表。将这些数据项打包成一个结构体,作为函数的参数,可以减少函数传递时的多个参数传进传出的复杂性。
- 减少维护成本:使用结构体可以减少代码的维护成本。一旦数据结构定义好了,就可以在整个程序中重复使用,而不需要每次都重新定义这些数据项。此外,如果需要改变数据结构,只需要修改结构体的定义,而不需要在所有使用这些数据的地方都进行修改。
2.2.5 静态数据表的缺点
- 内存空间的预分配:静态数据表在创建时需要预先申请足够大的一整块内存空间,这意味着存储数据元素的个数从其创建的那一刻就已经确定,后期无法更改。这种设计限制了灵活性,如果后续需要增加更多的数据,可能会遇到内存不足的问题。
- 内存碎片化:静态数据表可能会存在内存碎片化使用的情况,而且内存不连续,需要借助指针进行访问。这种内存管理方式可能导致内存利用率不高,同时也增加了编程的复杂性。
- 文件体积大:由于数据都存储在HTML文件中,静态数据表的文件体积通常较大。这不仅占用了更多的存储空间,也可能影响网站的传输效率和加载速度。
- 维护困难:如果需要更改源代码,静态数据表要求必须全部更改,而不能像动态数据表那样局部更新。这对于非专业人士来说可能会比较麻烦,尤其是在需要频繁更新内容的情况下。
总而言之就是空间开多了浪费,开少了不够
2.3动态顺序表
cpp
//动态顺序表
typedef int SLDataType; //方便后续该数据类型,如果不这么设置,想要修改数据类型的话,会产生很多步骤
#define INIT_CAPACITY 4
typedef struct SeqList
{
SLDataType* arr; //存储空间的起始位置
int size; //有效数据的个数
int capacity; //空间容量大小
}SL;
2.3.1动态顺序表的优点
- 灵活的容量调整:动态顺序表的一个显著优点是其容量可以动态调整。这意味着当需要插入的元素数量超过当前表长时,可以动态地增加数据域的长度。这种特性克服了静态顺序表容量固定不变的缺点,使得动态顺序表能够适应不断变化的数据量需求。
- 高效的存取速度:由于动态顺序表中的元素在内存中是连续存储的,因此可以通过下标直接访问元素,实现了快速的随机访问。这种O(1)级别的存取速度使得动态顺序表在需要频繁访问元素的应用场景中非常高效。
- 简洁的代码实现:动态顺序表的操作相对简单,例如初始化、插入、查找、删除等操作都可以通过简单的函数实现。这不仅降低了编程的复杂性,也有助于提高代码的可读性和维护性。
- 自动化内存管理:在C++等高级语言中,动态顺序表(如vector)可以自动管理内存,包括动态分配和释放内存。这种自动化管理减少了手动管理内存的负担,降低了出现内存泄漏等错误的风险。
- 空间利用率高:动态顺序表在存储数据时,由于其元素是连续存储的,因此具有较高的空间密度和利用率。相比于一些链式结构,动态顺序表在存储空间的使用上更加紧凑。
- 综上所述,动态顺序表因其灵活的容量调整、高效的存取速度、简洁的代码实现、自动化内存管理和高空间利用率等优点,在数据结构中占有重要地位,并广泛应用于各种软件开发中。
2.3.2 顺序表的增、删、查、改
顺序表的初始化
cpp
//顺序表的初始化
void SLInit(SL* ps)
{
assert(ps);
//将a的空间申请好
ps->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
//检查空间是否开辟成功
if (ps->a == NULL)
{
perror("malloc fail");
return 1;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
}
顺序表的销毁
cpp
//顺序表的销毁
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
顺序表的输出
cpp
//顺序表的输出
void SLPrint(SL* ps)
{
assert(ps);
int i = 0;
for (i = 0;i < ps->size;i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
顺序表的扩容
cpp
//检查容量并决定是否扩容
void SLCheckCapacity(SL* ps)
{
assert(ps);
//当有效数字个数等于空间容量时,我们就需要扩容了
SLDataType* tmp = (SLDataType*)realloc(ps->a,sizeof(SLDataType) * INIT_CAPACITY * 2);
//检查是否扩容成功
if (tmp == NULL)
{
perror("rrealloc fail");
return 1;
}
ps->a = tmp;
ps->capacity *= 2;
}
顺序表的插入与删除
顺序表的尾插


cpp
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
顺序表的尾删


cpp
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size > 0);
SLCheckCapacity(ps);
ps->a[ps->size - 1] = 0;
ps->size--;
}
顺序表的头插


cpp
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end+1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
顺序表的头删
cpp
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
顺序表的某个位置插入


cpp
//某个位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int end = ps->size-1;
if (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos]=x;
ps->size++;
}
顺序表的某个位置删除


cpp
//某个位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
SLCheckCapacity(ps);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin - 1];
++begin;
}
ps->size--;
}
头插:SLInsert(ps, 0, x);
头删:SLErase(ps, ps->size - 1);
尾插:SLInsert(ps,ps->size, x);
尾删:SLErase(ps, 0);
2.4完整代码
cpp
//SeqLIst.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*
typedef int SLDataType;
#define N 10
//静态顺序表--开少了不够用,开多了浪费
struct SeqList
{
SLDataType a[N];
int size;
};
*/
typedef int SLDataType;//好处想改为其他类型,只用改一处数据
#define INIT_CAPACITY 4
//动态顺序表--按需申请
typedef struct SeqList
{
SLDataType* a;//一个指针指向一个空间,在堆上申请空间
int size; //有效数据个数
int capacity; //空间容量
}SL;
//数据管理的需求:增,删,查,改
//顺序表的初始化
void SLTnit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印顺序表
void SLPrint(SL* s);
//检查容量并决定是否扩容
void SLCheckCapacity(SL* ps);
//顺序表的插入与删除
//尾插
void SLPushBack(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
//头插
void SLPushFront(SL* ps, SLDataType x);
//头删
void SLPopFront(SL* ps);
//某个位置插入
void SLInsert(SL* ps, int pos, SLDataType x);
//某个位置删除
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps, SLDataType x);
cpp
//SeqList.c
#include"SeqList.h"
//初始化
void SLTnit(SL* ps)
{
assert(ps);
ps->a =(SLDataType*)malloc(sizeof(SLDataType)*INIT_CAPACITY);
if (ps->a == NULL)
{
perror("malloc fail");
return 1;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
}
//销毁
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->a);
ps->a == NULL;
ps->capacity = ps->size = 0;
}
//打印顺序表
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0;i < ps->size;++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//检查容量并决定是否扩容
void SLCheckCapacity(SL* ps)
{
assert(ps);
//检查容量够不够,不够则扩容
if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);//一般来说扩容2倍比较合适
//检验是否开辟成功
if (tmp == NULL)
{
perror("realloc fail");
return 1;
}
ps->a = tmp;
ps->capacity *= 2;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
//assert(ps);
//SLCheckCapacity(ps);
//ps->a[ps->size] = x;
//ps->size++;
SLInsert(ps, ps->size, x);
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
//暴力检查
assert(ps->size > 0);
//温柔的检查
/*if (ps->size == 0)
return 1;*/
//ps->a[ps->size - 1] = 0;
//ps->size--;
SLErase(ps, ps->size - 1);
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
//assert(ps);
//SLCheckCapacity(ps);
//int end = ps->size - 1;
//while (end >= 0)
//{
// ps->a[end+1] = ps->a[end];
// --end;
//}
//ps->a[0] = x;
//ps->size++;
SLInsert(ps, 0, x);
}
//头删
void SLPopFront(SL* ps)
{
//assert(ps);
//assert(ps->size > 0);
从前往后来
//int begin = 1;
//while (begin < ps->size)
//{
// ps->a[begin - 1] = ps->a[begin];
// ++begin;
//}
//ps->size--;
SLErase(ps, 0);
}
//某个位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int end = ps->size-1;
if (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos]=x;
ps->size++;
}
//某个位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
SLCheckCapacity(ps);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin - 1];
++begin;
}
ps->size--;
}
//查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0;i <ps->size;i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}