写在前面的话:
因为在学习数据结构之前,学习过一年的算法,所以有一些基础,一些我觉得
没必要的代码或知识就没写上,记得多是一些知识点,写的可能对于别人来说
很难接受,望谅解。
我学习算法是在Acwing(直接百度搜Acwing就有官网)上面学的,闫学灿
老师对于我的算法学习帮助很大,有兴趣的同学可以去看看啊,相信你一定
会有收获的。(没有打广告的意思,完全是发自内心)。如果你也在为算法学习
困惑,相信闫学灿老师一定不会让你失望的。
逻辑特征:
在非空的线性表,有且仅有一个开始结点,有且仅有一个终端结点,
其余的内部节点都有且仅有一个直接前驱和一个直接后继
c里面释放空间:free( p )
~~~ p是一个指针类型的变量
c++里面释放空间:delete( p )
~~~ p是一个指针类型的变量
以物理位置相邻表示逻辑相邻关系。
任一元素均可随机存取。
进行插入和删除操作时,需移动大量的元素。存储空间不灵活。
头指针:指向链表中第一个结点的指针
首元结点:指链表中存储第一个数据元素a1的结点
头结点:在链表的首元结点之前附加的一个节点
在链表中设置头结点有什么好处?
头结点的数据与内装的是什么?
可以为空,也可存放线性表长度等等或其他值,自己设置。
广义表
从任意一个结点出发均可找到表中其他结点
链式存储结构的优点:
链式存储结构的缺点:
在原串里寻找匹配串
BF算法(暴力:n^2)
KMP(O(n + m))
利用原串i指针不回退原理,将匹配串的j直接移到下一个可以和i匹配的地方,利用next[j]数组
next数组:非平凡前缀和非平凡后缀的最大长度
推荐博客讲解地址:KMP讲解
线性结构的前驱和后继是唯一的
非线性结构的前驱和后继是不唯一的,
非线性结构:
树形结构:1:n (一个前驱,多个后继)
图形结构:m:n (多个前驱,多个后继)
树是n个结点的有限集合
结点:数据元素以及指向子树的分支
根节点:非空树中无前驱结点的结点
树中结点的度:结点拥有的子树数(相当于后继结点数)
树的度:树内各结点的度的最大值
度为0的结点:叶子结点
度不等于0的结点:非终端结点
根节点以外的分支结点称为内部结点
结点的子树的根称为该结点的孩子,该结点称为孩子的双亲
兄弟节点:有共同的双亲
堂兄弟:双亲不一样,但是双亲在同一层
结点的祖先:从根到该结点所经分支上的所有结点
结点的子孙:以某结点为根的子树中的任一结点
树的深度(高度):树中结点的最大层次
有序树:树中结点的各子树从左至右有次序(最左边的为第一个孩子)
无序树:树中结点的各子树无次序
森林:是m(m>=0)棵互不相交的树的集合
一棵树是一个只有一棵树的森林,把根节点删除就变成了具有多棵树的森林,给森林中的各子树加上一个双亲结点,森林就变成了树。
树一定是森林,森林不一定是树
二叉树的结构最简单,规律性最强
可以证明,所有树都能转为唯一对应的二叉树,不失一般性。
普通树(多叉树)若不转化为二叉树,则运算很难实现
特点
二叉树不是树的特殊情况,它们是两个概念
二叉树结点的子树要区分左子树和右子树,即使只有一棵子树也要进行区分,说明它是左子树,还是右子树。
树当结点只有一个孩子时,就无须区分它是左还是右的次序。因此两者是不同的。这是二叉树与树的最主要的区别。
(也就是二叉树每个结点位置或者说次序都是固定的,可以是空,但是不可以说它没有位置,而树的结点位置是相对于其他别的结点来说的,没有别的结点时,它就无所谓左右了)
表达式求值
利用二叉树求解表达式的值
若表达式为第一操作数 运算符 第二操作数
则相应的二叉树中左子树表示第一操作数,右子树表示第二操作数,根节点的数据域存放运算符(叶子结点存数,叶子节点的双亲结点存运算符)
在二叉树的第i层至多有**2^(i-1)**个结点(i>=1)
第i层至少有1个结点
深度为k的二叉树至多有2^k-1个结点(k>=1)
深度为k的二叉树至少有k个结点
对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0 = n2 + 1
–注意:n0表示度为0的结点数,n1表示度为1的结点数,n2表示度为2的结点数
证明:
总边数为B,n为总的结点数
从下往上看: B = n - 1
从上往下看: B = n2 * 2 + n1 * 1
所以 n = n2 * 2 + n1 * 1 +1 ,又n = n2 + n2 + n0
所以n0 = n2 + 1
满二叉树
一棵深度为k且有(2^k ) - 1个结点的二叉树称为满二叉树
特点:
对二叉树结点位置进行编号
满二叉树在同样深度的二叉树中结点个数最多
满二叉树在同样深度的二叉树中叶子结点个数最多
深度为k的具有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号为1~n的结点一一对应时,称之为完全二叉树。
注:在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一棵完全二叉树。
一定是连续的去掉
特点:
注意:满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树
性质4:具有n个结点的完全二叉树的深度为log2 n(下取整) + 1
性质5:如果对一棵有n个结点的完全二叉树(深度为log2 n(下取整) + 1)的结点按层序编号,从左到右,从上到下编号,则对任一结点,有:
二叉树的顺序存储结构
实现:按满二叉树的结点层次编号,依次存放二叉树中的数据元素
二叉树的链式存储结构
二叉树的链式存储结构可以由二叉链表实现也可以由三叉链表实现
二叉链表(结点存储值,左右儿子的地址)
在n个结点的二叉链表中,有 n + 1 个 空指针域
分析:必有2*n个链域。除根节点外,每个结点有且仅有一个双亲,所以只会有n-1个结点的链域存放指针,指向非空子女结点。
空指针数目 = 2 * n - (n - 1) = n + 1
三叉链表(结点存储值,左右儿子的地址,指向前驱的地址也就是双亲结点双亲只有一个)
若规定先左后右,则只有前三种情况:
DLR——先(根)序遍历
LDR——中(根)序遍历
LRD——后(根)序遍历
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根
根据遍历序列确定二叉树
由先序序列和中序序列求二叉树
分析:
~~~~~~ 由先序序列确定根;由中序序列确定右子树。
由中序序列和后序序列求二叉树
分析:
~~~~~~ 后序遍历,根结点必在后序序列尾部
二叉树的先序遍历算法
二叉树的中序遍历算法
二叉树的后序遍历算法
遍历二叉树的非递归算法
中序遍历非递归算法
二叉树的层次遍历
由二叉树的前序遍历序列构建二叉树(叶子节点的儿子节点如果给出null的话,可以唯一确定)
二叉树的复制(与先序遍历很像)
计算二叉树的深度
计算二叉树的结点总数
利用二叉链表的空指针域:
~~~~~ 如果某个结点的左孩子为空。则将空的左孩子指针域指向其前驱;如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继
~~~~~ —这种改变指向的指针称为**“线索”**
~~~~~ 加上了线索的二叉树称为线索二叉树,对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化
树的存储结构
双亲表示法
实现:定义结构数组,存放树的结点,每个结点含两个域
特点:找双亲容易,找孩子难。
特点:找孩子容易,找双亲难
实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟节点
将树转换成二叉树
树变二叉树:兄弟相连留长子
将二叉树转换成树
加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子······沿分支找到的所有右孩子,都与p的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线
调整:将结点按层次排列,形成树结构
二叉树变树;
左孩右右连双亲;
去掉原来右孩线。
森林转换成二叉树(二叉树与多棵树之间的关系)
二叉树转换成森林
二叉树变森林:
去掉全部右孩线,孤立二叉再还原
将森林看成由三部分构成:
若森林不空,则
即:依次从左至右对森林中每一棵树进行先根遍历。
即:依次从左至右对森林中每一棵树进行后根遍历。
判断树:用于描述分类过程的树
哈夫曼树(最优二叉树)
哈夫曼树的基本概念
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。
结点的路径长度:两结点间路径上的分支数。
树的路径长度:从树根到每一个结点的路径长度之和。
结点数相同的二叉树中,完全二叉树是路径长度最短的二叉树。
但是路径最短的二叉树不一定是完全二叉树。
权:将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。
树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和。
哈夫曼树:最优树
带权路径长度(WPL)最短的树
”带权路径长度最短“是在”度相同“的树中比较而得的结果,因此有最优二叉树、最优三叉树之称等等。
哈夫曼树:最优二叉树
带权路径长度(WPL)最短的二叉树
满二叉树不一定是哈夫曼树
哈夫曼树中权越大的叶子离根越近
具有相同带权结点的哈夫曼树不唯一
哈夫曼算法(构造哈夫曼树的方法)
构造森林全是根
选用两小造新树
删除两小添新人
重复2、3剩单根
哈夫曼树的结点的度数为0或2,没有度为1的结点
包含n个叶子结点的哈夫曼树中共有2*n-1个结点
包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生n-1个新结点,产生的n-1个新结点都是度为2的点
总结:
可见:哈夫曼树中共有n+n-1=2n-1个结点,且其所有的分支结点的度均不为1。
哈夫曼编码
关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀
图:G = (V,E)
V :顶点(数据元素)的有穷非空集合
E:边的有穷集合
无向图
有向图
完全图:任意两个点都有一条边相连
无向完全图:n个顶点,n(n-1)/2条边
有向完全图:n个顶点,n(n-1)条边
稀疏图:有很少边或弧的图(e < nlogn)。
稠密图:有较多边或弧的图。
网:边/弧带权的图。
邻接:有边/弧相连的两个顶点之间的关系。
~~~~~~ 存在(vi,vj),则称vi和vj互为邻接点。
~~~~~~ 存在
关联(依附):边/弧与顶点之间的关系。
~~~~~~ 存在
顶点的度:与该顶点相关联的边的数目,记为TD(v)
在有向图中,顶点的度等于该顶点的入度与出度之和。
顶点v的入度是以v为终点的有向边的条数,记作ID(v)
顶点v的出度是以v为始点的有向边的条数,记作OD(v)
问:当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是何形状?
答:是树!而且是一棵有向树!
路径:接续的边构成的顶点序列。
路径长度:路径上边或弧的数目/权值之和。
回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
连通图(强连通图)
在无(有)向图G=(V,{E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G是连通图(强连通图)。
连通图是对于无向图来说的
强连通图是对于有向图来说的
子图
设有两个图G =(V,{E})、G1 = (V1,{E1}),若V1是V的子集,E1是E的子集,则称G1是G的子图。
连通分量(强连通分量)
无向图G的极大连通子图称为G的连通分量。
极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。
注意:连通分量是对于无向图来说的,强连通分量是对于有向图来说的。
极大强连通子图意思是:该子图是G的强连通子图,将D的任何不在该子图中的顶点加入,子图不再是连通的。
极小连通子图:该子图是G的连通子图。在该子图中删除任何一条边该子图不再连通。
生成树:包含无向图G的所有顶点的极小连通子图。
生成森林:对非连通图,由各个连通分量的生成树的集合。
邻接矩阵
适合存储稠密图
有边填1,无边填0
无向图的邻接矩阵表示
无向图的邻接矩阵是对称的;
有向图的邻接矩阵表示
网(即有权图)的邻接矩阵表示法
存在边填边权,不存在边记录无穷大
邻接矩阵的缺点:
不利于增加和删除顶点
对于稀疏图来说浪费空间(对于稠密图来说很划算)
浪费时间—统计稀疏图中一共有多少条边
邻接表
适合存储稀疏图
邻接表 逆邻接表
dfs
生成树:所有顶点均由边连接在一起,但不存在回路的图。
一个图可以有许多棵不同的生成树
所有的生成树具有以下特点
最小生成树:给定一个无向网络,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树。
一、单源最短路径:用Dijkstra算法
从未确定最短距离的结点中选取一个距离源点最短的结点,加入已经确定最短距离的集合中,并用此结点更新源点到其他结点的最短距离,直到所有结点都加入到确定距离的集合中去。
二、所有顶点间的最短路径:用Floyd算法
自身到自身距离为0
逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之;否则,维持原值。所有结点试探完毕,算法结束。
有向无环图
排成线性序列
一个结点的前驱(不一定是前驱结点)必须在这个结点之前
根据拓扑排序判断有没有环(spfa是判断负环)
二叉排序树又称为二叉搜索树、二叉查找树
定义:
二叉排序树的性质:
中序遍历非空的二叉排序树所得到的数据元素序列是一个按关键字排列的递增有序序列。
平衡二叉树
哈希
哈希函数:
开放寻址法
拉链法
按数据存储介质: 内部排序和外部排序
按比较器个数: 串行排序和并行排序
按主要操作: 比较排序和基数排序
按辅助空间: 原地排序和非原地排序
按稳定性: 稳定排序和非稳定排序
按自然性: 自然排序和非自然排序
按存储介质可分为:
内部排序:数据量不大、数据在内存,无需内外存交换数据
外部排序:数据量较大、数据在外存(文件排序)
外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂得多
按比较器的个数可分为:
按主要操作可分为:
比较排序:用比较的方法
插入排序、交换排序、选择排序、归并排序
基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置。
按辅助空间可分为:
原地排序:辅助空间用量为O(1)的排序方法。
(所占的辅助存储空间与参与排序的数据量大小无关)
非原地排序:辅助空间用量超过O(1)的排序方法。
按稳定性可分为:
~~~~~~~~~ 排序的稳定性只对结构类型数据排序有意义。
~~~~~~~~~ 排序方法是否稳定,并不能衡量一个排序算法的优劣。
按自然性排序:
按排序依据原则
按排序所需工作量
基本思想:
~~~ 每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。
即边插入边排序,保证子序列中随时都是排好序的
顺序法定位插入位置 —— 直接插入排序
二分法定位插入排序 —— 二分插入排序
缩小增量多遍插入排序 —— 希尔排序
直接插入排序可以使用哨兵,哨兵存储的值为当前需要插入的数值,从后往前找,只要当前结点的值大于哨兵的值,将当前结点前面的数值赋给当前结点,一直循环下去。
时间复杂度结论
直接插入排序 时间复杂度 最好:O(n) 最坏:O(n^2) 平均情况:O(n^2) 空间复杂度:O(1) 稳定性:稳定的
基本思想:
先将整个待排记录序列分割成若干个子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
希尔排序特点
希尔排序算法分析
希尔排序算法效率与增量序列的取值有关
希尔排序是一种不稳定的排序算法
时间复杂度: O(n1.25)~O(1.6n(1.25))
最 好 O ( n ) 最好O(n) 最好O(n)
最 坏 O ( n 2 ) 最坏O(n^2) 最坏O(n2)
—基于简单交换思想
基本思想:每趟不断将记录两两比较,并按“前小后大”规则交换
当前一个数比后一个数大时,交换;
这样的话第一遍循环结束之后最大的数在最后一位,第二遍循环之后倒数第二大的数在倒数第二位·······
冒泡排序的算法评价
基本思想
任取一个元素(如:第一个)为中心
所有比它小的元素一律前放,比它大的元素一律后放
形成左右两个子表;
对各子表重新选择中心元素并依此规则调整
直到每个子表的元素只剩一个
递归思想
时间复杂度
空间复杂度
快速排序不是原地排序
稳定性
快速排序是一种不稳定的排序方法。
快速排序算法分析
基本思想:在待排序的数据中选出最大(小)的元素放在其最终的位置。
基本操作:
算法稳定性
时间复杂度:
最好:O(n^2)
最坏:O(n^2)
平均:O(n^2)
堆的定义:
小根堆
a[i]<=a[2*i]
a[i]<=a[2*i+1]
大根堆
a[i]>=a[2*i]
a[i]>=a[2*i+1]
从堆的定义可以看出,堆实质是满足如下性质的完全二叉树;二叉树中任一非叶子结点均小于(大于)它的孩子结点
实现堆排序需解决两个问题:
第二个问题:
时间复杂度:O(nlogn)
空间复杂度:O(1)
堆排序是一种不稳定的排序方法
基本思想:将两个或两个以上的有序子序列“归并”为一个有序序列。
在内部排序中,通常采用的是2-路归并排序。
时间效率:O(nlogn)
空间效率:O(n)
稳定性:稳定
基本思想:分配+收集
也叫桶排序或箱排序:设置若干个箱子,将关键字为k的记录放入第k个箱子,然后再按序号将非空的连接。
基数排序:数字是有范围的,均有0~9这十个数字组成,则只需设置十个箱子,相继按个、十、百…进行排序。
时间效率:O(k(n+m))*
空间效率:O(n+m)
稳定性:稳定
—、 时间性能
二、空间性能
指的是排序过程中所需的辅助空间大小
三、排序方法的稳定性能
四、关于“排序方法的时间复杂度的下限“
本章讨论的各种排序方法,除基数排序外,其他方法都是基于“比较关键字”进行排序的排序方法,可以证明,这类排序法可能达到的最快的时间复杂度为O(nlogn)
(基数排序不是基于“比较关键字”的排序方法,所以它不受这个限制)。
可以用一棵判定树来描述这类基于“比较关键字”进行排序的排序方法。