【已完结】数据结构概念汇总
1.绪论(简答题概率极高)
考察方向
- 概念(概率极高)
- 时间复杂度
- 逻辑结构和存储(物理)结构含义和关系
-
逻辑结构描述指数据元素之间逻辑关系
-
*四大基本逻辑结构
-
集合结构(集合关系)
-
线性结构(一对一关系)
-
树形结构(一对多关系)
-
图结构(多对多关系)
-
-
-
存储结构是逻辑结构在计算机中存储映象,包括数据元素的表示和关系的表示。
- 顺序存储、链式存储、索引存储、散列存储、树形存储、图形存储
-
关系:存储结构是逻辑关系的映象与元素本身映象,是数据结构的实现;逻辑结构是数据结构的抽象。
*数据类型和抽象数据类型的含义和关系
- 数据类型:表示一组性质相同的值的集合,以及集合上对应的操作。
- 有两大类型,分别是原子类型,如指针类型,和结构类型,如学籍表的学生信息
- 作用
- 解决了“存”的问题:它解释了开辟的空间应该如何存放
- 解决了“取”的问题:它解释了在取出内存空间时,看待内存中数据的视角
- 抽象数据类型定义数据对象、数据对象中各元素的关系和处理数据的操作
- 最主要的特点是数据抽象与信息屏蔽
- 作用是用于指定逻辑特性而不指定具体实现细节
- 抽象数据类型相对数据类型更广义,因为它不仅限于处理器中已定义的数据类型,还包括软件系统用户自定义的复杂系统类型。
什么是数据结构
- 数据结构两个构成要素:数据元素和关系
- 数据结构是按照一定逻辑关系将一组数据连接起来,将其映射到计算机存储器中,并定义运算的集合。
- 形式化的定义:数据元素的集合、关系的集合(线性/非线性)以及运算的集合(插入删除)
*线性结构和非线性结构区别
- 线性结构表示一对一的线性关系
- 非线性结构表示一对多或者多对多的层次或任意关系
*算法的定义 特性 时间复杂度
- 算法是规则的有限集合,是为解决特定问题而规定的一系列操作。
- 特性
- 有限性:有限步骤之内正常结束,不能形成无穷循环
- 确定性:算法中的每一个步骤必须有确定含义,无二义性。
- 可行性:原则上能精确进行,操作可通过已实现基本运算执行有限次而完成。
- 输入:有多个或0个输入
- 输出:至少有一个或多个输出。
- 要求
- 正确性
- 可读性
- 健壮性
- 高效率,低存储
- 时间复杂度
- 时间复杂度是用来定量描述算法运行时间,而时间复杂度是用问题规模的函数来表示的,即计算基本语句的执行次数,然后取同阶项,使用大O描述,从而表示当输入值大小为无穷大的问题规模
算法、语言、程序的关系
- 算法 + 数据结构 = 程序
- 算法描述了数据接间的关系(包括逻辑关系、存储关系)
- 算法通过高级语言描述
- 程序是算法在计算机中的实现
函数参数传递的方式与特点
- 参数传递的主要方式按值传递,按地址传递两类
- 函数内部对参数的修改是否会影响到实际参数
*分析时间复杂度
- 看内层嵌套,注意变量是n还是i
- 递归调用明确每一次调用过程。
2 线性表
考察方向
- 顺序存储和链式存储的实现,但是近年有考察数据结构概念的趋势
- 纵观信院和前几年的考察方式,要考也是基本上都是简单的一个基操
- 关注顺序和链式之间的优劣,以及实际使用的场景
- 关注假溢出的现象
- 算法题特别喜欢链式结构中(双)指针的考察(重点复习,很可能考!)
顺序存储和链式存储
- 顺序存储:在地址相邻的存储空间中存储线性表各元素,从而通过物理位置的相邻的关系反映相邻的逻辑关系
- 链式存储:每个元素结点包含了数据域和指针域,而链表就是通过指针域来将各个元素结点连接起来的,物理关系是不连续的
*头指针、头节点、首元素结点
- 头指针:指向链表的第一个结点,是访问链表的起点
- 头节点:是一个特殊的结点,指向链表最开始的结点,但是不包含有效数据,旨在方便链表操作
- 首元素结点:是第一个包含元素的结点,头指针指向的结点就是首元素结点
*顺序表和链表在应用上的比较
- 基于空间考虑:顺序表是静态分配的,需要人为预先估计好空间,而链表是动态分配的,不会产生溢出的问题
- 但是从存储密度上来说,链表除了存储数据外还有指针域,存储密度为50% 相比于顺序表的100%来说较占用内存空间的
- 基于时间考虑:顺序表是随机存取的结构,读取任一元素仅需O(1)的时间复杂度,但是链表则需要一次遍历才可完成,时间复杂度是O(n)
- 但是对于插入和删除的操作来说,链表只需修改指针,而顺序表需要移动平均表长一半的元素
- 基于语言考虑:对于没有提供指针的语言来说,也可以用游标来实现静态链表,插入和删除依旧很方便。
- 但是对于有指针语言来说,静态链表在线性表长度不变,而需要改变线性表之间关系时,静态链表也有用武之地。
*答疑复盘
- Alice在频繁需要删除最后一个结点的情况下选用了带尾指针的单向循环链表作为存储结构,为什么不行?
- Alice忽略了删除是需要前驱结点的指针的,要用到指针保留(pre)技术
- yuezi还是建议用双向链表,这样就可以访问前驱结点啦
- Bob删除链表的结点用了这样一个操作:p->next = p->next->next; free(p->next); 为什么不对?
- Bob 粗心大意,忽略了p->next的变更,实际上,他free的结点是p->next->next
- yuezi建议老老实实开个新结点s保存p->next,免得到时候free的是啥都忘了。。
3 栈和队列
-
因为他们本质上就是受限的链表,考察侧重于考察他们的概念和基本判空判满最基本的东西
-
又因为栈和递归能联系到一起,所以在递归上会有文章, 但是命题人的要求止步于让你分析递归过程
-
代码题侧重于队列的入队 出队操作,以及栈的应用(基本操作)。
如何让多个栈共享空间且优缺点
- 使用共享栈技术,即栈底位置不变,而栈顶位置动态变化
- 优点:节省空间、空间的灵活性高
- 缺点:涉及大量指针操作,复杂、性能差
*栈 队列 串具体是怎么限定的
栈:用户只能在输入端进行插入和删除,先进后出的特性
队列:用户只能在指定的一端插入元素,在另外一端删除元素,先进先出的特性
字符串:字符串是有限长度的线性表
*栈和队列为什么是线性表
- 线性表是n个类型相同的数据元素的有限序列,除了首元素和尾元素外 其余元素都有前驱和后继
- 而栈限制了输入和输出只能在一端进行,按照先入后出的顺序排列
- 队列限制了输入在一端进行,输出在另外一端进行,按照先入先出的顺序排列
*递归进层和递归出层的三件事
递归进层(i→i +1层)系统需要做三件事:
- 保留本层参数与返回地址
- 为被调用函数的局部变量分配存储区,给下层参数赋值
- 将程序转移到被调函数的人口。
递归退层(i←i +1层)系统也应完成三件工作:
- 保存被调丽数的计算结果
- 释放被调函数的数据区,恢复上层参数。
- 依照被调函数保存的返回地址,将控制转移回调用函数。
4 数组和字符串
什么是主串和子串
- 主串是一个完整的字符串,他包含了零个或多个子串
- 子串是主串的一段连续序列,可以是一个字符也可以是一个序列
*为什么说数组 广义表 串是线性表(推广)
- 线性表是n个类型相同的数据元素的有限序列,除了首元素和尾元素外 其余元素都有前驱和后继
- 数组的元素在内存中是连续存储的,也是存储相同元素序列的载体
- 数组将线性表推广到了二维或者高维,是多个线性关系的组合,本质上其实还是线性表。
- 广义表实质上也是一对一的有限序列,符合线性表特征
- 相比于线性表只有原子元素,广义表中的元素既可以是原子元素,又可以是另外一个子表
- 字符串实质上是多个字符组成的有限序列,符合线性表的特征
- 他的特点是数据元素都是字符
5.树
什么是平衡二叉排序树
- AVL是一颗二叉排序树或者空树,并且具有以下特质:①左右高度差的绝对值小于等于1 ②左右子树也是平衡二叉树
- 算是二叉排序树对不同序列顺序而产生的树不唯一的改进
什么是哈夫曼树
- 哈夫曼树是由n个带权叶子结点构成的最短带权路径 所生成的二叉树,他只有叶子结点和度为2的结点,可以用来构造最优前缀码,使得每个报文对应的二进制串平均查找长度最短
n结点k叉树空链域推导
- nk个链域,有n-1个结点有前驱,所以空链域有(nk- n + 1)个
- 二叉树有 n + 1个,常用于二叉线索树
6.图
怎么判断图是否有环
- 法一:使用深度优先搜索DFS,判断在搜索过程中是否有后向边,即是否存在连接该结点的父节点的边,若没有,则无环
- 法二:使用拓扑排序,将入度为0的顶点依次入栈,而后依次出栈,删除该顶点与和该顶点相连的边,如果又出现了入读为0的顶点,则继续入栈,直到最后栈为空时,判断,出栈的顶点个数是否与原图的顶点个数相同,如果相同,则无环
*怎么判断图联通(vis数组作用)
- 使用深度优先遍历或广度优先遍历,对每个结点用一个vis数组来标识是否访问过,如果遍历完后, 所有的结点对应的vis数组值都为true,即每个结点都访问到了,那么就是连通图
- 为什么要设立vis数组
- 主要目的是标识该结点是否被访问过,因为由于图是多对多的数据结构,即一个结点可能会有多个前驱,和多个后继,所以在遍历的过程中或是在递归回溯的过程中,是存在访问同一个结点的可能性的,为了避免重复的遍历,也同时为了便于找到没有被访问的结点,所以在遍历图的操作中,访问前都会先判定vis数组,如果为true,那么就不再对该结点进行访问操作,如果为false,那么可以进行访问
关键路径是什么 怎么求解 意义何在
- 关键路径,是从源点到汇点的最长路径长度,从而代表整个工程所需要完成的时间,该路径就是关键路径
- 意义:关键路径上的活动是关键活动,这些活动任意一项延期,都会导致整个工程时间的延长,当然,对关键活动的缩短(假设该活动是所有关键路径都经过的活动),那么整个工期也会随之缩短。
求关键路径四大步骤
- 对图中的顶点进行拓扑排序,在排序过程中得到事件的最早发生事件ve(i)
- 通过逆拓扑排序序列求得每个事件的最晚发生事件vl(i)
- 求得每个活动ai的最早开始时间e(i)和最晚发生时间l(i)
- 找出e(i) = l(i) 的活动即为关键活动,关键路径则是关键活动所组成
图两类存储结构
- 邻接表 时间复杂度 有向O(n²) 无向O(n²) ,空间复杂度 有向O(n²) 无向O(n(n-1)/2)
- 邻接矩阵 时间复杂度 有向O(n +e ) 无向O(n + e) ,空间复杂度 有向O(n + e) 无向O(n + e)
*求解两个顶点间最短路径bfs怎么实现?
- 该算法主要是使用了广度优先遍历中“按层递进”的特性,即bfs算法可以从一个顶点出发,通过队列技术来存放顶点以及该顶点到源点的距离,由于其按层递进特性,每一次访问的距离都是逐层递增的,所以,当第一次访问到终点的时候,他所对应的距离就是最短路径。
7. 查找
哈希查找基本思想和解决的两个关键问题
- 思想:关键字K和存储位置p之间建立一个哈希函数H,在创建的时候,根据哈希函数H将关键字k放入存储位置H(k)中,以后需要查找关键字K的时候,只需要通过哈希函数就可以实现直接存取元素
- 两个关键问题:选择什么方式来构造哈希函数、选择什么方法来解决冲突
分块查找的优势是什么
- 分块查找是按索引查找元素。分块查找要求n个区间中块内无序但块间有序,可以将每个块最大的元素作为块的最后一个位置,后续查找的时候,我可以通过k个区间长度来定位该元素在哪个区间内,快间查找可以用折半查找。
8.排序
*选择插入冒泡最好最坏情况稳定性说明
稳定性:对于待排序序列中相同的元素而言,在排序以后其相对的位置顺序保持不变。
对于插入排序
- 最好情况是待排序元素已经有序,此时比较次数 n - 1(共n - 1 躺,每趟排序只比较一次),移动次数为 2(n - 1) (插入排序每次开始会对记录r[i] 放置到哨兵处r[0],找到插入点后,直接放置到原先的位置r[i],共两步),时间复杂度为O(n)
- 最坏情况是待排序元素全部逆序排列,此时比较次数为(1 + 2 + .... + n),移动次数为(1+1, 2+1, 3+1, .... n + 1),时间复杂度为O(n²)
- 而对于改进后的插入排序算法(即在每趟前加了条件判断语句,如果满足条件直接走下一趟),此时在最好情况下元素的移动次数就为0,而最坏情况下的比较次数为(2 + 3 + 4 + ... + n + 1),但总体的时间复杂度不变
- 插入排序是稳定的,因为在判定插入位置时只有r[0] < r[j] 时,才会往前移动插入位置,对于相同的元素是不会改变相对位置的。
对于冒泡排序
- 最好情况是有序,此时元素移动次数为0
- 但元素比较次数与排列情况无关,元素的比较次数一直是(1 + 2 + ... + n -1 )次,所以时间复杂度是O(n²) 空间复杂度O(1)
- 冒泡排序是稳定的,因为在进行交换操作的时候,他是比较a[i] > a[j] 的,对于相同的元素是不会通过交换改变相对位置的。
对于选择排序
- 最好情况是有序,元素移动次数为0
- 最坏情况是逆序,移动元素个数为3(n - 1)次(n - 1躺,每趟进行一次交换)
- 而总的比较次数与排列情况无关,总的比较次数永远是(1 + 2 + 3... + n -1 )次,始终是O(n²)
- 选择排序是不稳定的,反例说明:{3,3*,2},排序后{2,3*,3}。
- 引申不稳定
快速排序什么时候性能最差,如何改进
- 最坏情况是:数组已经排好序,且枢轴元素为最大值或最小值,导致划分后,无法减少各个元素间的逆转数,相当于退化到了选择排序,时间复杂度为O(n²)
- 数组是否排好序无法改变,所以改进思想是想办法让枢轴元素避免为最大值或最小值,两个方案:第一是取第一个元素和最后一个元素,以及他们中间的元素,取这些元素中值为中间的元素作为枢轴元素,第二是随机选取枢轴元素。选取好了枢轴元素后与第一个元素互换即可。
1000个关键字找前10小的元素
- 可以借助堆排序的思想,因为它可以避免对整个元素排序,只需要局部调整堆,具体步骤如下:
- 1.建立10个元素的大顶堆
- 2.从第11个元素开始遍历到第1000个元素,如果他小于等于大顶堆的根结点,则替换为该元素,然后调整堆
- 3.最后得到的大顶堆内的元素即为1000个关键字中最小的十个元素
怎么找到第k小的元素
- 使用分治的思想。随机选择一个枢轴K(为了尽量避免在基本有序的坏情况下,由于直接选取第一个元素为枢轴造成的划分不对称造成的划分次数增加的问题),将小于K的元素放在左边,大于K的元素放在右边
- 因为每一次划分是可以确定该枢轴最终位置的,所以我们可以判断当前K的位置是否为k,如果是,那么就输出该元素,否则,按照上述划分的方法,继续对基准左边的元素进行划分,再对基准右边的元素进行划分,以此下去递归,直到该枢轴元素最终指向第k个位置为止。