精华内容
下载资源
问答
  • 各种常用排序算法时空复杂度汇总 如图

    各种常用排序算法时空复杂度汇总

    如图

    在这里插入图片描述

    展开全文
  • 各种排序算法时空复杂度分析

    千次阅读 2016-09-08 20:34:04
    各种排序算法时空复杂度分析表

    各种排序算法时空复杂度分析表
    这里写图片描述

    展开全文
  • 算法时空复杂度分析

    2020-09-01 20:29:55
    文章目录算法时空复杂度分析0.为什么要学习算法时空复杂度分析1.理论2.分析方法3.大O复杂度表示法4.多项式量级复杂度5.时间复杂度分析的简单规则6.两种时间复杂度7.常见的时间复杂度8.空间复杂度分析9.不同情况下的...

    算法时空复杂度分析

    0.为什么要学习算法时空复杂度分析

    算法的复杂度估算是计算机中很重要的一块内容,通过复杂度分析我们可以估算程序和算法的运行时间,使用内存,随着输入数据的规模变大的增长规律,从而分析一个算法的优劣

    算法分析在竞赛中的实际意义就是可以通过算法的复杂度分析估计可以得到的分数,以及选择更好的算法。通常初学者常常遇到的问题是自己写了一个暴力算法,却没有算法复杂度的概念,从而也不知道写的是一个暴力算法,也无法理解自己的算法为什么会超时(TLE),超内存(MLE)

    不论在何种环境,我们学习数据结构和算法以及算法的时空复杂度分析的最终目的就是让我们设计出的算法在计算机上运行的速度更快,所用的内存更小

    算法的复杂度分析在许多大牛的的成长路上以及对于写出更加优秀的算法是十分必要的环节

    1.理论

    理论内容取材于维基百科,详见链接

    算法的时间复杂度是一个函数,用于定性描述这个算法的运行时间。这是一个代表算法输入值的字符串的长度函数,时间复杂度常用大O符号表示,不包括这个函数的低阶项和首项系数,使用这种方式的时候,时间复杂度可以被称为是渐进的,即考察输入值大小趋近无穷时的情况,例如一个算法对于任何大小为n(大于n0)的输入,它至少需要5n^3 + 3n的时间来完成,那么这个算法的时间复杂度就是O(n^3)

    2.分析方法

    通常算法时间复杂度分析有如下的两种方法:

    i. 运行后分析

    ​ 这种方法通常是将写出的算法在机器上运行,统计这个算法使用了多少时间和内存,但是这种方法存在缺陷:这种测试方式对机器的依赖性较强,性能比较强的机器相对的运行时间,占用内存当然比较小;同时测试的结果依赖于测试用的数据,例如二分法,很依赖于数据的排列方式和所查找数据的位置,测试得出的结果参考的意义较低

    ii. 运行前分析

    ​ 因为运行后才对算法的时空复杂度进行分析这样的分析方法存在问题,于是我们考虑到在纸上提前模拟计算一个算法所需要的大概的执行时间和占用内存,于是我们用到了大O复杂度表示法

    3.大O复杂度表示法

    ​ 我们从下面的代码来了解大O复杂度表示法:

    #include<map>
    #include<list>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<cstdio>
    #include<vector>
    #include<iomanip>
    #include<cstring>
    #include<iterator>
    #include<iostream>
    #include<algorithm>
    #define R register
    #define LL long long
    #define pi 3.141
    #define INF 1400000000
    using namespace std;
    
    int main() {
    	int n;
    	scanf("%d", &n);
    	for (R int i = 0; i < n; ++i) {
    		for (R int j = 0; j < n; ++j) {
    			printf("%d ", n);
    		}
    	}
    	return 0;
    }
    

    ​ 我这里展示了一个两层循环嵌套的简单的代码,这段代码对于CPU而言需要分三步进行:读取代码和指令数据,计算,输出结果如果学习过汇编的同学可以更能明白这里。

    ​ 我们假设每条代码在CPU上的运行时间为cpu_time,那么我们可以粗略的估计这里使用的时间。

    对于定义n和输出n的步骤,分别需要一个cpu_time,这里需要2 × cpu_time

    对于每一个单层的循环,我们都需要2n个cpu_time,因此循环嵌套的部分需要4n^2 × cpu_time

    循环内的输出函数需要一个cpu_time,因此对于输出我们需要n^2 × cpu_time

    ​ 这样,对于这个程序,我们总的运行时间为:
    T ( n ) = 4 n 2 + n 2 + 2 = 5 n 2 + 2 T(n) = 4n^2 + n^2 + 2 = 5n^2 + 2 T(n)=4n2+n2+2=5n2+2
    ​ 对于我们每一次输入数据,代码执行时间随n的增大而增大,这个公式的系数是CPU执行每一次代码的时间,即我们之前定义的cpu_time,此时我们将上述公式写成大O复杂度表示法即:
    T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n))
    ​ 其中T(n)表示算法执行的时间,f(n)表示算法执行的总次数,O()表示 T(n) 和 f(n) 的关系,即大O的由来

    因此我们可以将上面的公式换成:
    T ( n ) = O ( 5 n 2 + 2 ) T(n) = O(5n^2 + 2) T(n)=O(5n2+2)
    ​ 但是我们这里的大O复杂度表示法并不需要计算代码的准确执行时间,而是需要表示一种代码的执行时间或者占用内存随着数据规模增长的一个变化趋势这里需要的不是准确时间,而是变化趋势,因为实际工作的算法的时间可能需要大量的数据,通过分析算法的运行时间和输入数据的规模的变化趋势就能大致的了解一个算法在其相应的环境中较好的工作

    ​ 上面的表示的方法并不简洁,在绝大多数情况下,我们设计的算法时间复杂度的多项式式子可能很长,这样仍旧不方便,那么我们**可以将一些常数,低阶项等运行次数对最高阶多项式影响不大的项删去,我们还可以将多项式的系数删去,**因为我们需要了解的是一个算法时间随数据的变化趋势,多项式的系数对时间的影响并不大,因此我们同样可以删去

    ​ 因此,上述代码的时间复杂度如下:
    T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)
    ​ 因此我们说这个算法的时间复杂度是n^2级别的

    ​ 上述就是大O复杂度分析

    4.多项式量级复杂度

    算法的复杂度根据量级可以分为多项式(Polynomial)和非多项式量级(Non-Deterministic Polynomial),举例O(2^n)和O(n!)属于非多项式量级的时间复杂度,即当数据规模n越来越大的时候,非多项式量级的算法执行时间将急剧增加,且增加速度很恐怖,所以非多项式量级的算法通常情况下是不可接受的低效的算法

    5.时间复杂度分析的简单规则

    当我们拿到一段代码,我们如何分析这段代码,如下是几个比较实用的方法:

    i. 只关注循环执行次数做多的一段代码

    ​ 刚才有讲过,对于大O复杂度分析只是一种变化趋势,我们通常会忽略掉公式中的常量,低阶项,系数等,我们只需要记录一个最大阶的量即可,因此我们在分析算法的复杂度的时候,只需要关注循环执行次数最多的一段代码即可。

    #include<map>
    #include<list>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<cstdio>
    #include<vector>
    #include<iomanip>
    #include<cstring>
    #include<iterator>
    #include<iostream>
    #include<algorithm>
    #define R register
    #define LL long long
    #define pi 3.141
    #define INF 1400000000
    using namespace std;
    
    int main() {
    	int n;
    	scanf("%d", &n);
    	int count = 0;
    	for (R int i = 0; i < n; ++i) {
    		++count;
    	}
    	return 0;
    }
    

    ​ 例如上述代码在定义变量和输入数据的时候时间复杂度是常量级别的时间复杂度,与数据规模无关,当数据规模变化的时候,时间复杂度基本没有影响,而主要影响因素在循环中,因此这段代码的总的时间复杂度就是O(n)

    ii. 加法法则:总复杂度等于量级最大的代码的时间复杂度

    #include<map>
    #include<list>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<cstdio>
    #include<vector>
    #include<iomanip>
    #include<cstring>
    #include<iterator>
    #include<iostream>
    #include<algorithm>
    #define R register
    #define LL long long
    #define pi 3.141
    #define INF 1400000000
    using namespace std;
    
    int main() {
    	int n;
    	scanf("%d", &n);
    	int count_1 = 0, count_2 = 0, count_3 = 0;
    	for (R int i = 0; i < n; ++i) {
    		++count_1;
    	}
    	for (R int i = 0; i < n; ++i) {
    		++count_2;
    	}
    	for (R int i = 0; i < n; ++i) {
    		for (R int j = 0; j < n; ++j) {
    			++count_3;
    		}
    	}
    	return 0;
    }
    

    ​ 对于上述代码,我们可以分析到这段代码的时间复杂度是:
    T ( n ) = O ( 5 ) + O ( n ) + O ( n ) + O ( n 2 ) T(n) = O(5) + O(n) + O(n) + O(n^2) T(n)=O(5)+O(n)+O(n)+O(n2)
    ​ 对于这样的时间复杂度,我们取其中最大的量级,所以这个算法总的时间复杂度是O(n^2)。因此**总的时间复杂度等于量级最大的代码的时间复杂度,我们可以将公式总结为:
    若 T 1 ( n ) = O ( f ( n ) ) , T 2 ( n ) = O ( g ( n ) ) 若 T1(n) = O(f(n)), T2(n) = O(g(n)) T1(n)=O(f(n)),T2(n)=O(g(n))

    则 T ( n ) = T 1 ( n ) + T 2 ( n ) = m a x ( O ( f ( n ) ) , O ( g ( n ) ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) 则 T(n) = T1(n) + T2(n) = max(O(f(n)), O(g(n))) = O(max(f(n), g(n))) T(n)=T1(n)+T2(n)=max(O(f(n)),O(g(n)))=O(max(f(n),g(n)))

    iii. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

    #include<map>
    #include<list>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<cstdio>
    #include<vector>
    #include<iomanip>
    #include<cstring>
    #include<iterator>
    #include<iostream>
    #include<algorithm>
    #define R register
    #define LL long long
    #define pi 3.141
    #define INF 1400000000
    using namespace std;
    
    inline int add(int n) {
    	int cnt = 0;
    	for (R int i = 0; i < n; ++i) {
    		cnt += i;
    	}
    	return cnt;
    }
    
    int main() {
    	int n;
    	scanf("%d", &n);
    	int count = 0;
    	for (R int i = 0; i < n; ++i) {
    		count += add(i);
    	}
    	return 0;
    }
    

    ​ 对于上述代码,我们实现了代码的嵌套,循环每进行一次就调用一次函数,循环的时间复杂度是O(n)的,函数的时间复杂度是O(n)的,因此这段代码的时间复杂度是O(n^2)的

    ​ 因此乘法法则的公式我们可以总结为:
    T ( n ) = T 1 ( n ) × T 2 ( n ) = O ( n × n ) = O ( n 2 ) T(n) = T1(n) × T2(n) = O(n × n) = O(n^2) T(n)=T1(n)×T2(n)=O(n×n)=O(n2)

    6.两种时间复杂度

    ​ 对于对数级的时间复杂度,我们通常使用换底公式将对数换为以2为底的对数,因此我们通常忽略对数的底,统一将时间复杂度表示为O(logN)

    ​ 对于O(n + m) / O(n × m)量级的时间复杂度,我们无法事先评估大小,那么表述时间复杂度的时候需要全部写出

    7.常见的时间复杂度

    摘自维基百科->https://zh.wikipedia.org/zh-hans/%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6

    非常常见已加粗表示

    名称运行时间(T(n))算法举例
    常数时间O(1)判断数字(二进制)奇偶性
    反阿克曼时间O(alpha (n))并查集单个操作的平摊时间
    迭代对数时间O(logn)分散式圆环着色问题
    对数对数时间O(loglogn)有界优先队列的单个操作
    对数时间O(logn)二分查找
    幂对数时间O(logN^2)
    幂时间O(n^c)(0 < c < 1)K-d树的搜索操作
    线性时间O(n)无序数组的搜索
    线性迭代对数时间O(nlogn)莱姆德·赛德尔的三角分割多边形算法
    线性对数时间O(nlogn)最快的比较排序
    二次时间O(n^2)冒泡排序,插入排序
    三次时间O(n^3)矩阵乘法的基本实现,计算部分相关性
    多项式时间线性规划中的卡马卡演算法,AKS质数测试
    准多项式时间
    次指数时间(第一定义)
    次指数时间(第二定义)
    指数时间2^O(n)使用动态规划解决旅行推销员问题
    阶乘时间O(n!)通过暴力搜索解决旅行推销员问题
    指数时间2^poly(n)
    双重指数时间22poly(n)在预膨胀算数中决定一个给定描述的真实性

    具体各时间复杂度详解参见维基百科

    8.空间复杂度分析

    内存复杂度的概念与时间复杂度类似,是计算一个程序使用的内存的增长趋势的方式

    与时间复杂度类似,渐进空间复杂度表示的是算法的存储空间随着数据规模变化的趋势,空间复杂度的分析较为容易

    例如下面的代码:

    #include<map>
    #include<list>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<cstdio>
    #include<vector>
    #include<iomanip>
    #include<cstring>
    #include<iterator>
    #include<iostream>
    #include<algorithm>
    #define R register
    #define LL long long
    #define pi 3.141
    #define INF 1400000000
    using namespace std;
    
    int main() {
    	int n;
    	scanf("%d", &n);
    	int count = 0;
    	int *number = new int[n];
    	for (R int i = 0; i < n; ++i) {
    		scanf("%d", &number[i]);
    	}
    	return 0;
    }
    

    上述代码申请了n个int大小的数组,并且数组大小随数据规模的变化而变化,此时的空间复杂度就是O(n)

    常用的空间复杂度有O(1),O(n),O(n^2)

    空间复杂度的分析比较简单,主要看是否有与数据规模相关的内存申请操作即可

    一般的算法竞赛会提供64MB/128MB/256MB的内存,一个int的大小是4B,假设给出128MB的空间,可以估算可以放得下的int的数组的大小为
    128 × 1 0 6 4 = 32 × 1 0 6 \frac{128 × 10^6}{4} = 32 × 10^6 4128×106=32×106
    所以说开百万级别的int数组是安全的(不过大的数组尽量不要开在子函数内,可能会爆栈),开千万级别的数组需要考虑,更不要开int flag[100000][100000](NOIP2012铺地毯出现过)

    9.不同情况下的复杂度分析

    除了上述各种情况下的复杂度分析外,我们还需要知道不同情况下的复杂度,主要的有如下的四种情况:最好,最坏;平均;均摊

    i. 最好最坏情况时间复杂度

    #include<map>
    #include<list>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<cstdio>
    #include<vector>
    #include<iomanip>
    #include<cstring>
    #include<iterator>
    #include<iostream>
    #include<algorithm>
    #define R register
    #define LL long long
    #define pi 3.141
    #define INF 1400000000
    using namespace std;
    
    int main() {
    	int n;
    	scanf("%d", &n);
    	int number[100];
    	int search;
    	scanf("%d", &search);
    	for (R int i = 0; i < n; ++i) {
    		scanf("%d", &number[i]);
    	}
    	for (R int i = 0; i < n; ++i) {
    		if (number[i] == search) {
    			printf("%d", i);
    			break;
    		}
    	}
    	return 0;
    }
    

    ​ 比如上面的例子,在数组种查找元素search并返回其位置,假如要查找的元素是数组的第一个元素,那么我们只需要执行一次就可以结束程序,那么这个算法的时间复杂度为O(1),即最好情况下的时间复杂度,假如我们需要查找的元素不在数组种,我们就需要执行n次才能结束算法,此时的时间复杂度即为O(n)此时对应的是最坏情况下的时间复杂度

    ii. 平均情况时间复杂度

    ​ 因为此时我们求的是平均情况,所以我们可以做个假设:这个数字在数组中的概率是1/2;不在数组中的概率是1/2;这个数字出现在0-1/n的概率是1/n,所以根据概率的乘法原理,要查找的数据出现在数组中的概率为1/2n,现在数组中的概率乘以任意位置的概率,然后我们就可以将每个元素被查找到时要查找的次数和对应的概率相乘,最后进行求和就可以得到算法的平均复杂度:
    1 × 1 2 n + 2 × 1 2 n + 3 × 1 2 n + . . . + n × 1 2 n + n × 1 2 = 3 n + 1 4 1 × \frac{1}{2n} + 2 × \frac{1}{2n} + 3 × \frac{1}{2n} + ... + n × \frac{1}{2n} + n × \frac{1}{2} = \frac{3n + 1}{4} 1×2n1+2×2n1+3×2n1+...+n×2n1+n×21=43n+1
    ​ 因为我们可以省略系数,所以上述的代码查找元素search的时间复杂度同样也是O(n),通常情况我们不需要严格分析这三种不同的情况。一般情况下,使用前面的复杂度分析方法计科,如果需要详细推导可以计算平均时间复杂度

    iii. 均摊时间复杂度

    ​ 均摊时间复杂度是一种特殊情况下的时间复杂度,并不是很常见,主要思想是把运行时间多的情况下的复杂度拆分,并均摊到运行时间少的情况下

    ​ 如下所示下面的代码:

    #include<map>
    #include<list>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<cstdio>
    #include<vector>
    #include<iomanip>
    #include<cstring>
    #include<iterator>
    #include<iostream>
    #include<algorithm>
    #define R register
    #define LL long long
    #define pi 3.141
    #define INF 1400000000
    using namespace std;
    
    int array_length;
    int number[100];
    
    inline void add(int value) {
    	if (array_length < 100) {
    		number[array_length] = value;
    		++array_length;
    	}
    	else {
    		int sum = 0;
    		for (R int i = 0; i < 100; ++i) {
    			sum += number[i];
    		}
    		number[0] = sum;
    	}
    }
    
    
    int main() {
    	int n;
    	scanf("%d", &n);
    	for (R int i = 0; i < n; ++i) {
    		int num;
    		scanf("%d", &num);
    		add(num);
    	}
    	return 0;
    }
    

    ​ 上述代码实现了下述功能,向数组中从插入一个元素,当元素个数大于100后,将数组求和并将和放在数组第一个元素上;当元素少于100是,直接将元素插入空闲的位置上

    ​ 显然这两种情况的时间复杂度为O(n)和O(1),但是考虑到实际情况,实际情况下总是先将一个个位置存满后才执行求和操作,并且这两种情况的发生有规律

    ​ 我们可以将耗时的O(n)复杂度的代码均摊到不太耗时的O(1)上,这样总体的时间复杂度就会变成O(1),这就是均摊时间复杂度的均摊的思想,通常情况下均摊时间复杂运用在绝大多数情况下不太耗时,少数情况下耗时的操作,并且两种情况逻辑联系,有前后顺序

    10.算法时间复杂度与数据规模

    对比不同复杂度的增长,大概最大可以接收的数据如下表所示(具体情况具体分析)

    算法时间复杂度建议不超过的n的范围
    O(logn)很大,long long内均可
    O(n)10^7
    O(nlogn)10^5 - 5 * 10^5
    O(n^2)1000 - 5000
    O(n^3)200 - 500
    O(2^n)20 - 24
    O(n!)12

    11.总结

    我们在计算时间复杂度的时候,取代码中对时间增长贡献最大的一部分,从数学的角度来看就是取这个多项式的最大的一项

    分析时间的复杂度通常并不容易,对于只有很多for循环的程序,分析复杂度当然是容易的,但是当遇到递归的情况的时候,分析时间复杂度就相对麻烦

    另外时间复杂度是可以估算的,例如整个程序要从1到n进行二分,这边的时间复杂度是O(logn),每一次枚举需要进行一次k次验证的操作,算法的时间复杂度是O(k),那么整个程序的时间复杂度就是O(klogn)

    展开全文
  • 各内排序算法时空复杂度对比表 ...

    目录

    各内排序算法时空复杂度对比表

    源码


    各内排序算法时空复杂度对比表

    算法最好平均最好空间复杂稳定
    插入O(n)O(n²)O(1)
    选择O(n²) 
    冒泡O(n)O(n²)
    希尔-O(n1.3)- 
    快排O(nlog₂n)O(n²)O(log₂n) 
    堆排O(1) 
    归并O(n)
    基数O(d(n+r))O(r)

    源码

    package top.senseiliu.codesnippet;
    
    public final class SortUtils {
        private SortUtils() {
    
        }
    
        // 输出数组
        public static void printNum(int[] num) {
            for (int item : num) {
                System.out.print(item + " ");
            }
            System.out.println();
        }
    
        /**
         * 直接插入法
         * 从第二个元素开始遍历(正序)
         * 对当前元素向前遍历(倒序)
         *   如果前一个值大于当前值,把当前位置值置为前一个值,继续向前遍历
         * 最后把当前值插回去
         * 
         * @param num
         */
        public static void insertSort(int[] num) {
            for (int i = 1; i < num.length; ++i) {
                int curNum = num[i];
                int preIndex = i - 1;
                while (preIndex >= 0 && num[preIndex] > curNum) {
                    num[preIndex + 1] = num[preIndex];
                    --preIndex;
                }
                num[preIndex + 1] = curNum;
            }
        }
    
        /**
         * 简单选择法
         * 从第一个元素遍历到倒数第二个
         * 从当前元素下标的下一个下标开始遍历,得到一个最小值的下标
         * 如果当前元素下标不等于当前元素下标,则交换两值
         * 
         * @param num
         */
        public static void selectSort(int[] num) {
            for (int i = 0; i < num.length - 1; ++i) {
                int minIndex = i;
                for (int j = i + 1; j < num.length; ++j) {
                    if (num[j] < num[minIndex]) {
                        minIndex = j;
                    }
                }
                if (i != minIndex) {
                    int minValue = num[minIndex];
                    num[minIndex] = num[i];
                    num[i] = minValue;
                }
            }
        }
    
        /**
         * 冒泡排序法-普通版
         * 
         * @param num
         */
        public static void bubbleSort(int[] num) {
            for (int i = 0; i < num.length - 1; ++i) {
                for (int j = num.length - 1; j > i; --j) {
                    if (num[j - 1] > num[j]) {
                        int tmp = num[j];
                        num[j] = num[j - 1];
                        num[j - 1] = tmp;
                    }
                }
            }
        }
    
        /**
         * 冒泡排序法-优化版
         * 一次都没有产生交换说明数组基本有序直接退出
         * 
         * @param num
         */
        public static void bubbleSortOptimized(int[] num) {
            boolean sorted;
            for (int i = 0; i < num.length - 1; ++i) {
                sorted = true;
                for (int j = num.length - 1; j > i; --j) {
                    if (num[j - 1] > num[j]) {
                        int tmp = num[j];
                        num[j] = num[j - 1];
                        num[j - 1] = tmp;
                        sorted = false;
                    }
                }
                if (sorted) {
                    return;
                }
            }
        }
    
        /**
         * 交换排序法
         * 
         * @param num
         */
        public static void exchangeSort(int[] num) {
            for (int i = 0; i < num.length - 1; ++i) {
                for (int j = i + 1; j < num.length; ++j) {
                    if (num[i] > num[j]) {
                        int tmp = num[i];
                        num[i] = num[j];
                        num[j] = tmp;
                    }
                }
            }
        }
    
        /**
         * 希尔排序法
         * 基于直接插入法
         * 根据间隔分组,使用直接插入法
         * 缩小间隔,直到为1,数组基本有序
         *
         * @param num 数组
         */
        public static void shellSort(int[] num) {
            int gap = num.length / 2;
            while (gap > 0) {
                for (int i = gap; i < num.length; ++i) {
                    int curNum = num[i];
                    int j = i - gap;
                    while (j >= 0 && num[j] > curNum) {
                        num[j + gap] = num[j];
                        j -= gap;
                    }
                    num[j + gap] = curNum;
                }
                gap /= 2;
            }
            
        }
    
        /**
         * 快速排序法
         * 选定一个元素作为基准点,这里选beginIndex作为基准元素
         * 对数组两端开始与基准元素比较,向中间靠拢
         * 当两指针停止移动,交换两值继续向中间靠拢
         * 当两指针在同一下标时,将该下标值与基准元素值交换
         * 将该下标值左右两边的数组再做递归快排
         *
         * @param num 数组
         * @param beginIndex 起始下标
         * @param endIndex 结束下标
         */
        public static void quickSort(int[] num, int beginIndex, int endIndex) {
            int i = beginIndex;
            int j = endIndex;
            if (i < j) {
                int base = num[i];
                while (i != j) {
                    while (j > i && num[j] >= base) {
                        --j;
                    }
                    num[i] = num[j];
                    while (i < j && num[i] <= base) {
                        ++i;
                    }
                    num[j] = num[i];
                }
                num[i] = base;
                quickSort(num, beginIndex, i - 1);
                quickSort(num, i + 1, endIndex);
            }
        }
    
        /**
         * 调整为大顶堆
         *
         * @param num 数组
         * @param curIndex 将该节点所在的子树调整为大顶堆
         * @param maxRange 将被调整的最大范围
         */
        public static void sift(int[] num, int curIndex, int maxRange) {
            int i = curIndex;
            int j = i * 2;
            int tmp = num[i];
            while(j <= maxRange) {
                if (j < maxRange && num[j + 1] > num[j]) {
                    ++j;
                }
                if (num[j] > tmp) {
                    num[i] = num[j];
                    i = j;
                    j = i * 2;
                } else {
                    break;
                }
            }
            num[i] = tmp;
        }
    
        /**
         * 堆排序
         * 数组中的第一个元素(下标为0)不参与排序
         * 如new int[]{0, 5, 4, 3, 2, 1}数组
         * 排序后为{0, 1, 2, 3, 3, 4}
         * 
         * @param num 数组,第一个元素需要为空的
         */
        public static void heapSort(int[] num) {
            for (int i = (num.length - 1) / 2; i >= 1; --i) {
                sift(num, i, num.length - 1);
            }
            for (int i = num.length - 1; i >= 2; --i) {
                int tmp = num[1];
                num[1] = num[i];
                num[i] = tmp;
                sift(num, 1, i - 1);
            }
        }
    
    }
    

    源码地址:

    https://gitee.com/feistel/Blog/tree/master/Java/code/CodeSnippet/code-snippet

    展开全文
  • 文章目录各类排序算法时空复杂度、稳定性对比1. 插入排序1.1 直接插入排序1.2 折半插入排序2. 冒泡排序3. 选择排序4. 希尔排序5. 归并排序6. 快速排序7. 堆排序完整测试代码 各类排序算法时空复杂度、稳定性对比 ...
  • Python算法和数据结构(二)——各大排序算法时空复杂度比较一、各大排序算法时空复杂度表格二、对一些问题的注释1、冒泡排序2、选择排序3、插入排序4、快速排序5、堆排序 一、各大排序算法时空复杂度表格 ...
  • 排序稳定性:若存在多个关键字相同的记录,经过排序后,这些具有相同关键字的记录之间的相对次序保持不变,则称该排序算法为稳定的排序算法;若具有相同关键字的记录之间的相对次序发生了变化,则称这种排序方法是不...
  • 这其中排序算法的出镜率异常的高,笔者的观点是要事可以不优先,但想做的时候一定要找的到。于是,下面的比较表便横空出世了~   正文   看完表格后,大家想不想知道上标,下标如何打出呢,请看我的下篇博客...
  • 排序算法总结(C语言版)已介绍排序算法的基本思想和C语言实现,本文只介绍时空复杂度和稳定性。 1.基本概念 时间复杂度: 一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费...
  • 各种排序算法时间复杂度比较

    千次阅读 2011-08-21 13:38:52
    各种排序算法时间复杂度比较: 以上图片来维基百科,http://en.wikipedia.org/wiki/Algorithms
  • 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。  各种内部排序按所采用的基本思想(策略)可...
  • 如果排序前两个相等的数据其在序列中的先后位置顺序与排序后它们两个先后位置顺序相同,我们说算法具有稳定性。
  • 排序算法时间复杂度的记忆方法

    千次阅读 2020-03-05 18:15:30
    选泡插, 快归堆希桶计基, n方n老n一三, 对n加kn乘k, 不稳稳稳不稳稳, 不稳不稳稳稳稳。 冒泡:基本不用,太慢 选择:基本不用,慢、不稳 ...马士兵说:30秒让你记住所有排序算法-宋词记忆法 ...
  • 稳定性
  • 常用的排序算法的时间复杂度和空间复杂度 小结: 1. 平均时间复杂度为O(nlogn)的几个算法:快些归队(快速,希尔,归并,堆排序) 2. 稳定性--不稳定的算法:快些建堆!(快速,希尔,简单选择,堆排序) ...
  • 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法, 冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。 冒泡法:  这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的...
  • 一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法的语句执行次数称为语句频度或时间频度。记为T(n)。n称为问题的规模,当n不断变化时,时间频度T(n)也会不断...
  • 分类空间复杂度算法的时间复杂度(计算实例)算法的时间复杂度O(1)O(n^2)O(n)O(log2n )O(n^3)算法复杂度的渐近表示法一 大O记号二 Ω记号三 Θ记号四 小o记号五 例子常见排序算法时空复杂度总结 算法复杂度分为...
  • 算法时空复杂度分析及重定向

    千次阅读 2014-06-28 14:35:44
    关于图的DFS的时间复杂度分析: DFS是递归函数,一共调用n次,n为定点数,每次访问其对应的临界点,设为ei 若是临界表,因为循环遍历list。总的基本操作次数 n+sum(e1+...en)=n+e, 故O(n+e) 若是matrix,因为遍历...
  • 排序算法分为两大类:简单排序算法和高级排序算法假设一下元素都有n个 《1》常见的简单排序算法 (1)冒泡法:从最后一个元素开始依次和前面的元素比较;最后一个和前面所有的比较完后,倒数第二个再和前面的...
  • 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法, 冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。 冒泡法:  这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的...
  • 排序算法的时间复杂度

    千次阅读 2019-03-16 05:36:38
    各种排序算法比较 各种常用排序算法 类别 排序方法 时间复杂度 空间复杂度 稳定性 复杂性 特点 最好 ...
  • 排序算法之 冒泡排序及性能优化(时间复杂度+空间复杂度分析) 排序算法之 简单选择排序及时间复杂度分析 直接插入排序 直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择...
  • QUESTION:八大排序:Java实现八大排序算法复杂度分析 ANSWER: 一:冒泡排序 1.算法分析 2.时间复杂度分析 3.代码 二:选择排序 1.算法分析 2.时间复杂度分析 3.代码 三:插入排序 1.算法分析 2.时间...
  • 算法时间复杂度计算In computer science, analysis of algorithms is a very crucial part. It is important to find the most efficient algorithm for solving a problem. It is possible to have many algorithms...
  • 自学算法时空复杂度的计算

    千次阅读 2018-03-21 10:59:18
    直接进入时空复杂度的计算。首先。时间复杂度概念:算法需要运行的时间,一般将算法的执行次数作为时间复杂度的度量标准。我们看算法1:  int sum = 0; //运行1次  int total = 0; //运行1次  for(int i = 0; ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,267
精华内容 1,706
关键字:

排序算法的时空复杂度