目录
时间频度介绍和特点
算法的时间复杂度
基本介绍
一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)
基本案例
比如计算1-100所有数字之和,我们设计两种算法:
int total=0;
int end=100;
//使用for循环计算
for(int i=1;i<=end;i++){
total+=i;
}
T(n)=n+1;
//直接计算
total=(1+end)*end/2;
T(n)=1;
①忽略常数项
结论
1)2n+20和2n随着n变大,执行曲线无限接近,20可以忽略
2)3n+10和3n随着n变大,执行曲线无限接近,10可以忽略
②忽略低次项
结论
1)2n^2+3n+10和2n^2随着n变大,执行曲线无限接近,可以忽略3n+10
2)n^2+5n+20和n^2随着n变大,执行曲线无限接近,可以忽略5n+20
③忽略系数
结论
1)随着n值变大,5n^2+7n和3n^2+2n,执行曲线重合,说明这种情况下,5和3可以忽略
2)而n^3+5n和6n^3+4n,执行曲线分离,说明多少次方式关键
时间复杂度计算和举例说明
时间复杂度
1)一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=0(f(n)),称O(f(n))为算法的渐进时间复杂度,简称时间复杂度。
2)T(n)不同,但时间复杂度可能相同,如:T(n)=n^2+7n+6与T(n)=3n^2+2n+2它们的T(n)不同,但时间复杂度相同,都为O(n^2)
3)计算时间复杂度的方法
用常数1代替运行时间中的所有加法常数 T(n)=n^2+7n+6=>T(n)=n^2+7n+1
修改后的运行次数函数中,只保留最高阶项 T(n)=n^2+7n+1=>T(n)=3n^2
去除最高阶项的系数 T(n)=n^2=>T(n)=n^2=>O(n^2)
常见的时间复杂度
1)常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
int i=1;
int j=2;
++i;
j++;
int m=i+1;
上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。
2)对数阶O(log2n)
int i=1;
while(i<n)
{
i=i*2;
}
说明:在while循环里面,每次都将i乘以2,乘完之后,i距离n就越来越接近了,假设循环x次之后,i就大于2了,此时这个循环就退出了,也就是说2的x次方等于n,那么x=log2n也就是说当循环log2n也就是说当循环log2n次以后,这个代码就结束了,因此这个代码的时间复杂度为:O(log2n),O(log2n)的这个2时间上是根据代码变化的,i=i*3,则是O(log3n).
3)线性阶O(n)
for(i=1;i<=n;++i)
{
j=i;
j++;
}
说明:这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。
4)线性对数阶O(nlog2n)
for(m=1;m<n;m++)
{
i=1;
while(i<n)
{
i=i*2;
}
}
说明:线性对数阶O(nlogN)其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是n*O(logN),也就是了O(nlogN)
5)平方阶O(n^2)
for(x=1;i<=n;x++)
{
for(i=1;i<=n;i++)
{
j=i;
j++;
}
}
说明:平方阶O(n^2)就更容易理解了,如果把O(n)的代码再嵌套循环一遍,它的时间复杂度就是O(n^2),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是O(n*n),即O(n^2)如果将其中一层循环的n改成m,那它的时间复杂度就变成了O(m*n)
6)立方阶O(n^3)
7)k次方阶O(n^k)
说明:参考上面的O(n^2)去理解就好了,O(n^3)相当于三层n循环,其它的类似
8)指数阶O(2^n)
说明:常见的算法时间复杂度由小到大依次为:O(1)<O(log2n)<O(n)<O(nlog2n)<O(n^2)<O(n^3)<O(n^k)<O(2^n),随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
从图中可知,我们应该尽可能避免使用指数阶的算法
平均和最坏时间复杂度
1)平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间
2)最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
3)平均时间复杂度和最坏时间复杂度是否一致,和算法有关。
基本介绍
1)类似于时间复杂度的讨论,一个算法的空间复杂度定义为该算法所耗费的存储空间,它也是问题规模n的函数。
2)空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。有时算法需要占用的临时工作单位数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法就属于这种情况。
3)在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。一些缓存产品的算法本质就是用空间换时间。
冒泡排序算法思路图解
冒泡排序的基本思路是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就像水底下的气泡一样逐渐向上冒。
因为排序的过程,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
图解冒泡排序算法的过程
原始数组:3,9,-1,10,20
第一趟排序
(1)3,9,-1,10,20 //如果相邻的元素逆序就交换
(2)3,-1,9,10,20
(3)3,-1,9,10,20
第二趟排序
(1)-1,3,9,10,20
(2)-1,3,9,10,20
(3)-1,3,9,10,20
第三趟排序
(1)-1,3,9,10,20
(2)-1,3,9,10,20
第四趟排序
(1)-1,3,9,10,20
小结冒泡排序规则
(1)一共进行数组的大学-1次大的循环
(2)每一趟排序的次数在逐渐的减少
(3)如果我们发现在某趟排序中国,没有发生过一次交换,可以提前结束冒泡排序。
代码一
package sort;
import java.util.Arrays;
public class BuubleSort {
public static void main(String[] args) {
int arr[]= {3,9,-1,10,-2};
//为了容易理解,我们把冒泡排序的演变过程,给大家展示
//第一趟排序,就是将最大的数排在最后
int temp=0;//临时变量
for(int j=0;j<arr.length-1;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第一趟排序后的数组");
System.out.println(Arrays.toString(arr));
//第二趟排序,就是将第二大的数排在倒数第二位
for(int j=0;j<arr.length-1-1;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第二趟排序后的数组");
System.out.println(Arrays.toString(arr));
//第三趟排序,就是将第二大的数排在倒数第三位
for(int j=0;j<arr.length-1-2;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第三趟排序后的数组");
System.out.println(Arrays.toString(arr));
//第四趟排序,就是将第二大的数排在倒数第四位
for(int j=0;j<arr.length-1-3;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第四趟排序后的数组");
System.out.println(Arrays.toString(arr));
}
}
运行结果
代码二
package sort;
import java.util.Arrays;
public class BuubleSort {
public static void main(String[] args) {
int arr[]= {3,9,-1,10,20};
//为了容易理解,我们把冒泡排序的演变过程,给大家展示
//第一趟排序,就是将最大的数排在最后
int temp=0;//临时变量
boolean flag=false;//标识变量,表示是否进行过交换
for (int i = 0; i < arr.length-1; i++) {
for(int j=0;j<arr.length-1-i;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
flag=true;
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第"+(i+1)+"一趟排序后的数组");
System.out.println(Arrays.toString(arr));
if (!flag) {//在一趟排序中,一次交换都没有发生过
break;
}else {
flag=false;//重置flag,进行下次判断
}
}
运行结果
代码三
package sort;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class BuubleSort {
public static void main(String[] args) {
// int arr[]= {3,9,-1,10,20};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
//为了容易理解,我们把冒泡排序的演变过程,给大家展示
//测试一下冒泡排序的速度O(n^2),给80000个数据,测试
//创建要给80000个的随机的数组
int[] arr=new int[80000];
for (int i = 0; i <80000; i++) {
arr[i]=(int)(Math.random()*80000);//生成一个[0,8000000]数
}
Date data1=new Date();
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str=simpleDateFormat.format(data1);
System.out.println("排序前的时间是:"+date1Str);
//测试冒泡排序
bubbleSort(arr);
Date data2=new Date();
String date2Str=simpleDateFormat.format(data2);
System.out.println("排序后的时间是:"+date2Str);
// System.out.println("排序后");
// System.out.println(Arrays.toString(arr));
//第一趟排序,就是将最大的数排在最后
/* int temp=0;//临时变量
boolean flag=false;//标识变量,表示是否进行过交换
for (int i = 0; i < arr.length-1; i++) {
for(int j=0;j<arr.length-1-i;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
flag=true;
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第"+(i+1)+"一趟排序后的数组");
System.out.println(Arrays.toString(arr));
if (!flag) {//在一趟排序中,一次交换都没有发生过
break;
}else {
flag=false;//重置flag,进行下次判断
}
}*/
/* //第二趟排序,就是将第二大的数排在倒数第二位
for(int j=0;j<arr.length-1-1;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第二趟排序后的数组");
System.out.println(Arrays.toString(arr));
//第三趟排序,就是将第二大的数排在倒数第三位
for(int j=0;j<arr.length-1-2;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第三趟排序后的数组");
System.out.println(Arrays.toString(arr));
//第四趟排序,就是将第二大的数排在倒数第四位
for(int j=0;j<arr.length-1-3;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第四趟排序后的数组");
System.out.println(Arrays.toString(arr));*/
}
//将前面的冒泡排序算法,封装成一个算法
public static void bubbleSort(int[] arr) {
int temp=0;//临时变量
boolean flag=false;//标识变量,表示是否进行过交换
for (int i = 0; i < arr.length-1; i++) {
for(int j=0;j<arr.length-1-i;j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]) {
flag=true;
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第"+(i+1)+"一趟排序后的数组");
System.out.println(Arrays.toString(arr));
if (!flag) {//在一趟排序中,一次交换都没有发生过
break;
}else {
flag=false;//重置flag,进行下次判断
}
}
}
}