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