2024年最新数据结构与算法——线性表(顺序表篇)_顺序建立线性表的理解,开发人员必学

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ai

ai这个元素时,我们就可以称作

a

i

1

ai-1

ai−1是

a

i

ai

ai的直接前驱元素,称

a

i

1

ai+1

ai+1是

a

i

ai

ai的直接后继元素。当

i

=

1

,

2

,

3

,

.

.

.

,

n

1

i=1,2,3,…,n-1

i=1,2,3,…,n−1时,

a

i

ai

ai有且仅有一个直接后继,当

i

=

2

,

3

,

4

,

.

.

.

,

n

i=2,3,4,…,n

i=2,3,4,…,n时,

a

i

ai

ai有且仅有一个直接前驱

线性表中元素的个数n(n>=0)定义为线性表的长度n=0时,表示线性表为空,称空表;在非空的线性表中,每一个元素都有一个确定的位置,如

a

1

a1

a1是第一个数据元素,

a

n

an

an是最后一个数据元素,

a

i

ai

ai是第i个数据元素,称i为数据元素

a

i

ai

ai在线性表中的位序

在稍微复杂的线性结构中,一个数据元素可以由若干个数据项(item)组成,在这种情况下,我们通常把数据元素称为记录,含有大量记录的线性表又称为文件

例如,把一张学生表中的每个学生看为一个记录,记录由学生的学号、姓名、性别、班级组成,那整张学生表,可以看做一个文件

在这里插入图片描述

🚢线性结构的特点

在数据元素的非空有限集中:

  • 📌存在唯一的一个首元素
  • 📌存在唯一的一个尾元素
  • 📌除第一个元素之外,集合中的每个数据元素有且只有一个前驱
  • 📌除最后一个元素外,集合中的每个数据元素有且只有一个后继

🚀线性表的基本操作

  • 📌初始化表。构造一个空的线性表L,分配内存空间
  • 📌销毁操作。销毁线性表,并释放线性表L所占用的内存空间
  • 📌插入操作。在表L中的第i个位置上插入指定的元素
  • 📌删除操作。删除表L中的第i个位置的元素
  • 📌按值查找操作。根据给定的值,在表L中查找到与之对应的元素
  • 📌按位查找操作。获取到表L中的第i个位置元素的值

其他常用操作:

  • 📌求表长。返回一个线性表的长度,也可以理解为表L中的元素个数
  • 📌输出操作。顾名思义,按照先后次序依次打印出表L中的所有元素值
  • 📌判空操作。判断一个线性表L是否为空表

🚀线性表的顺序表示和实现(顺序表)

线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素

🚢什么是顺序表?

用顺序存储的方式实现线性表

线性表的顺序表示称作为线性表的顺序存储结构或者顺序映像(sequential mapping),元素之间的关系由存储单元的邻接关系来体现,称这种存储结构的线性表为顺序表,顺序表的相邻元素在逻辑结构和物理结构(存储结构)位置相邻。

在这里插入图片描述

假设线性表的每个元素需占用l个存储空间,并以所占用的第一个单元的存储地址作为数据元素的存储位置。则线性表中第i+1个数据元素的存储位置

L

O

C

(

a

i

1

)

LOC(ai+1)

LOC(ai+1)和第i个数据元素的存储位置

L

O

C

(

a

i

)

LOC(ai)

LOC(ai)之间满足下列关系:

📌

L

O

C

(

a

i

1

)

=

L

O

C

(

a

i

)

l

LOC(ai+1) = LOC(ai)+l

LOC(ai+1)=LOC(ai)+l

在这里插入图片描述

则通过上述这个关系,又可以得出第i个元素第m个元素之间的关系为:

📌

L

O

C

(

a

m

)

=

L

O

C

(

a

i

)

(

m

i

)

l

LOC(am) = LOC(ai)+(m-i)*l

LOC(am)=LOC(ai)+(m−i)∗l

在这里插入图片描述

只要确定了存储线性表的起始位置,线性表中任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的存储结构

🚢顺序表的特点
  • 📌随机访问,即可以在

O

(

1

)

O(1)

O(1)时间内找到第i个元素

  • 📌存储密度高,每个节点只存储数据元素
  • 📌拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
  • 📌插入、删除操作不方便,需要移动大量元素

🚀顺序表的基本操作

🚢静态分配(初始化表)
# define MaxSize 10 //定义最大长度
typedef struct{
    int data[MaxSize];      //用静态数组存放数据元素
    int length;             //顺序表的当前长度
}SqList;                    //顺序表名称

void InitializeList(SqList &L){
    for (int i = 0; i < MaxSize; i++)
    {
        L.data[i] = 0;      //将所有数据元素初始化为0
    }
    L.length = 0;
}

int main(){
    SqList L;               //声明一个顺序表
    InitializeList(L)       //初始化顺序表
    ......
}

在这里插入图片描述

🚢动态分配(初始化表)

在C语言中,动态分配需要借助两个函数:

  • void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。指针指向已分配大小的内存
  • void free(void *ptr) 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。指针指向一个要释放内存的内存块
#define InitSize 10 //默认的最大长度
typedef struct{
    int \*data;              //指向动态分配数组的指针
    int MaxSize;            //顺序表的最大容量
    int length;             //顺序表的当前长度
}SeqList;

void InitializeList(SeqList &L){
    //用malloc函数申请一片连续的存储空间
    L.data = (int \*)malloc(InitSize\*Sizeof(int));       //返回一个指针变量
    L.length = 0;
    L.MaxSize = InitSize;
}

//动态增加数组的长度
void IncreaseSize(SeqList &L, int len){
    int \*p = L.data;
    L.data = (int \*)malloc((L.MaxSize+len)\*sizeof(int));
    for(int i=0; i<L.length; i++){      //将原来的数据赋值到新区域
        L.data[i] = p[i];
    }
    L.MaxSize = L.MaxSize + len;        //增加顺序表的最大长度 
    free(p);                            //释放原来的内存空间
}


int main(){
    SeqList L;              //声明一个顺序表
    InitializeList(L);      //初始化顺序表
    ......
    ......
    IncreaseSize(L, 5);     //往原来顺序表中再添加5个元素
}

内存分析和上面静态分配一样,不同的是,在动态分配中,生成连续的存储空间后,需要通过指针指向这片连续的空间

在这里插入图片描述

动态增加顺序表的长度理解起来其实也很简单,实际上就是再次使用malloc()函数再次开辟一个更大的空间,然后把原来的顺序表复制到新开辟的空间里,再回收原来的空间

在这里插入图片描述

🚢插入元素

插入操作:在表L中的第i个位置上插入指定元素

#define MaxSize 10 //定义的最大长度
typedef struct{
    int MaxSize;            //顺序表的最大容量
    int length;             //顺序表的当前长度
}SeqList;

bool ListInsert(SeqList &L, int i, int j){
    if(i<1 || i>L.length+1){                //判断i的范围是否有效 
        return false;
    }
    if(L.length >= MaxSize){                //判断存储空间是否已经满了
        return false;
    }
    for(int k=L.length; K>=i; K--){         //将第i个元素及其以后的元素后移
        L.data[k] = L.data[k-1];
    }      
    L.data[i-1] = e;        //在位置i处放入新增元素j
    L.length++;             //长度加1
}

int main(){
    SeqList L;              //声明一个顺序表
    InitializeList(L);      //初始化一个顺序表
    ......
    ......
    ListInsert(L, 3, 3);    //在顺序表L第三个元素的位置插入元素3
}

插入操作时,我们要在执行插入操作的具体实现中分析最深层循环语句的执行次数与问题规模n的关系,上述代码问题规模n=L.length

分析上述代码,可以得出,假如新元素插入在表尾,那么就不需要移动元素,那时间复杂度就为

O

(

1

)

O(1)。

O(1)。假如新元素插入在表头,那么所有元素都要向后移动,时间复杂度就为

O

(

n

)

O(n)

O(n)。

我们一般都是考虑最坏时间复杂度,所以,可得出在顺序表中插入一个元素的时间复杂度为:

📌

T

(

n

)

=

O

(

n

)

T(n) = O(n)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

析最深层循环语句的执行次数与问题规模n的关系,上述代码问题规模n=L.length

分析上述代码,可以得出,假如新元素插入在表尾,那么就不需要移动元素,那时间复杂度就为

O

(

1

)

O(1)。

O(1)。假如新元素插入在表头,那么所有元素都要向后移动,时间复杂度就为

O

(

n

)

O(n)

O(n)。

我们一般都是考虑最坏时间复杂度,所以,可得出在顺序表中插入一个元素的时间复杂度为:

📌

T

(

n

)

=

O

(

n

)

T(n) = O(n)

[外链图片转存中…(img-leLIQL2M-1715660721956)]
[外链图片转存中…(img-gwyWThaZ-1715660721956)]
[外链图片转存中…(img-1iMksSyR-1715660721956)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值