文章目录
数据简要介绍
数据:数据是信息的载体。
数据元素:数据元素是数据的基本单位,一个数据元素可以由若干个数据项组成。数据项是构成数据元素不可分割的最小单位。比如,学生记录是一个数据元素,包含学号、姓名等数据项。
逻辑结构:一般可以分为线性结构和非线性结构,线性结构包含:线性表、栈和队列、串、数组等;非线性结构有集合、树形结构和图状结构。树形结构又包含一般树和二叉树,图状结构又包含有向图和无向图。
存储结构:顺序存储、链式存储、索引存储、散列存储
数据运算
算法特征
算法的五大特征:有穷性、确定性、可行性、输入、输出
会计算程序算法的时间复杂度和空间复杂度。
线性表
线性表:具有相同数据类型的n(n≥0)个数据元素的有限序列。
特点:除第一个元素以外,每个元素都只有一个直接前驱;除最后一个元素以外,每个元素都只有一个直接后继。
线性表的顺序存储
建立顺序表:可以建立静态表,比如借助数组开辟一段连续的空间大小存储数据;还可以建立动态表,建立一个动态分配数组的指针,比如使用malloc函数。动态分配并不是链式存储,其本质依然是属于顺序存储结构,只是分配的空间大小是可以在运行时决定的。
顺序表插入的流程:假设目前有一个长度为n的线性表,现在要在第i个位置插入一个值,首先要判断这个i的值是否正确,对于一个顺序表来说,可以插入的i的值为1到当前线性表的长度+1(也就是插到表尾);其次就是要判断一下当前存储空间是否已满,如果已满也无法插入;接着就是将第i个元素及之后的元素向后移动,然后把要插入的元素插入进来,线性表长度+1,END。顺序表的删除操作类似…
顺序表插入操作的性能:在表尾插入,时间复杂度O(1);在表头插入,时间复杂度为O(n);平均情况下,顺序表插入一个节点移动的平均次数为n/2;因此顺序表插入算法的平均时间复杂度为O(n)。
顺序表删除操作的性能:删除表尾元素,时间复杂度O(1);删除表头元素,时间复杂度为O(n);平均情况下,顺序表删除一个节点移动的平均次数为n-1/2;因此顺序表删除算法的平均时间复杂度为O(n)。
线性表的顺序存储的存取是随机存取,其时间复杂度是O(1),因为建立的是一个顺序结构,所以实际上每个元素之间的相对关系是已知的。
顺序存储的优缺点:优点: 存储密度大,不需要为元素之间的逻辑关系增加额外的存储空间;其次就是随机存取,其可以快速存取表中任一位置的元素;**缺点:**插入和删除操作需要移动大量数据;对存储空间要求很高,会产生大量的“碎片”空间。
线性表的链式存储
线性表的顺序存储下的插入和删除比较消耗时间,可以考虑采用链式存储的方式来解决这一问题。
线性表的链式存储:通过一组任意的存储单元来存储线性表中的数据元素,为了建立起线性表中各个元素之间的关系,对于每个链表的节点除了存放自身的信息之外,还要存放一个指向其后继的指针。
搞清楚头结点和头指针的区别。
单链表的基本操作
双链表的基本操作:双链表是在单链表的基础上又新增了一个指针
循环单链表、循环双链表
静态链表:链表不一定只能用指针来实现,还可以使用结构体数组来实现。数组第一个元素不存储数据,其指针域(反映元素下标)存储的第一个元素所在的数组下标。
栈和队列
栈(Stack):只允许一端进行插入或删除操作的线性表。Last in First Out (LIFO)
顺序栈、共享栈、链式栈
队列:First in First Out (FIFO)
顺序队列:队列的顺序存储结构 front、rear分别是队首和队尾对应的数组下标
循环队列: 队满条件:(rear+1)%Maxsize == front ;
队列中元素个数:(rear - front + Maxsize )%Maxsize
链式队列:队列的链式存储结构
双端队列
栈的应用
栈的应用:
-
括号匹配问题
-
表达式求值: 中缀表达式(也就是我们常见的书写运算式的方式,比如(5+3)*2)和后缀表达式5 3 + 2 *,后缀表达式也叫逆波兰式,没有括号,所有的符号都在要运算的数字后面。计算机之所以比较喜欢后缀表达式,主要是因为计算机可以通过栈来计算后缀表达式的值。中缀表达式转换为后缀表达式也可以通过栈来实现。
-
递归思想
3.1 求斐波那契数列的第n项:n=0,值为0;n=1,值为1;n>1,值为fib(n-1)+fib(n-2)
矩阵
矩阵:
矩阵的压缩:按照特定的行优先或者列优先的方式,对于某些特殊形式的矩阵而言,是可以找到下标ai,j与数组中第k个元素个数的关系的。比如对称矩阵、三角矩阵、三对角矩阵(对于n阶方阵A中的任一元素ai,j而言,当下标之差的绝对值>1时则该元素为0)
稀疏矩阵:对于某个维度较大的二维矩阵而言,只有部分元素是非0,其余都是0,那么这样构建一个二维数组占用太多空间,因此可以通过三元组的方式存储非0元素,所谓三元组就是存放非0元素对应的行下标、列下标、数值;但是这样做就失去了随机存取的优点。
树和二叉树
树:一对多
树中结点的度数:结点的度等于结点拥有的子树的数量。树的度等于树中所有结点的度数的最大值。
树的性质:
树中的结点数等于树中所有结点度数加1.
度为m的树中第i层上至多有 m i − 1 m^{i-1} mi−1个结点(i≥1)
高度为h的m叉树至多有 ( m h − 1 ) / ( m − 1 ) (m^h-1)/(m-1) (mh−1)/(m−1)个结点(证明需要利用上述第二个性质)
具有n个结点的m叉树的最小高度为 l o g m ( n ( m − 1 ) + 1 ) log_m(n(m-1)+1) logm(n(m−1)+1)(向上取整)
树的存储结构,首先是顺序存储结构,可以借助双亲表示法:每个结点除了包含有自身的数据之外,还包含有其对应的双亲结点的下标。双亲表示法可以根据parent的值找到该结点的双亲结点,时间复杂度为O(1)。
树的链式存储结构:孩子表示法、孩子兄弟表示法
二叉树:
性质:非空二叉树上的叶子结点数等于度为2的结点数加1。
非空二叉树上第K层至多有 2 k − 1 2^{k-1} 2k−1个结点
高度为H的二叉树至多有 2 H − 1 2^{H}-1 2H−1个结点
具有N个结点的完全二叉树的高度为 l o g 2 ( N + 1 ) log_2(N+1) log2(N+1)(向上取整)
顺序存储和链式存储:使用顺序存储,为了保证二叉树的逻辑结构,将非完全二叉树也当作完全二叉树来处理,因此需要开辟很大的内存空间;所以考虑采用链式存储的结构,每个结点除了数据域之外还包含两个指针域,分别指向该结点的左右孩子指针,从而构建出二叉链表,还可以增加指针指向该结点的双亲结点,此时该链表叫做三叉链表。
哈夫曼树和哈夫曼编码
哈夫曼树和哈夫曼编码:
哈夫曼树:含有N个带权叶子结点的二叉树中,其中带权路径长度(WPL,也就是每个叶子节点所带的权值与根结点到叶子结点的路径长度的乘积)最小的二叉树,也称为最优二叉树。
要掌握如何构建一颗哈夫曼树,并求其加权路径长度WPL比如给定一个权集w={5,7,2,3,8,9},构造一个关于w的哈夫曼树
每个初始结点最终都成为叶子结点,并且权值越小的结点到根节点的路径长度越大;构造过程中新建了N-1个结点,因此哈夫曼树中结点总数是2N-1;由于每次构造都会选择2棵树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点。
哈夫曼编码
树、二叉树以及森林之间的相互转换:首先回想树的孩子兄弟表示法,以及二叉树的二叉链表表示法,将这两者结合在一起,将孩子兄弟表示法理解为二叉链表,那么就会将树转换为一个二叉树。同时要掌握树转换为二叉树的方法;
森林转换为二叉树,森林实际上是由多个树组成的,先把森林中的每个树利用上述方法转换为二叉树,然后依次将后续二叉树作为之前二叉树的根结点的右子树。
树的遍历方式:只有先序遍历和后序遍历
一棵树的先序遍历等于其对应的二叉树的先序遍历,一棵树的后序遍历等于其对应的二叉树的中序遍历。
森林的遍历方式:只有先序遍历和后序遍历 。其遍历方式就是对森林中的每一棵树都进行对应的遍历方式。
森林的先序遍历等于其对应的二叉树的先序遍历,森林的后序遍历等于其对应的二叉树的中序遍历。
图
图
图是由顶点集V和边集E组成,记作G=(V,E),其中V(G)表示图G中顶点的有限非空集,|V|表示图G中顶点的个数,也叫做图G的阶。E(G)表示的是图G中顶点之间的边集合,用|E|表示图G中边的条数。
图不可能为空,即使边集为空,那么点集也一定不为空。
理解完全图的概念,完全图就是图中每两个结点之间都有边,根据是否有向分为有向完全图和无向完全图。对于n个顶点而言,有向完全图中有n(n-1)条边,无向完全图中有n(n-1)/2条边。
图的存储结构:
顺序存储:采用一维数组来存储顶点,采用邻接矩阵的方式来存储边;但是对于这种顺序存储方式,如果对于一个稀疏图来说,也就是说顶点数目很多,但是边数很少,此时通过顺序存储的方式就会很浪费空间资源。因此可以考虑采用链式存储的方式。
链式存储:采用邻接表来存储有向图,一列是顶点表的顶点,一列是单链表的结点。有向图的邻接表更关心有向图的出边问题,通过其可以很容易找到各个顶点对应的出边,但是如果考虑该顶点的入度问题就需要遍历整个图。这是邻接表存储有向图的一个缺陷。因此继续改进,可以考虑采用十字链表。
十字链表在邻接表的基础上进行优化,不仅包含了邻接表本身就包含的出度结点,同时也包含了入度结点的信息。十字链表的存储形式不唯一。
邻接表存储的无向图中查找顶点比较容易,但是如果要修改图中的边或者查询边那么就需要遍历链表 。这是邻接表存储无向图的一个缺陷。仿照十字链表,对邻接表的边表进行改造,就可以得到专门针对于存储无向图的邻接多重表。
图的遍历
图的遍历: 广度优先遍历(BFS: Breadth-First-Search )广度优先遍历类似于树的层序遍历算法。其对应的空间复杂度:BFS需要借助一个队列,n个顶点均需要入队一次,所以最坏情况下n个顶点都在队列,因此所需要的空间复杂度就是O(|V|). |V|表示的就是这个图中的顶点数目。 时间复杂度:对于邻接表来说,每个顶点都入队一次,时间复杂度为O(|V|)。对于每个顶点而言,需要搜索其邻接点,也就是需要访问这个顶点的所有边,其对应的时间复杂度是O(|E|), 所以总的时间复杂度为O(|V|+|E|); 对于邻接矩阵而言,每个顶点都入队一次,时间复杂度为O(|V|)。对于每个顶点而言,需要搜索其邻接点,时间复杂度为O(|V|),所以总的时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
BFS的应用:BFS能够解决单源非带权图最短路径问题,按照距离由近到远来遍历图中每个顶点。
广度优先生成树:如果图是用邻接矩阵存储的,广度优先生成树唯一;如果是用邻接表存储的,广度优先生成树不唯一。
图的遍历: 深度优先遍历(DFS: Depth-First-Search )深度优先遍历类似于树的先序遍历算法。其对应的空间复杂度:DFS是一个递归算法,因此需要一个工作栈来辅助工作,最多需要图中所有顶点都入栈,因此空间复杂度是O(|V|);时间复杂度:对于邻接表而言,遍历过程的主要操作是对顶点遍历其邻接点,由于通过访问边表来查找邻接点,所以时间复杂度为O(|E|),访问顶点时间为O(|V|),所以总的时间复杂度为O(|V|+|E|); 对于邻接矩阵而言,查找每个顶点的邻接点的时间复杂度为O(|V|)。对于每个顶点而言都需要搜索其邻接点,时间复杂度为O(|V|),所以总的时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
深度优先生成树:对于一个连通图而言,可以转换为一个深度优先生成树,但对于一个非连通图而言,生成的可能是一个深度优先森林。
图的应用:最小生成树
图的应用:最小生成树
对于连通图的一个生成树而言,是一个极小的连通子图,包含图中全部的顶点,但只有足以构成一棵树的n-1条边。但是对于一个不带权的连通图的生成树是不唯一的。后续讨论关于带权连通图得到最小生成树的生成方式。
算法一:普利姆Prim算法:需要维护两个数组,lowcost[n](当前生成树的最小权值) adjvex[n](为了找到两个顶点之间的边) n表示的是图中的顶点数。
流程:具体流程见video讲解。
普利姆算法采用的是双重循环的方式,外层循环次数是n-1,n表示的是顶点数目,内层并列的两个循环次数均为n,因此prim算法时间复杂度是 O ( n 2 ) O(n^2) O(n2),并且只与n有关(顶点),因此适合稠密图(边多顶点少)。
算法二:克鲁斯卡尔算法: 从图中的边的角度入手,找最小权重的边,直到找到n-1条边。 将图中边按照权值从小到大排列(可采用堆排序的手段),然后从最小的边开始扫描,设置一个边的集合来记录,如果该边并不构成回路(可采用并查集的手段)的话,那么该边并入当前生成树。直到所有的边都检测完为止。
克鲁斯卡尔算法操作分为对边的权值进行排序,以及一个单重for循环,这两者是并列的关系,由于排序耗时大于单重循环时间,因此克鲁斯卡尔算法的主要耗时都在排序上,排序和边的数量有关,因此克鲁斯卡尔算法更加适合稀疏图。
图的应用:最短路径
图的应用:最短路径
图的最短路径求解包括非带权图和带权图;迪杰斯特拉算法考虑的是一个源点到其他顶点的最短路径;弗洛伊德算法考虑的是所有顶点到所有顶点的最短路径问题。
迪杰斯特拉算法的时间复杂度:迪杰斯特拉算法的核心部分在于一个双重循环,迪杰斯特拉算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),其中n表示的是图中的顶点数。**注:**迪杰斯特拉算法不能用于权值有负数的图,否则会报错。
弗洛伊德算法:可以求解图中任意一对顶点之间的最短路径。弗洛伊德算法的核心是一个三重循环,所以时间复杂度是 O ( n 3 ) O(n^3) O(n3),其中n表示的是顶点数。
图的应用:拓扑排序
图的应用:拓扑排序
AOV网:试想一个考研的流程图,把每个环节看作是图中的一个顶点,在这样一个有向图中,用顶点表示活动,用弧表示活动之间的优先关系,那么这样的有向图就叫做AOV网。也就是说,只有弧尾完成才能进入弧头的项目。AOV网实际上反映的是工程中各个活动之间的制约关系。
AOV网是不能有回路的,这种有向无环图也叫做DAG网。
拓扑序列是对图中所有的顶点,如果存在一条从顶点A到顶点B的路径,那么在排序中顶点A要出现在顶点B的前面。
拓扑排序是对一个有向图构造拓扑序列的过程,构造的结果有两种,如果此图的全部顶点都被输出了,那么就是不存在回路的AOV网;如果没有输出全部顶点,那么就说明这个图有回路,不是AOV网。(一个DAG图的拓扑序列不唯一,但是当活动排成线性的,那么拓扑序列就是唯一的)
拓扑排序对于AOV图需要打印图中所有的顶点,而且由于要“删除边”(实际上并没有删除,只是为了寻找入度为0的顶点),所以也需要对所有边进行扫描,因此这个算法的时间复杂度就是O(|V|+|E|)。
对于一个一般的图而言,如果其邻接矩阵是三角矩阵,那么就存在拓扑序列,反之则不一定成立。也就是说存在拓扑序列的图,其邻接矩阵不一定是三角矩阵。
图的应用:关键路径
图的应用:关键路径
AOE(Activity on Edge)网:在一个表示工程的带权有向图中,用顶点表示的是事件,用有向边表示的是活动,用边上的权值表示的是活动持续事件。 AOE网可以用来分析工程的最少所需时间,或者是为了缩短工期,需要找出哪些活动是需要加快的。
关键活动的最早和最晚发生时间是相同的。