排序算法介绍:
简单的来讲哈,就是将一组数据按照指定的规则顺序进行排列的过程。常见的排序算法分为内部排序和外部排序:内部排序就是指所有数据在内部存储中进行排序。相对的外部排序就是指因为数据量过大无法全部加载到内部存储时,需要借助外部存储进行排序的算法。
常见的排序算法:
算法的时间复杂度
衡量一个算法的执行时间一般有两种方法,统计法、估算法。所谓的统计法就是在程序开到到程序结束记录程序的运行时间,但这种方法存在局限性,因为程序的执行时间一般受计算机的硬件、软件限制。同样的程序在不同计算机中的表现不同。估算法则是根据某个算法的时间复杂度来判断那段算法更优。
时间频度
一个算法中语句的执行次数跟算法的执行时间是成正比的,算法中语句的执行次数就称为算法的时间频度。用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
从之前的知识可以分析出,选择、插入、冒泡等基本算法至少是需要两层循环。依次可以继续分析下去。