1)冒泡排序:
从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换,每一轮循环下来 找出剩余未排序数的中的最大数并“冒泡”至顶端。
稳定; 平均时间复杂度:O(n 2)
最好情况:顺序O(n);
最坏情况:逆序O(n2)
好处:稳定,而且可以适用于单向链表----其它排序方法基本不行
2)插入排序:
待排序的依次与已排序序列比较并寻找插入的位置,每次外循环结束后,将当前的数插入到合适的位置。
稳定; 平均时间复杂度:O(n 2)
复杂度应为(n+i) i为逆序对的个数 可知,对基本有序情况下效果好。
所以常常与其它排序方法结合,优化
有折半插入排序,稳定,n^2复杂度
3)希尔排序(缩小增量排序):
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
不稳定;平均时间复杂度:希尔排序算法的时间复杂度分析比较复杂,实际所需的时间取决于各次排序时增量的个数和增量的取值。时间复杂度在O(n ^ 1.3)到O(n ^ 2)之间。
增量不互质时,小增量不起作用
4)选择排序:
从所有记录中选出最小的一个数据元素与第一个位置的记录交换;然后在剩下的记录当中再找最小的与第二个位置的记录交换,循环到只剩下最后一个数据元素为止。
不稳定; 平均时间复杂度:O(n ^ 2)
(数组,交换 ----不稳定,可以稳定但需要额外的时/空 链表/新开)
5)快速排序
从待排序的n个记录中任意选取一个记录(通常选取第一个记录)为分区标准;(pivot 中枢)
把所有小于该排序列的记录移动到左边,把所有大于该排序码的记录移动到右边,中间放所选记录,称之为第一趟排序;
然后对前后两个子序列分别重复上述过程,直到所有记录都排好序。
不稳定; 平均时间复杂度:O(nlogn)
递归规模小的时候可用插入排序优化
有大量重复元素时退化
二路快排、三路快排
6)堆排序:
建立堆,然后删除堆顶元素,同时交换堆顶元素和最后一个元素,再重新调整堆结构,直至全部删除堆中元素。(最大堆)
不稳定; 平均时间复杂度:O(nlogn)
7)归并排序:
采用分治思想,现将序列分为一个个子序列,对子序列进行排序合并,直至整个序列有序。
稳定; 平均时间复杂度:O(nlogn)
缺点:需要额外空间,复制元素需要时间, 在外排序时非常有用
8)计数排序:
思想:如果比元素x小的元素个数有n个,则元素x排序后位置为n+1。
步骤:
找出待排序的数组中最小和最大的元素;
统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
稳定; 时间复杂度:O(n+k),k是待排序数的范围。
适用于取值范围有限,有大量重复元素的
9)桶排序:
步骤:
设置一个定量的数组当作空桶;
寻访序列,并且把记录一个一个放到对应的桶子去;
对每个不是空的桶子进行排序。
从不是空的桶子里把项目再放回原来的序列中。
时间复杂度:O(n+C) ,C为桶内排序时间。
10)基数排序
基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始(LSD,次位优先),依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
时间复杂度:T(n) = O(n * k), 稳定
也可用于处理多关键字的排序
表排序 是一种间接排序方式,当待排数据结构移动较为困难时
算法要考虑具体环境
如排序:数据有什么特征?
包含大量重复元素? ----三路快排
是否大部分数据近乎有序? —插入排序
是否数据的取值范围非常有限? —计数排序
额外的要求? 如稳定?----归并
数据的存储? 链表?----归并
数据量很大,或者内存很小,不足以装载在内存里,需要使用外排序算法。
1 顺序查找
顺序查找也称为线形查找,属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,时间复杂度为O(n)。
2 二分查找
元素必须是有序的,如果是无序的则要先进行排序操作。
也称为是折半查找,属于有序查找算法。用给定值k先与中间结点比较,若相等则查找成功;若不相等,再根据比较结果确定下一步查找哪个子表,递归,直到查找到或查找结束发现表中没有这样的结点。时间复杂度为O(log2n);
3 插值查找
基于二分查找算法,将查找点的选择与最大最小记录比较,改进为自适应选择,可以提高查找效率。当然,插值查找也属于有序查找。时间复杂度为O(log2(log2n))。
对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。
4 斐波那契查找
也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。是根据斐波那契序列的特点对有序表进行分割的。
他要求开始表中记录的个数为某个斐波那契数小1,及n=F(k)-1;将k值与第F(k-1)位置的记录进行比较(即mid=low+F(k-1)-1);
时间复杂度为O(log2n)
5 树表查找
对二叉查找树进行中序遍历,即可得到有序的数列
插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度
—平衡树
6 分块查找
分块查找又称索引顺序查找,它是顺序查找的一种改进方法。
算法思想:将n个数据元素"按块有序"划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";
step1 先选取各块中的最大关键字构成一个索引表;
step2 查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。
7 哈希查找
以空间换时间,可以快速访问任意键的值
一般依赖于:两点之间的最短路径也包含了路径上其它顶点间的最短路径
深度优先和广度优先—更适合与无权图
从起点开始访问所有深度遍历路径或广度优先路径,则到达终点节点的路径有多条,取其中路径权值最短的一条则为最短路径。
单源最短路径Dijkstra
基本思想:每次找到离源点(如1号节点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。
适用于非负权图,求点s到所有定点的最短路径;
基于贪心的策略,
不断加点进行松弛操作;
----大规模数据的处理?
可以使用堆进行优化,(优先队列)
可以使用近似最短;
可以换算法,
可以采用分布式/并行;
可以采用关键节点(类似关键路由)
各顶点之间最短路径Floyd
弗洛伊德算法(解决多源最短路径):时间复杂度o(n3),空间复杂度o(n2);
基本思想:最开始只允许经过1号顶点进行中转,接下来只允许经过1号和2号顶点进行中转…允许经过1~n号所有顶点进行中转,来不断动态更新任意两点之间的最短距离。即求从i号顶点到j顶点只经过前k号点的最短距离。
迪杰斯特拉算法和弗洛伊德算法都属于广度优先算法
Bellman-Ford 算法
Bellman-Ford算法(解决负权边,解决单源最短路径)
主要思想:所有的边进行n-1轮松弛,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边。
换句话说,第1轮在对所有的边进行松弛操作后,得到从1号顶点只能经过一条边到达其余各定点的最短路径长度,第2轮在对所有的边进行松弛操作后,得到从1号顶点只能经过两条边到达其余各定点的最短路径长度,…此外,Bellman-Ford算法可以检测一个图是否含有负权回路:如果经过n-1轮松弛后任然存在dst[e[i]]>dst[s[i]]+w[i].
可以用于判断是否存在从原点可达的负环;
若在某一轮中发现所有边都没有被松弛,可以提前退出。
SPFA算法
SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford队列优化,它是一种十分高效的最短路径算法。
实现方法:建立一个队列,初始时队列里只有起始点s,再建立一个数组记录起始点s到所有点的最短路径(初始值都要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里的点去刷新起始点s到所有点的距离的距离,如果刷新成功且刷新的点不在队列中,则把该点加入到队列,重复执行直到队列为空。
此外,SPFA算法可以判断图中是否有负权环,即一个点的入队次数超过N。
只有当某个顶点U的d【u】发生变化时,从它出发的邻接点v的d【v】才可能改变。
Prim的基本思想是随机选一个顶点出发,选择与其邻接最小代价的边,然后从与此边相连的下个顶点出发重复上述过程直至所有顶点都被访问过。
Kruskal
它的基本思想是按照权值递增顺序依次从剩下未被访问的边中选择边加入集合,如果加入之后构成回路则含弃,直到集合中含有n-1条边。
请比较最小生成树的算法(普里姆算法,克鲁斯卡尔算法)的异同
最小生成树就是–各边上权值之和最小的生成树。
普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法
普里姆算法的基本思想:(简单的说就是一直加点)
取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。添加顶点w的条件为:w 和已在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。
prim算法对于稠密图更合算,kruskal更适合稀疏图
克鲁斯卡尔算法的基本思想:(简单的说就是找不围成圈的最小的边)
考虑问题的出发点: 为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。
具体做法: 先构造一个只含 n 个顶点的子图 SG,然后从权值最小的边开始,若它的添加不使SG 中产生回路,则在 SG 上加上这条边,如此重复,直至加上 n-1 条边为止。
(每次选择最小权边,如果不构成回路,则加入)
什么时候最小生成树唯一?—当带权连通图的任意一个环中所包含的权值均不相同
kruskal在实现过程中需要注意的:
1.选最小权边时 可用最小堆来优化
2.判断是否构成回路,可用并查集
Dijkstra算法与Prim算法的区别
1.prim算法过程:
prim算法是最小生成树算法,它运用的是贪心原理,设置两个点集合,一个集合为要求的生成树的点集合A,另一个集合为未加入生成树的点B。
它的具体实现过程是:
(1):所有的点都在集合B中,A集合为空。
(2):任意以一个点为开始,把这个初始点加入集合A中,从集合B中减去这个点(visited[1]=1)。寻找与它相邻的点中路径最短的点,如后把这个点也加入集合A中,从集合B中减去这个点(visited[pos]=1)。
(3):更新未被访问的节点的dist[]值。
(4):重复上述过程。一直到所有的点都在A集合中结束。
2.dijkstra算法过程:
(1)初始时,S只包含源点v,即S=v。U包含除v外的其他顶点,U中顶点u距离为边上的权(若v与u有边)或(若u不是v的出边邻接点)。
(2)从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
(3)以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u(u U)的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
(4)重复步骤(2)和(3)直到所有顶点都包含在S中。
小结
1:Prim是计算最小生成树的算法,Dijkstra是计算最短路径的算法,
2、都是使用贪婪和线性规划,每一步都是选择权值/花费最小的边。
贪婪:一个局部最有解也是全局最优解;
线性规划:主问题包含n个子问题,而且其中有重叠的子问题。
prim算法和Dijkstra算法的实现基本相同,关键在于数组d[]的定义不同,在Dijkstra算法中定义为起点s到vi的最短距离,在prim算法中定义为集合s与vi的最短距离。
(抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。)
继承:继承是从已有类得到继承信息创建新类的过程。提供继承的类叫父类(超类、基类)、得到继承的类叫子类(派生类)。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。实现多态需要做两件事:
1). 方法重写(子类继承父类并重写父类中的方法);
2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)