(一)数据结构与算法的区别
- 数据结构:是计算机存储,组织数据的方式。
指相互之间存在一种或多种特定关系的数据元素的集合。 - 算法:算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
- 数据结构是数据元素的集合;算法是一系列的计算步骤。
(二)算法的效率
1. 时间效率(时间复杂度):衡量算法运行的速度(快慢)。
2. 空间效率(空间复杂度):衡量算法运行所需要的额外空间。
(三)时间复杂度
-
时间复杂度:衡量算法快慢的标准刻度。
-
虽然是时间复杂度,但是无法直接根据时间来判断算法的快慢,因为根据时间判断会有各种因素的干扰。
-
所需前提:在cpu单位时间运行指令数恒定的情况下,
(算法运行的基本指令个数)
求算法运行指令数和数据规模n的关系 F(n)。 -
时间复杂度不是绝对意义上的快慢。
随着数据规模的变化,运行时间的变化趋势。 -
大O渐进表示法:
(1)只保留最高次项;
(2)最高次项的系数化为1. -
算法的时间复杂度分为三种情况:
| 三种情况 | 说明 |
|---|---|
| 最好情况 | 任意输入规模的最小运行次数(下界) |
| 最坏情况 | 任意输入规模的最大运行次数(上界) |
| 平均情况 | 任意输入规模的期望运行次数 |
注:一般情况下关注的是算法的最坏情况。根据最坏情况计算时间复杂度。
-
常见的几种时间复杂度
(1) O(1)
(2)O(log(n))
(3)O(n)
(4)O(n*log(n))
(5)O(n^2)
时间复杂度的大小由上到下依次为由小到大。
举例(1):
void Func1(int N){
int count = 0;
for (int i = 0; i < N ; i++) {
for (int j = 0; j < N ; j++) {
count++;
}
}
for (int k = 0; k < 2 * N ; k++) {
count++;
}
int M = 10;
while ((M--) > 0) {
count++;
}
System.out.println(count);
}
分析程序可知:
该程序的基本操作次数为F(N) = N * N+2 * N+10
- 根据大O渐进表示法:
(1)只保留最高次项得:F(N) = N * N=N^2
(2)使最高项次数为1:F(N) = N ^2
所以该算法的时间复杂度为O(n^2).
举例(2)
void Func2(int N) {
int count = 0;
for (int k = 0; k < 2 * N ; k++) {
count++;
}
int M = 10;
while ((M--) > 0) {
count++;
}
System.out.println(count);
}
分析可得,基本操作次数:F(N) = 2*N +10
- 根据大O渐进表示法
(1)取最高项:F(N) = 2*N
(2)将最高项的系数化为1:F(N) = N
即该算法时间复杂度为O(n)
举例3:
void Func3(int N, int M) {
int count = 0;
for (int k = 0; k < M; k++) {
count++;
}
for (int k = 0; k < N ; k++) {
count++;
}
System.out.println(count);
}
基本操作次数:F(M+N) = M+N
此时有两个可变操作数,系数都为1;
所以时间复杂度为:O(n+m)
举例4:
void Func4(int N) {
int count = 0;
for (int k = 0; k < 100; k++) {
count++;
}
System.out.println(count);
}
分析可知基本操作次数为:F(N)=100
没有可变的操作数,直接将系数化为1即可。
所以时间复杂度为O(1)
举例5:
// 计算 String 类的 indexOf 的时间复杂度?
int String.indexOf(int ch);
分析可得:该算法在最好情况下执行一次,在最坏情况下执行n次
所以取最坏情况。
时间复杂度为:O(n)
举例6:
// 计算冒泡排序的时间复杂度
void bubbleSort(int[] array){
for(int i = 0 ; i < array.length-1; i++){
//每次冒泡之前,假设数组已经存在
boolean sorted = true;
for(int j = 0 ; j <= arrary.length-2-i; j++){
if(array[j] > array[j+1]{
int t = array[j];
array[j] = array[j+1];
array[j+1] = t;
sorted = false;
}
}
if(sorted == true){
break;
}
}
}
}
分析得:在最好情况下,需要n次基本操作;在最坏情况下,需要 n*(n-1)次操作。取最坏情况,则基本操作数为 F(n) = n*(n-1)/2=1/2n^2-1/2n.
(1)取最高次项: F(n) =1/2 n^2
(2)最高次项系数化为1:F(n) = n^2;
所以该算法得时间复杂度为O( n^2)
举例7:
// 计算二分查找的时间复杂度?
int BinarySearch(int[] array, int value) {
int begin = 0;
int end = array.length - 1;
while (begin <= end) {
int mid = begin + ((end-begin) / 2);
if (array[mid] < value)
begin = mid + 1;
else if (array[mid] > value)
end = mid - 1;
else
return mid;
}
return -1;
}
基本操作执行最好1次,最坏O(logN)次,时间复杂度为 O(logN)
举例9:
// 计算阶乘递归Factorial的时间复杂度?
long Factorial(int N) {
return N < 2 ? N : Factorial(N-1) * N;
}
通过计算分析发现基本操作递归了N次,时间复杂度为O(N)
举例10:
// 计算斐波那契递归Fibonacci的时间复杂度?
long Fibonacci(int N) {
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}
例如n= 5时:
通过分析发现基本操作递归了2 ^N次,
时间复杂度为O(2^N)。
(四)空间复杂度
大O渐进表示法:
数据规模是n得情况下:
(1)额外使用的空间大小(不考虑输入/输出中用到的空间)
(2)常见形式:
i:int[] array = new array[];
ii: 递归函数
举例1:
// 计算冒泡排序的空间复杂度
void bubbleSort(int[] array){
for(int i = 0 ; i < array.length-1; i++){
boolean sorted = true;
for(int j = 0 ; j <= arrary.length-2-i; j++){
if(array[j] > array[j+1]{
int t = array[j];
array[j] = array[j+1];
array[j+1] = t;
sorted = false;
}
}
if(sorted == true){
break;
}
}
}
}
使用了常数个额外空间,所以空间复杂度为 O(1)
举例2:
// 计算Fibonacci的空间复杂度?
long[] Fibonacci(int n)
{
long[] fibArray = new long[n + 1];
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; i++) {
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}
动态开辟了N个空间,空间复杂度为 O(N)。
举例3:
// 计算阶乘递归Factorial的空间复杂度?
long Factorial(int N) {
return N < 2 ? N : Factorial(N-1)*N;
}
动态开辟了N个空间,空间复杂度为 O(N)。