赞
踩
在算法的分析中,语句的执行次数T(n)是一个关于n(问题规模)的一个函数。分析n的变化引起T(n)的改变,进而得到T(n)的数量级,也就是时间频率。如果存在某一个辅助函数f(n),当n趋于无穷大时,T(n)/f(n)的值为一个不为0的常数,有T(n)=O(f(n)),这就是算法的渐进时间复杂度,也就是我们常说的时间复杂度。
大O表示法:用O(f(n))来体现时间复杂度的方法被称作大O表示法;
大O推导法:
O(1)叫做常数阶;O(n)叫做线性阶;O(n^2)叫做平方阶。
举一个简单的例子
int i;
for(i=0;i<n;i++)//该语句的复杂度为O(n)
{
cout<<i; //该语句的复杂度为O(1);
}
这段代码的时间复杂度为O(n),其中“cout<<i”的执行次数为1,是常量阶,所以它的复杂度为O(1);“for(i=0;i<n;i++)”的执行次数是n,是线性阶,所以它的复杂度为O(n)。整段代码的复杂度为O(1*n),也就是O(n).
常数阶O(1), 如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句, 其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。 对数阶O(log2 n), 线性阶O(n), 线性对数阶O(n log2 n), 平方阶O(n^2), 立方阶O(n^3) k次方阶O(n^K), 指数阶O(2^n)。 其他时间复杂度都会随着n的变化慢慢变大,算法开销也越来越大 。
常见的时间复杂度所损耗时间排序:
O(1)<O(log n)<O(n)<O(nlogn)<O(n ^2)<O(n ^3)<O(2 ^n)<O(n!)<O(n ^n)
空间复杂度指的是一个程序在执行时,所占有的临时内存空间大小:S(n)=O(f(n));n是问题的规模,f(n)为语句关于n所占据的内存的函数。
举个简单的例子:
交换两个变量的值,它需要定义一个临时的变量,这就造成了空间复杂度,因为是常量阶,所以它的空间复杂度为O(1).
int i = 10, j = 100;//两个需要交换的值
//交换变量的值
int temp=i;//定义临时变量
i=j;
j=temp;
提到空间复杂度,就需要提一下递归了,在进行递归的算法的时候,每一次的递归都会使用临时的变量保存递归的信息,所以递归的方法很消耗内存,也就是空间复杂度相对较高。如果递归次数过多,会造成内存超载,无法计算得到需要的数据。所以,当循环的次数过多的时候,尽量不要使用递归方法。
将一个记录插入到已排好序的序列中,从而得到一个新的有序序列
将序列的第一个数据看成是一个有序的子序列,然后从第二个记录逐个向该有序的子序列进行有序的插入,直至整个序列有序
#include<iostream> using namespace std; void printArray(int *arr, int len) { for (int i = 0; i < len; ++i) { cout << arr[i] << " "; } cout << endl; } void InsertSort(int *arr, int len) { for (int i = 1; i < len; ++i) { if (arr[i] > arr[i - 1]) { int temp = arr[i]; int j = i - 1; for (; j >= 0 && temp > arr[j]; j--) { arr[j + 1] = arr[j]; } arr[j + 1] = temp; } } } int main() { int arr[] = { 4,5,8,9,1,2 }; int len = sizeof(arr) / sizeof(int);//通过字节计算数组大小 printArray(arr, len); InsertSort(arr, len); printArray(arr, len); return 0; }
1、当初始序列为正序时,只需要外循环n-1次,每次进行一次比较,无需移动元素。此时比较次数(C min)和移动次数(M min)达到最小值。
C min=n-1;
M min=0;
此时时间复杂度为O(n).
2、初始序列为反序时,需要外循环n-1次,每次排序中待插入的元素都要和[0,i-1]中的i个元素进行比较且要将这i个元素后移i次,此时比较次数和移动次数达到最大值。
C min=1+2+3+4+……+n-1=(n-1)n/2;
M min=1+2+3+……+n-1=(n-1)n/2;
此时时间复杂度为O(n^2).
3、在直接插入排序中只使用了i,j,tmp这三个辅助元素,与问题规模无关,空间复杂度为O(1)
重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故称为"冒泡排序"。
#include<iostream> using namespace std; void BubbleSort(int *a, int size) { for (int i = 0; i < size; i++)//外循环,循环每个元素 { for (int j = 1; j < size - i; j++)//内循环进行元素的两两比较 { if (a[j] < a[j - 1])//判断相邻元素并进行交换 { int temp = a[j]; a[j] = a[j - 1]; a[j - 1] = temp; } } } } int main() { int a[10] = { 2, 7, 34, 54, 12, 5, 19, 33, 88, 23 }; cout << "原来的数组为:" << endl; for (int i = 0; i < 10; i++) { cout << a[i] << " "; } cout << endl; BubbleSort(a, 10); cout << "冒泡排序后的数组为:" << endl; for (int i = 0; i < 10; i++) { cout << a[i] << " "; } return 0; }
外循环和内循环以及判断和交换元素的时间开销。
最优的情况也就是开始就已经排序好序了,那么就可以不用交换元素了,由于外层循环为n,内层所需要循环比较的次数为(n-1)、(n-2)…1由等差数列求和得时间花销为:[ n(n-1) ] / 2;所以最优的情况时间复杂度为:O( n^2 )。
最差的情况也就是开始的时候元素是逆序的,那么每一次排序都要交换两个元素,则时间花销为:[ 3n(n-1) ] / 2;(其中比上面最优的情况所花的时间就是在于交换元素的三个步骤);所以最差的情况下时间复杂度为:O( n^2 );
冒泡排序的辅助变量空间仅仅是一个临时变量,并且不会随着排序规模的扩大而进行改变,所以空间复杂度为O(1)。
void Efferve() { int m[5] = { 12, 8, 6, 9, 10 }; int max = m[0]; for (int i = 0; i < 4; i++) { for (int j = i; j < 4; j++) { if (m[j] < m[j + 1]) { max = m[j + 1]; m[j + 1] = m[j]; m[j] = max; } } } }
上面的代码可以看出选择排序套用了两个循环如下:
for (int i = 0; i < 4; i++)
{
for (int j = i; j < 4; j++)
{
}
}
当i=0下面循环4次,每次i+1下面循环都执行次4-i次,因此上述循环次数为T=(4-1))+ (4 -2)+(4 - 3)+ 1;
T=[4*(4-1)]/2次
哪当N个数进行排序时,将进行T=[N*(N-1)]/2次,根据计算方法保留最高次N ^2,因此选择排序的时间复杂度为O(N ^2);
因为排序中始终只用到了数组大小的空间,为常数,因此空间复杂度为O(1)。
在一个数组中,找一个数为基准数,将这个数中所有比基准数大的数放在该数的右边,比基准数小的数放在该数的左边。
例如"6 1 2 7 9 3 4 5 10 8"这个数组
以6作为基准数,将比6小的数放在6的组左边,比6大的数放在6的右边
得到:3 1 2 5 4 6 9 7 10 8
可以看出6的左边的数都比6小,而右边的数都比6大,此时6已经归位
具体步骤为:
void QuickSort(int *n, int left, int right) { if (left > right) return ; int temp = n[left]; //temp中存的数为基准数 int i = left, j = right; int t; while (i != j) { //一定要先从右向左找 while (i < j && n[j] >= temp) j--; while (i < j && n[i] <= temp) i++; if (i < j) { t = n[i]; n[i] = n[j]; n[j] = t; } } n[left] = n[i]; n[i] = temp; QuickSort(n, left, i - 1); //处理左边的数 QuickSort(n, i + 1, right); //处理右边的数 }
空间复杂度:logn
主要是由于递归造成的栈空间的使用,
最好的情况下其树的深度为:log2(n)
空间复杂度为 O(logn)
而最坏的情况下:需要n-1次调用,每2个数都需要交换,此时退化为冒泡排序
空间复杂度为 O(n)
平均时间复杂度为:O(logn)
时间复杂度:O(nlogn)
由于快速排序用到了递归调用,因此计算其时间复杂度也需要用到递归算法计算
递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n)
**
最优情况下时间复杂度
快速排序最优的情况就是每一次取到的元素都刚好平分整个数组
此时的时间复杂度公式则为:T[n] = 2T[n/2] + f(n);T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间
第一次递归:
T[n] = 2T[n/2] + n;
第二次递归:
令 n = n/2
= 2^2 T[ n/ (2^2) ] + 2n
第三次递归:
令:n = n/(2^2)
= 2^3 T[ n/ (2^3) ] + 3n
…
第m次递归:
令:n = n/( 2^(m-1) )
= 2^m T[1] + mn
当最后平分的不能再平分时,也就是说把公式一直往下跌倒,到最后得到T[1]时,说明这个公式已经迭代完了(T[1]是常量了)。
得到:T[n/ (2^m) ] = T[1] ===>> n = 2^m ====>> m = logn;
T[n] = 2^m T[1] + mn ;其中m = logn;
T[n] = 2^(logn)T[1]+lognn = n +nlogn
又因为当n >= 2时:nlogn >= n (也就是logn > 1),所以取后面的 nlogn;
综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )
最差情况下时间复杂度
最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)
此时的时间复杂度为:T[n] = n * (n-1) = n^2 + n;
综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )
平均时间复杂度
快速排序的平均时间复杂度也是:O(nlogn)
参考:
https://blog.csdn.net/not_in_mountain/article/details/77976743
本博文由小队成员(Wu_0526,qq_49032326,ly1196324806,SamGeren,qq_39124199)共同编写
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。