参考:
数据结构的三要素
一、数据的逻辑结构
逻辑结构是指数据元素之间的逻辑关系,即从逻辑关系上描述数据。
逻辑结构包括:
- 集合结构:结构中的数据元素之间除"同属一个集合"外,别无其它关系(例如:一群羊)。
- 线性结构:结构中的数据元素之间只存在一对一的关系,除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继(例如:排队取号)。
- 树形结构:结构中数据元素之间存在一对多的关系(例如:思维导图)。
- 图状结构:数据元素之间是多对多的关系(例如:道路信息)。
二、数据的存储结构(物理结构)
如何用计算机表示数据元素的逻关系?
存储结构是指数据结构在计算机中的表示(又称映像),也称物理结构。
存储结构包括:
- 顺序存储:把逻辑上相邻的元素存储在物理位置也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
- 链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。
- 索引存储:在存储元素信息的同时,还建立附加的索引表,索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)。
- 散列存储:根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储。
需要理解几点:
- 若采用顺序存储,则各个数据元素在物理上必须是连续的;若采用非顺序存储,则各个数据元素在物理上可以是离散的。
- 数据的存储结构会影响存储空间分配的方便程度。
- 数据的存储结构会影响对数据运算的速度
三、数据的运算
- 数据上的运算包括运算的定义和实现。
- 运算的定义是针对逻辑结构指出运算的功能。
- 运算的实现是针对存储结构的,指出运算的具体操作步骤。
针对于某种逻辑结构,结合实际需求,定义基本运算。
这里要注意理解,运算的定义和运算的实现,很容易就理解错误。
比如,我想要一个线性结构,这是逻辑上的,但是具体实现可以用顺序存储也可以用链式存储,这是存储结构。
区别数据结构和算法
程序 = 数据结构+算法
- 数据结构:如何用数据正确地描述现实世界的问题,并存入计算机。
- 算法:如何高效地处理这些数据,以解决实际问题
注意区分:数据结构是数据结构,算法是算法,是两个东西,不要混为一谈。数据结构是数据的组织方式,算法是基于数据结构进行的数据处理的方法。数据结构就那么几种,但是算法在理论上是无穷无尽的。
算法(Algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作。
算法的特性:
- 有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。
- 确定性:算法中每条指令必须有确定的含义,对于相同的输入只能得到相同的输出。
- 可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现。
- 输入:一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合。
- 输出:一个算法有一个多个输出,这些输出是与输入有着某种特定关系的量。
我们可以类比:y = f(x)函数,其中x就是输出,y就是输出,这个函数就是算法。
好的算法达到的目标:
- 正确性:算法应能够正确的求解问题。
- 可读性:算法应具有良好的可读性,以帮助人们理解。
- 健壮性:输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名奇妙地输出结果。
- 效率与低存储量需求:花的时间少即:时间复杂度低。不费内存即:空间复杂度低。
更多待补充。
线性表的定义和基本操作
线性表是描述一种逻辑结构,也就是线性结构。
线性表的定义
线性表是具有相同数据类型的n(n>0)个数据元素的有限序列。
(其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为)
L=(a1,a2,...,ai,a1i+1,...,an,)
特点:
存在惟一的第一个元素。
存在惟一的最后一个元素。
除第一个元素之外,每个元素均只有一个直接前驱。
除最后一个元素之外,每个元素均只有一个直接后继
几个概念:
ai是线性表中的"第i个"元素线性表中的位序。
a1是表头元素;an是表尾元素。
除第一个元素外,每个元素有且仅有一个直接前驱:除最后一个元素外,每个元素有且仅有一个直接后继。
存储结构:
顺序存储结构:顺序表
链式存储结构:链表
线性表的基础操作
InitList(&L):初始化表。构造一个空的线性表L,分配内存空间。
DestroyList(&L): 销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
ListInsert(&L;i,e):插入操作。在表L中的第i个位置上插入指定元素e。
ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
GetElem(L,i): 按位查找操作。获取表L中第i个位置的元素的值。
Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若L为空表,则返回true,否则返回false。
等等。
Q:什么时候要传入参数的引用"&"
A:对参数的修改结果需要"带回来"
更多按照实际需求进行实现即可。
顺序表
我们看完线性表的逻辑结构和基本运算,现在继续学习物理结构:顺序表
顺序表的概念
顺序表:用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
顺序表的特点:
- 随机访问,即可以在O(1)
时间内找到第 i 个元素。
存储密度高,每个节点只存储数据元素。
拓展容量不方便(即使使用动态分配的方式实现,拓展长度的时间复杂度也比较高,因为需要把数据复制到新的区域)。
插入删除操作不方便,需移动大量元素:O(n)
顺序表的实现
顺序表的静态分配 顺序表的表长刚开始确定后就无法更改(存储空间是静态的)
顺序表的动态分配
顺序表的基本操作
插入和删除
查找
链表
链表是另一种储存结构。
单链表
单链表的基本概念
单链表:用链式存储实现了线性结构。一个结点存储一个数据元素,各结点间的前后关系用一个指针表示。
特点:
优点:不要求大片连续空间,改变容量方便。
缺点:不可随机存取,要耗费一定空间存放指针。
两种实现方式:
带头结点,写代码更方便。头结点不存储数据,头结点指向的下一个结点才存放实际数据。
不带头结点,麻烦。对第一个数据结点与后续数据结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑。
单链表的插入和删除
单链表的查找
双链表
循环链表
单链表和循环单链表的比较:
单链表:从一个结点出发只能找到该结点后续的各个结点;对链表的操作大多都在头部或者尾部;设立头指针,从头结点找到尾部的时间复杂度=O(n),即对表尾进行操作需要O(n)的时间复杂度;
循环单链表:从一个结点出发,可以找到其他任何一个结点;设立尾指针,从尾部找到头部的时间复杂度为O(1),即对表头和表尾进行操作都只需要O(1)的时间复杂度;
循环单链表优点:从表中任一节点出发均可找到表中其他结点。
其实说实话,可能很多人依然分不清线性表 ,顺序表 ,和链表之间的区别和联系!
线性表:逻辑结构, 就是对外暴露数据之间的关系,不关心底层如何实现,数据结构的逻辑结构大分类就是线性结构和非线性结构而顺序表、链表都是一种线性表。
顺序表、链表:物理结构 ,他是实现一个结构实际物理地址上的结构。比如顺序表就是用数组 实现。而链表用指针完成主要工作。不同的结构在不同的场景有不同的区别。
更多参考:一文搞懂线性表(顺序表、链表)-腾讯云开发者社区-腾讯云 (tencent.com)
顺序表和链表的对比
顺序表和链表是常见的两种数据结构,用于组织和存储数据。
它们在实际应用中各有优缺点,下面详细讲解一下它们的区别、特点以及各自的优缺点。
顺序表(Sequential List): 顺序表是一种连续的存储结构,数据元素在内存中占据一块连续的存储空间。它可以通过下标来直接访问元素,因此支持随机访问。顺序表可以使用数组来实现,每个元素占据固定大小的内存空间。
优点:
随机访问性能好:由于顺序表的元素在内存中连续存储,可以通过下标直接访问元素,因此随机访问的时间复杂度为O(1)。
存储效率高:顺序表不需要额外的存储空间来存储指针,只需要存储元素本身,因此相对于链表来说,存储效率较高。
缺点:
插入和删除操作复杂:由于顺序表是连续存储的,当需要插入或删除元素时,需要移动其他元素来腾出空间或填补空缺,因此时间复杂度为O(n),其中n是元素的个数。
存储空间固定:顺序表在初始化时需要指定固定大小的存储空间,如果元素数量超过了预设的大小,需要进行扩容操作,这可能导致时间和空间的浪费。
链表(Linked List): 链表是一种离散的存储结构,数据元素存储在称为节点的单元中,每个节点包含数据和指向下一个节点的指针。通过链接各个节点,形成数据的逻辑顺序。
优点:
插入和删除操作简单:由于链表的节点通过指针链接,插入和删除操作只需要修改指针的指向,不需要移动其他元素,因此时间复杂度为O(1)。
灵活使用内存空间:链表可以根据实际情况动态分配内存空间,只需要在插入新节点时分配新的内存,因此避免了固定大小的存储空间的限制。
缺点:
随机访问性能差:由于链表中的元素并不连续存储,访问元素需要通过指针依次遍历,因此随机访问的时间复杂度为O(n),其中n是元素的个数。
需要额外的存储空间:链表的每个节点需要额外的指针来指向下一个节点,这样会占用额外的存储空间,相对于顺序表来说,存储效率较低。
顺序表适用于需要频繁进行随机访问的场景,而链表适用于频繁进行插入和删除操作的场景。选择使用哪种数据结构需要根据具体的应用场景和需求来决定,权衡时间复杂度、空间复杂度以及操作的频率等因素。在实际应用中,也可以根据具体需求采用顺序表和链表的组合形式,发挥各自的优势。
顺序表的一些应用场景举例:
顺序表的应用场景非常广泛。例如,在处理大量数据时,可以使用顺序表来存储和操作数据。由于顺序表的存取速度快,因此在需要频繁访问和修改数据的情况下,使用顺序表可以提高程序的执行效率。另外,在实现一些常见的数据结构(如栈、队列等)时,也可以使用顺序表作为底层存储结构。 需要注意的是,虽然顺序表具有存取速度快和空间利用率高的优点,但也存在一些局限性。例如,当需要频繁插入和删除元素时,顺序表的效率较低,因为需要移动大量的元素来保持元素的连续性。此时,链表等其他数据结构可能更适合。
链表的一些应用场景举例:
实现栈和队列:链表作为栈或队列的底层数据结构,实现入栈、出栈、入队、出队等操作时具有高效性和灵活性。
实现哈希表:在哈希表中,每个关键字会被映射到一个桶中,而这个桶则是一个链表,其中存放相同关键字的元素。
作为缓存的数据结构:在缓存中,经常需要在数据集中插入、删除、移动元素。链表结构提供了一种高效的方式来操作这些数据。
栈
栈是逻辑上的概念,属于线性表的一种。
栈的基本概念
只允许在一端(栈顶top)进行插入或删除操作的受限的线性表。
后进先出(Last In First Out)LIFO。
进栈顺序:a1 > a2 > a3 > a4 > a5
出栈顺序:a5 > a4 > a3 > a2 > a1
栈的基本操作
InitStack(&S):初始化栈。构造一个空栈 S,分配内存空间。
DestroyStack(&S):销毁栈。销毁并释放栈 S 所占用的内存空间。
Push(&S, x):进栈。若栈 S 未满,则将 x 加入使其成为新的栈顶元素。
Pop(&S, &x):出栈。若栈 S 非空,则弹出(删除)栈顶元素,并用 x 返回。
GetTop(S, &x):读取栈顶元素。若栈 S 非空,则用 x 返回栈顶元素。
StackEmpty(S):判空。断一个栈 S 是否为空,若 S 为空,则返回 true,否则返回 false。
既然栈是个线性表,那么就有顺序存储和链式存储两种具体实现方式。
栈的顺序存储实现
栈的链式存储
链栈的定义
定义:采用链式存储的栈称为链栈。
优点:链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。
特点:进栈和出栈都只能在栈顶一端进行(链头作为栈顶)
链表的头部作为栈顶,意味着:
在实现数据"入栈"操作时,需要将数据从链表的头部插入;
在实现数据"出栈"操作时,需要删除链表头部的首元节点;
因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表;
队列
队列的基本概念
只允许在表的一端(队尾)插入,表的另一端(队头)进行删除操作的受限的线性表。
特点:先进先出(先入队的元素先出队)、FIFO(First In First Out)。
队列的基本操作
InitQueue(&Q):初始化队列,构造一个空队列Q。
QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。
EnQueue(&Qx):入队,若队列Q未满,则将x加入使之成为新的队尾。
DeQueue(&Q&x):出队,若队列Q非空,则删除队头元素,并用x返回。
GetHead(Q&x):读队头元素,若队列Q非空则用x返回队头元素。
ClearQueue(&Q):销毁队列,并释放队列Q占用的内存空间。
队列的顺序存储实现
队头指针:指向队头元素
队尾指针:指向队尾元素的下一个位置
队列的链式存储实现
同样的,队列也可以用链式存储实现。
略
串
其实,字符串的"串"也是一种数据结构
了解即可,略。
树
树的基本定义
树:n(n>=0)个节点的有限集合,是一种逻辑结构,当n=0时为空树,且非空树满足: 有且仅有一个特定的称为根的节点. 当n>1时,其余结点可分为m (m >0) 个互不相交的有限集合T1,T2,...,Tm ,其中每个集合本身又是一棵树,并且称为根结点的子树。
树是一种递归的数据结构
非空树特点:
有且仅有一个根节点 没有后继的结点称为"叶子结点"(或终端节点) 有后继的结点称为"分支结点" (或非终端结点) 除了根节点外,任何一个结点都有且仅有一个前驱 每个结点可以有0个或多个后继。
基本术语
祖先结点:自己的之上都是祖先节点。
子孙结点:自己的之下都是子孙节点。
双亲结点 (父节点) :和自己相连的上一个就是父节点。
孩子结点:和自己相连的下面一个。
兄弟结点:我自己同一个父节点的。
堂兄弟结点:同一层的节点。
属性:
结点的层次(深度)--从上往下数
结点的高度-一从下往上数
树的高度 (深度)-一总共多少层
结点的度--有几个孩子(分支)
树的度一-各结点的度的最大值
有序树和无序树
有序树--逻辑上看,树中结点的各子树从左至右是有次序的,不能互换
无序树--逻辑上看,树中结点的各子树从左至右是无次序的,可以互换
森林是m(>=0)棵互不相交的树的集合。
更多查看参考文章即可。
图
图状结构,暂略。
后记
数据结构中,我们重点学习线性表,树和图作为了解即可。
线性表中,我们要掌握顺序表和链表(单链表+双链表)这两种存储方式的具体实现,然后再掌握栈和队列。
总结来说就是:
顺序表+单链表+双链表+顺序栈+链栈+顺序队列+链式队列,共七种实现。
具体分为四篇文章讲解,分别为:数据结构之顺序表,数据结构之链表,数据结构之栈,数据结构之队列。
算法的专业性比较强,具体业务场景有特定的一些算法,作为嵌入式开发,重点是掌握这些基本的数据结构,算法部分按需学习即可。