0%

时间复杂度,空间复杂度和稳定性

时间复杂度,空间复杂度和稳定性

时间复杂度

指的是算法执行语句的次数(取最多次数的那个语句来表示)
在这里插入图片描述
a^x^ =N(a>0,且a≠1) ==> x=logaN
注: Hash的查找的时间复杂度是1,原因“key-value键值对”,离散的

下面是3个例子关于while简单的理解一下时间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//第一个算法执行次数是20,常量所以是O(1)
int x = 1;
while (x<20)
{
x++;
}

//第二个执行次数是n决定,O(n)
int x = 1;
while (x<n)
{
x++;
}

// 执行次数由 c 和 n共同决定,c = 1 时,O(1),c != 1时,O(n)
int x = 1;
c = c or 1;
while (x<n && c != 1)
{
x++;
}

再来看一个简单插入排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>

// 分类 ------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2)
// 最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定

int main()
{
int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };// 从小到大插入排序
int n = sizeof(A) / sizeof(int);
int i, j, get;

for (i = 1; i < n; i++) // 类似抓扑克牌排序
{
get = A[i]; // 右手抓到一张扑克牌
j = i - 1; // 拿在左手上的牌总是排序好的
while (j >= 0 && A[j] > get) // 将抓到的牌与手牌从右向左进行比较
{
A[j + 1] = A[j]; // 如果该手牌比抓到的牌大,就将其右移
j--;
}
A[j + 1] = get;// 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边(相等元素的相对次序未变,所以插入排序是稳定的)
}
printf("插入排序结果:");
for (i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}

改进一下,第二层采用二分查找法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>

// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- O(nlogn)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定

int main()
{
int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大二分插入排序
int n = sizeof(A) / sizeof(int);
int i, j, get, left, right, middle;

for (i = 1; i < n; i++) // 类似抓扑克牌排序
{
get = A[i]; // 右手抓到一张扑克牌
left = 0; // 拿在左手上的牌总是排序好的,所以可以用二分法
right = i - 1; // 手牌左右边界进行初始化
while (left <= right) // 采用二分法定位新牌的位置
{
middle = (left + right) / 2;
if (A[middle] > get)
right = middle - 1;
else
left = middle + 1;
}
for (j = i - 1; j >= left; j--) // 将欲插入新牌位置右边的牌整体向右移动一个单位
{
A[j + 1] = A[j];
}
A[left] = get; // 将抓到的牌插入手牌
}
printf("二分插入排序结果:");
for (i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}

再改进一下,成为希尔排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>  

// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 根据步长序列的不同而不同。已知最好的为O(n(logn)^2)
// 最优时间复杂度 ---- O(n)
// 平均时间复杂度 ---- 根据步长序列的不同而不同。
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定

int main()
{
int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大希尔排序
int n = sizeof(A) / sizeof(int);
int i, j, get;
int h = 0;
while (h <= n) // 生成初始增量
{
h = 3*h + 1;
}
while (h >= 1)
{
for (i = h; i < n; i++)
{
j = i - h;
get = A[i];
while ((j >= 0) && (A[j] > get))
{
A[j + h] = A[j];
j = j - h;
}
A[j + h] = get;
}
h = (h - 1) / 3; // 递减增量
}
printf("希尔排序结果:");
for (i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}

空间复杂度

指对一个算法在运行过程中临时占用存储空间,看你开辟的临时空间就好了。

稳定性

排序稳定性

指的是针对相同数值,在排序前后是否有发生变化的可能。一般举返例来证明

这只是一个例子来说明稳定性,大家普遍说冒泡是稳定的,条件换成“>=”导致算法不稳定,建议不要

下面是个简单冒泡排序来排序{1,1,1,1,1,1,2, 2,1}

1
2
3
4
5
6
7
8
9
10
for (int j = 0; j < n - 1; j++)           
{
for (int i = 0; i < n - 1 - j; i++)
{
if (A[i] > A[i + 1]) // 如果条件改成A[i] >= A[i + 1],则变为不稳定的排序算法
{
exchange(A, i, i + 1);
}
}
}

数值稳定性

讨论的是计算机编程中,由于四舍五入等导致的结果偏差。

  • 尽量减少运算次数
  • 加法运算时,避免大数加小数
  • 避免两个相近的数相减
  • 避免小数做除法或者大数做乘法

附录

常见排序总结

在这里插入图片描述
编程排序算法