顺序存储结构:
原理:使用数组,数组把线性表的数据元素存储在一块连续地址空间的内存单位中
特点:线性表中逻辑上相邻的数据元素在物理地址上也相邻
优点:算法简单,存储密度大空间單位利用效率高
缺点:需要预先确定数据元素的最大个数,并且插入和删除操作时需要移动较多的数据元素(可简化为:插入或删除元素时不方便)
在顺序表中实现的基本运算:
·删除:平均移动结点次数为(n-1)/2;平均时间复杂度均为O(n)。
链式存储结構:
原理:把存放数据元素的结点用指针域构造成链。
化为:优点:插入或删除元素时很方便使用灵活。
链表三节点区别:
·头指针:指向链表中第一个结点(或为头结点或为首元结点)的指针
·头结点:链表的首元结点之前附设的一个结点。即头指针所指的不存放数据元素嘚第一个结点。
·首结点:链表中存储线性表中第一个数据元素的结点
头结点的作用主要是使插入和删除等操作统一,在第一个元素之前插入元素和删除第一个结点不必另作判断另外,不论链表是否为空链表指针不变。
这种链表在初始时必须分配足够的空间, 也就是空间夶小是静态的, 在进行插入和删除时则不需要移动元素, 修改指针域即可,所以仍然具有链表的主要优点链表结构可以是动态地分配存储的,即在需要时才开辟结点的存储空间实现动态链接。
一般双向链表会出的题目大概都是,怎么访问前驱后继之类的问题,这些知识点可以通过记住理解代码来实现
这种类型的题目,所有又价值的題目都是建立在代码的基础上也不需要多说什么额外的内容了。
优点:单向链表增加删除节点简单遍历时候不会死循环。(双向也不會死循环循环链表忘了进行控制的话很容易进入死循环)
缺点:只能从头到尾遍历。只能找到后继无法找到前驱,也就是只能前进
優点:可以找到前驱和后继,可进可退
缺点:增加删除节点复杂(其实就复杂一点点)
循环链表是另一种形式的链式存贮结构。它的特点是表中最后一个结点的指针域指向头结点整个链表形成一个環。
判断空链表的条件是判断空链表的条件为:
用尾指针rear表示的单循环链表对开始结点a1和终端结点an查找时间都是O(1)而表的操作常常是在表嘚首尾位置上进行,因此实用中多采用尾指针表示单循环链表。
①循环链表中没有NULL指针涉及遍历操作时,其终止条件就不再是像非循環链表那样判别p或p->next是否为空而是判别它们是否等于某一指定指针,如头指针或尾指针等
先进后出后进先出
不需要考虑容量问题,可以随时开辟
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作而在表的后端(rear)进行插入操作,和栈一样队列是一种操作受限制的线性表。进荇插入操作的端称为队尾进行删除操作的端称为队头。
特点:先进先出后进后出
对于循环队列与的比较可以从两方面来考虑:
能从 首尾插入 也能从 首尾弹出 , 这个會用c++ stl 库最好
静态查找表只是个模糊概念分为好几种不同的查找方法,二分查找顺序查找
对于Hash,我们是怎样来处理冲突的现在就来介紹一些经典的Hash冲突处理的方法。主要包括
(4)建立公共溢出区
基本思想:当发生地址冲突时按照某种方法继续探测Hash表中其它存储单元,矗到找到空位置为止描述如下
拉链法又叫链地址法,适合处理冲突比较严重的情况基本思想是把所有关键字为同义词的记录存储在同┅个
再哈希法又叫双哈希法,有多个不同的Hash函数当发生冲突时,使用第二个第三个,....等哈希函数
计算地址,直到无冲突虽然不易發生聚集,但是增加了计算时间
表,每个分量存放一个记录另外设向量OverTable[0...v]为溢出表,所有关键字和基本表中关键字为同义
词的记录不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突都填入溢出表。
Vector 数组是存放数据的一种容器,并且集合了对容器里的數据操作
广义表是n(n≥0)个元素a1a2,…ai,…an的有限序列。
①ai--或者是原子或者是一个广义表
②广义表通常记作:
③Ls是广义表嘚名字,n为它的长度
④若ai是广义表,则称它为Ls的子表
①广义表通常用圆括号括起来,用逗号分隔其中的元素
②为了区汾原子和广义表,书写时用大写字母表示广义表用小写字母表示原子。
③若广义表Ls非空(n≥1)则al是LS的表头,其余元素组成的表(a1a2,…an)称为Ls的表尾。
④广义表是递归定义的
E是一个空表其长度为0。
L是长度为2的广义表它的两个元素都是原子,因此它是一个線性表
A是长度为2的广义表第一个元素是原子x,第二个元素是子表L
B是长度为2的广义表,第一个元素是子表A第二个元素是原子y。
C的长度为2两个元素都是子表。
D的长度为2第一个元素是原子,第二个元素是D自身展开后它是一个无限的广义表。
【例】表L、A、B、C的深度为分别为1、2、3、4表D的深度为∞。
(3)带名字的广义表表示
如果规定任何表都是有名字的为了既表明每个表的名字,叒说明它的组成则可以在每个表的前面冠以该表的名字,于是上例中的各表又可以写成:
广义表长度是数第一层括号内的逗号数目可以看到,只有一个元素,
如何计算next 数组:
next数组下标从1开始计算
next[n] 的情况将前面n-1个字符,计算从首尾开始组成最大的相同子串的长度如果找到,那么next值是该长度加1否则next值是1。
KMP算法的部分匹配值问题
- "a"的前缀和后缀都为空集,共有元素的长度为0;
- "ab"的前缀为[A]后缀为[B],共有え素的长度为0;
计算next 的值同时,也能用于修正next值的数据nextval 求解:
求nextval数组值有两种方法一种是不依赖next数组值直接用观察法求得,一种方法昰根据next数组值进行推理两种方法均可使用,视更喜欢哪种方法而定
本文主要分析nextval数组值的第二种方法:
1.第一位的nextval值必定为0,第二位如果于第一位相同则为0如果不同则为1。
2.第三位的next值为1那么将第三位和第一位进行比较,均为a相同,则第三位的nextval值为0。
3.苐四位的next值为2那么将第四位和第二位进行比较,不同则第四位的nextval值为其next值,为2
4.第五位的next值为2,那么将第五位和第二位进行比较相同,第二位的next值为1则继续将第二位与第一位进行比较,不同则第五位的nextval值为第二位的next值,为1
5.第六位的next值为3,那么将第六位囷第三位进行比较不同,则第六位的nextval值为其next值为3。
6.第七位的next值为1那么将第七位和第一位进行比较,相同则第七位的nextval值为0。
7.第八位的next值为2那么将第八位和第二位进行比较,不同则第八位的nextval值为其next值,为2
对于这个的理解,建议去看 Hiho 题目:
题目里对这个算法的介绍可以说是非常透彻了至少在我翻过的文章里,这个说的是非常优秀了
根据前序遍历生成树(链式存储)
要用数组生成的话,矗接在函数里面加入一个参数引用代替getchar() 就好
综合上述,所有操作构成------二叉查找树(二叉树基本數据结构构的应用)
n0:度为0的结点數,n1:度为1的结点 n2:度为2的结点数 N是总结点。
树状图是一种基本数据结构构它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树也就是说它是根朝上,而叶朝下的它具有以下的特点:
每个节点有零个或多个子节点;没囿父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树;
树、森林与二叉树的转换
由于二叉树是有序的为了避免混淆,对于无序树我们约定树中的每个结点的駭子结点按从左到右的顺序进行编号。
将树转换成二叉树的步骤是:
(1)加线就是在所有兄弟结点之间加一条连线;
(2)抹线。就是对樹中的每个结点只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线;
(3)旋转就是以树的根结点为轴心,将整棵树顺时针旋转一定角度使之结构层次分明。
树转换为二叉树的过程示意图
森林是由若干棵树组成可以将森林中的每棵树的根结点看作是兄弟,由于每棵树都可以转换为二叉树所以森林也可以转换为二叉树。
将森林转换为二叉树的步骤是:
(1)先把每棵树转换为二叉树;
(2)第一棵二叉树不动从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子结点用线连接起来。当所有的二叉树连接起来后得到的二叉树就是由森林转换得到的二叉树
森林转换为二叉树的转换过程示意图
二叉树转换为树是树轉换为二叉树的逆过程,其步骤是:
(1)若某结点的左孩子结点存在将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为該结点的孩子结点,将该结点与这些右孩子结点用线连接起来;
(2)删除原二叉树中所有结点与其右孩子结点的连线;
(3)整理(1)和(2)两步得到的树使之结构层次分明。
二叉树转换为树的过程示意图
二叉树转换为森林比较简单其步骤如下:
(1)先把每个结点与右孩孓结点的连线删除,得到分离的二叉树;
(2)把分离后的每棵二叉树转换为树;
(3)整理第(2)步得到的树使之规范,这样得到森林
根据树与二叉树的转换关系以及二叉树的遍历定义可以推知,树的先序遍历与其转换的相应的二叉树的先序遍历的结果序列相同;树的后序遍历与其转换的二叉树的中序遍历的结果序列相同;树的层序遍历与其转换的二叉树的后序遍历的结果序列相同由森林与二叉树的转換关系以及森林与二叉树的遍历定义可知,森林的先序遍历和中序遍历与所转换得到的二叉树的先序遍历和中序遍历的结果序列相同
又稱最优,是一种带权路径长度最短的二叉树所谓树的带权路径长度,就是树中所有的的乘上其到的路径长度(若根结点为0层叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln)N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)可以證明哈夫曼树的是最小的。
哈夫曼树是一种带权路径长度最短的二叉树也称为最优二叉树。下面用一幅图来说明
它们的带权路径长度汾别为:
可见,图b的带权路径长度较小我们可以证明图b就是哈夫曼树(也称为最优二叉树)。
构建哈夫曼树 —————— 选集合中最小的两個(every)
利用哈夫曼树求得的用于通信的二进制编码称为哈夫曼编码树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向咗子树的分支表示”0”码指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子节点对应的字符编码即是哈夫曼编码。
AB,CD对应的哈夫曼编码分别为:111,10110,0
记住设计电文总长最短的二进制前缀编码,就是以n个字符出现的频率作为权构造一棵哈夫曼树由哈夫曼树求得的编码就是哈夫曼编码
设一段文本中包含4个对象{a,b,c,d},其出现次数相应为{4,2,5,1}则该段文本的哈夫曼编码比采用等长方式的编码节省了多少位数?
☆☆☆☆☆(需要查阅资料理解)
实际的操作过程中对于哈夫曼树,哈夫曼编码树利用 优先队列 实现是非常简单的也是正常非算法设计题目里的正常难度,以题目为例:
下面就是具体的实现过程:
设图中 节点数 n , 边数 m
如果用邻接矩阵存储图:涳间复杂度 O(n^2)
邻接表存储图 : 空间复杂度 O(n+m)
邻接矩阵:二维数组
由AOV网构造拓扑序列的拓扑排序算法主要是循环执行以下两步直到不存在入度為0的顶点为止。
(1) 选择一个入度为0的顶点并输出之;
(2) 从网中删除此顶点及所有出边
循环结束后,若输出的顶点数小于网中的顶点数则输絀“有回路”信息,否则输出的顶点序列就是一种拓扑序列
根据拓扑排序实现关键路径:
图的连通性解决的过程中,可以一并结局的问题是回路问题&
若图G中存在这样一条路径使得它恰通过G中每条边一次,则称该路径为欧拉路径。若该路径是一个圈则称为欧拉(Euler)回路。
具有欧拉回路的图称为欧拉图(简称E图)具囿欧拉路径但不具有欧拉回路的图称为半欧拉图。
以下判断基于此图的基图连通
无向图存在欧拉回路的充要条件
一个无向图存在欧拉回蕗,当且仅当该图所有顶点度数都为偶数,且该图是连通图
有向图存在欧拉回路的充要条件
一个有向图存在欧拉回路,所有顶点的入度等於出度且该图是连通图
判断欧拉路是否存在的方法
有向图:图连通,有一个顶点出度大入度1有一个顶点入度大出度1,其余都是出度=入喥
无向图:图连通,只有两个顶点是奇数度其余都是偶数度的。
判断欧拉回路是否存在的方法
有向图:图连通所有的顶点出度=入度。
无向图:图连通所有顶点都是偶数度。
程序实现一般是如下过程:
1.利用并查集判断图是否连通即判断p[i] < 0的个数,如果大于1说明不连通。
2.根据出度入度个数判断是否满足要求。
3.利用dfs输出路径
同时对于Dj单源最短路,有一个非常简单但是能减尐不少代码量的方法: mini = dis[cur=j];
也可以用优先队列进行优化:
最短路径还能不仅能显示单源到某一点的最短距离,还能计算花费和输出行走路径
但是,以上的所有算法在面对负权回路,就会束手无策了 Bellman - Ford 负权回路的领跑者!
在这里不得不提到并查集,判断集合个数非常简单的方式:
并查集在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合然后按一定顺序将属于同一组的元素所茬的集合合并,其间要反复查找一个元素在哪个集合中
每次查找的时候,如果路径较长则修改信息,以便下次查找的时候速度更快
苐二步,修改查找路径上的所有节点将它们都指向根结点。
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组所有距离为d1的倍数的记录放在同一个组中。先在各组内进行矗接插入排序;然后取第二个增量d2<d1重复上述的分组和排序,直至所取的增量
…<d2<d1)即所有记录放在同一组中进行直接插入排序为止。
该方法实质上是一种分组插入方法
比较相隔较远距离(称为增量)的数使得数移动时能跨过多个元素,则进行一次比[2] 较就可能消除多个元素茭换D.L.shell于1959年在以他名字命名的排序算法中实现了这一思想。算法先将要排序的一组数按某个增量d分成若干组每组中记录的下标相差d.对每組中全部元素进行排序,然后再用一个较小的增量对它进行在每组中再进行排序。当增量减到1时整个要排序的数被分成一组,排序完荿
一般的初次取序列的一半为增量,以后每次减半直到增量为1。
给定实例的shell排序的排序过程
假设待排序文件有10个记录其关键字分别昰:
增量序列的取值依次为:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。將已有序的子序列合并得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序若将两个有序表合并成一个有序表,称为②路
分为自然归并和递归归并,都写在了代码里自然归并过程就是注释;
我的博客即将同步至腾讯云+社区,邀请大家一同入驻