目录
一、计算的资源
程序运行时需要的资源有两种,即计算时间和存储时间。一个算法对这两个资源的使用程度可以用来衡量算法的优劣。
- 时间复杂度:程序运行所需的时间。
- 空间复杂度:程序运行所需要的存储空间。
可以用clock()函数统计程序运行的时间。
#include <bits/stdc++.h>
using namespace std;
int main(){
clock_t start, end;
start = clock();
...
end = clock();
cout << (double)(end - start) / CLOCK_PER_SEC << endl;
}
由于程序的运行是时间依赖于计算机的性能,不同的计算机结果不同,所以通常用程序执行的 “次数” 来衡量,例如执行了 n 次就记为O(n)。
下面给出一个例子:
Problem Description:
给你n个整数,请按从大到小的顺序输出其中前m大的数。
Input:
每组测试数据有两行,第一行有两个数n,m(0<n,m<1000000),第二行包含n个各不相同,且都处于区间[-500000,500000]的整数。
Output:
对每组测试数据按从大到小的顺序输出前m大的数。
思路是先对100万个数进行排序,然后输出前m大的数。
1、冒泡排序
首先用最简单的冒泡排序解决问题。
//#include <bits/stdc++.h>
#include <stdio.h>
using namespace std;
int a[1000001];
# define swap(a, b){int temp = a; a = b; b = temp;}
int m, n;
void bubble_sort(){
for(int i = 1; i <= n - 1; i++){
for(int j = 1; j <= n - i; j++){
if(a[j] > a[j + 1])
swap(a[j], a[j + 1]);
}
}
}
int main(){
while(~scanf("%d %d", &n, &m)){
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
bubble_sort();
for(int i = n; i >= n - m + 1; i--){
if(i == n - m + 1) printf("%d\n", a[i]);
else printf("%d ", a[i]);
}
}
return 0;
}
下面分析程序的时间和空间效率。
(1)时间复杂度
在bubble_sort()中有两层循环,循环次数约为 n^2/2;在swap(a,b)中做了三次操作;总的操作次数为 3n^3/2,时间复杂度记为 O(n^2) 。
(2)空间复杂度
程序使用了int a[1000001] 存储数据,bubble_sort()也没有使用额外的空间。int 是32位整数,所以a[1000001]共使用了4MB的空间。这是冒泡排序的优点,它不额外占用时间。
2、快速排序
快速排序是一种基于分治法的优秀的排序算法。这里直接引用了STL的sort()函数,它是改良版的快速排序,称为“内省式排序”。
直接把上面的程序更改为
//#include <bits/stdc++.h>
#include <iostream>
#include <algorithm>
using namespace std;
int a[1000001];
# define swap(a, b){int temp = a; a = b; b = temp;}
int m, n;
void bubble_sort(){
for(int i = 1; i <= n - 1; i++){
for(int j = 1; j <= n - i; j++){
if(a[j] > a[j + 1])
swap(a[j], a[j + 1]);
}
}
}
int main(){
while(~scanf("%d %d", &n, &m)){
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
sort(a + 1, a + n + 1); // bubble_sort();
for(int i = n; i >= n - m + 1; i--){
if(i == n - m + 1) printf("%d\n", a[i]);
else printf("%d ", a[i]);
}
}
return 0;
}
时间复杂度为O(nlogn),刚好通过测试。
3、哈希算法
哈希算法是一种以空间换取时间的算法。
本题的哈希思路是:子啊输入数字 t 的时候,在a[500000 + t] 这个位置记录a[500000 + t] = 1, 在输出的时候逐个检查 a[i],如果a[i] 等于1,表示这个数存在,打印出前m个数。程序如下:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000001;
int a[MAXN];
int main(){
int n, m;
while(~scanf("%d %d"), &n, &m){
memset(a, 0, sizeof(a));
for(int i = 0; i < n; i++){
int t;
scanf("%d", &t);
a[500000 + t] = 1;
}
for(int i = MAXN; m > 0; i++){
if(a[i]){
if(m > 1) printf("%d ", i - 500000);
else printf("%d\n", i - 500000);
m--;
}
}
}
return 0;
}
程序并没有做显示的排序,只是在每次输入时候按哈希插入到相对应的位置,只有一次操做;n个数输入完毕,就相当于排序好了。总的时间复杂度是O(n)。
二、算法的定义(省略)
三、算法的评估
上面已经反复提到,衡量算法性能的主要标准是时间复杂度,为什么一般不讨论空间复杂度呢?在一般情况下,一个程序的空间复杂度是容易分析的,而时间复杂度往往关系到算法的根本逻辑,更能说明一个程序的优劣。
一个程序或算法的时间复杂度有以下可能。
1、O(1)
计算时间是一个常数,和问题的规模 n 无关。例如,哈希算法,用hash函数在常数时间内计算出存储位置。
2、O(logn)
计算时间是对数,通常以2为底的对数,每一步计算后,问题的规模减小一倍。例如在一个长度为 n 的有序数列中查找某个数,用折半查找的方法只要 logn 次就能找到。
O(logn) 和 O(1) 没有太大差距。
3、O(n)
计算时间随着规模 n 线性增长。在很多情况下,这是算法可能得到的最优复杂度,因为对输入的 n 个数,程序一般需要处理所有的数,即计算 n 次。例如查找一个无序数列中的某个数,可能需要检查所有的数。
4、O(nlogn)
这常常是算法能得到的最优复杂度。例如分治法,一共O(logn)个步骤,每个步骤对每个数操做一次,所以总的时间复杂度是O(nlogn)。
5、O(n^2)
一个两重循环算法,时间复杂度是O(n^2)。例如冒泡排序是典型的两重循环。
6、O(2^n)
一般对应集合问题,例如一个集合中有n个数,要求输出它的所有子集,子集有2^n个。
7、O(n!)
在集合问题中,如果要求按顺序输出所有的子集,那么复杂度就是O(n!)。