排序算法介绍:
简单的来讲哈,就是将一组数据按照指定的规则顺序进行排列的过程。常见的排序算法分为内部排序和外部排序:内部排序就是指所有数据在内部存储中进行排序。相对的外部排序就是指因为数据量过大无法全部加载到内部存储时,需要借助外部存储进行排序的算法。
常见的排序算法:
算法的时间复杂度
衡量一个算法的执行时间一般有两种方法,统计法、估算法。所谓的统计法就是在程序开到到程序结束记录程序的运行时间,但这种方法存在局限性,因为程序的执行时间一般受计算机的硬件、软件限制。同样的程序在不同计算机中的表现不同。估算法则是根据某个算法的时间复杂度来判断那段算法更优。
时间频度
一个算法中语句的执行次数跟算法的执行时间是成正比的,算法中语句的执行次数就称为算法的时间频度。用T(n)来表示。
例如:
@Test
public void run() {
int res = 0;
int end = 100;
//算法1
for (int i = 1; i <= end; i++) {
res += i;
}
//算法2
res = (1 + end) * end / 2;
}
那么在这个例子中算法1执行了end+1次;算法2执行了一次;
所以算法1的时间复杂度为:T(n)=n+1;
所以算法2的时间复杂度为:T(n)=1;
时间复杂度的计算
忽略常数项
| T(n)=2n+20 | T(n)=2*n | T(n)=3n+10 | T(n)=3n |
---|---|---|---|---|
1 | 22 | 2 | 13 | 3 |
2 | 24 | 4 | 16 | 6 |
5 | 30 | 10 | 25 | 15 |
8 | 36 | 16 | 34 | 24 |
15 | 50 | 30 | 55 | 45 |
30 | 80 | 60 | 100 | 90 |
100 | 220 | 200 | 310 | 300 |
300 | 620 | 600 | 910 | 900 |
由表和折线图不难看出,在相同系数的情况下时间复杂度可以忽略常数项。因为随着数据量的增大曲线重合。
忽略低次项
| T(n)=2n^2+3n+10 | T(2n^2) | T(n^2+5n+20) | T(n^2) |
---|---|---|---|---|
1 | 15 | 2 | 26 | 1 |
2 | 24 | 8 | 34 | 4 |
5 | 75 | 50 | 70 | 25 |
8 | 162 | 128 | 124 | 64 |
15 | 505 | 450 | 320 | 225 |
30 | 1900 | 1800 | 1070 | 900 |
100 | 20310 | 20000 | 10520 | 10000 |
由表和折线图不难看出,随着n的增大低次项系数可以忽略。
忽略系数
| T(3n^2+2n) | T(5n^2+7n) | T(n^3+5n) | T(6n^3+4n) |
---|---|---|---|---|
1 | 5 | 12 | 6 | 10 |
2 | 16 | 34 | 18 | 56 |
5 | 85 | 160 | 150 | 770 |
8 | 208 | 376 | 552 | 3104 |
15 | 705 | 1230 | 3450 | 20310 |
30 | 2760 | 4710 | 27150 | 162120 |
100 | 30200 | 50700 | 1000500 | 6000400 |
根据折线图可以看出在平方的情况下时间复杂度与系数关系不大,所以可以忽略系数;但是在立方的情况下时间复杂度与系数的关系就非常大了,可以说系数是立方时间复杂度的关键。
一般情况下,一个算法的语句执行次数是数据规模n的某个函数,记做T(n),若存在某个函数f(n)与T(n)的比值无线接近于1,那么T(n)和f(n)就同处一个数量级,表示为f(n)=O(f(n)),为算法的时间复杂度。
计算时间复杂度的方法:
对于平方的算法:忽略常数项,忽略低次项,忽略系数;
例:T(n)=n²+7n+6 => T(n)=O(n²)
常见的时间复杂度:
常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
对数阶O(log2n)
在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2n也就是说当循环 log2n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(log2n) 。 O(log2n) 的这个2 时间上是根据代码变化的,i = i * 3 ,则是 O(log3n) 。
线性阶O(n)
for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度
线性对数阶O(nlogN)
线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)
平方阶O(n²)
平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n*n),即 O(n²) 如果将其中一层循环的n改成m,那它的时间复杂度就变成了 O(m*n)
这里立方阶、k次方阶就不举例子了。
平均复杂度和最坏复杂度:
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。
最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
平均时间复杂度和最坏时间复杂度是否一致,和算法有关。
这里注明一下最后一张图表的来源:https://blog.csdn.net/JohinieLi/article/details/80959584
从之前的知识可以分析出,选择、插入、冒泡等基本算法至少是需要两层循环。依次可以继续分析下去。