首页 » codingbaby

有时候算法的复杂度跟数据的排列由很大关系,这种复杂度不能简单的用一个复杂度来分析,而是由最好,最坏,平均复杂度来分析

1.如下面的,如果x在第0个位置,就是O(1),如果在第n-1个位置,就是O(n),这里就带表了最好和最坏


// n 表示数组 array 的长度,
int find(int[] array, int n, int x) {
  int i = 0;
  int pos = -1;
  for (; i < n; ++i) {
    if (array[i] == x) {
       pos = i;
       break;
    }
  }
  return pos;
}

2.最好最坏的都是极端的,大概率不会发生,那么就引入了平均(情况)时间复杂度,那么x的位置,由n+1种情况,即在0~n-1的位置,还有不在数组种

  • 我们把每种情况的时间复杂度相加,除以可能出现的情况个数,就是平均时间复杂度---即1+2+3+4+...+ n 除以n+1,平均情况时间复杂度就是:n*(n+3)/2(n+1) 分子分母,逐步忽略常量,就是O(n)

3.加权平均时间复杂度,第二步中有点问题,就是没有考虑各种情况的概率,如果x出现在某个位置的概率非常大,那么上面的计算是把所有复杂度的情况都认为是1/(n+1)。

  • 实际上我们可以考虑,x在数组中的概率是1/2,x在每个位置的概率是1/n,那么x在某个位置的概率退化为1/2n,不在数组中的概率为1/2
  • 所以加权平均值是11/2n + 21/2n +...+ n1/2n + n1/2 = (3n+1)/4
  • 第二步的算法,是理想化了,每个情况的权重都看成了1/(n+1)

4.均摊时间复杂度---摊还分析,均摊时间复杂度就是一种特殊的平均时间复杂度,即只有1/n的情况复杂度是n(这里出现的概率很规律,比如数组插入n个数后必然发生扩容,不需要概率),那么均摊下来是O(1),而不需要通过平均计算

big o是算法分析的常用手段,简单的说就是估计算法的执行时间,我们假设每行代码的时间是一样的,所以就可以理解为代码的执行次数.用n表示数据规模,f(n)表示执行次数,那么执行时间T(n) = O(f(n))表示Tn和执行次数fn成正比

1.看个简单的例子,1+ 1 + n + n ,即时间复杂度 (2n+2)*unit_time



 int cal(int n) {
   int sum = 0;//执行1次
   int i = 1;//执行1次
   for (; i <= n; ++i) {//执行n次
     sum = sum + i;//执行n次
   }
   return sum;
 }

2.再看一个例子,T(n) = O(2n2+2n+3),即O表示随着n变化时间的趋势的法则,对于函数的概念,可以理解为映射,这里大O表示次数和时间的映射

int cal(int n) {
   int sum = 0;//1
   int i = 1;//1
   int j = 1;//1
   for (; i <= n; ++i) {//n
     j = 1;//n
     for (; j <= n; ++j) {//n*n
       sum = sum +  i * j;//n*n
     }
   }
 }

3.总结+ 计算手段(下面列的都是): * 大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度

  • 计算手段1:只关注循环执行次数最多的一段代码---我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了
  • 计算手段2:加法法则,如果两段代码是满足加法法则(即没有嵌套),就是次数是相加的,那么最终取最大的----如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n))).
  • 如下满足加法法则,即n方
int cal(int n) {
   int sum_1 = 0;//sum_1 是100次
   int p = 1;
   for (; p < 100; ++p) {
     sum_1 = sum_1 + p;
   }

   int sum_2 = 0;//sum_2是n次
   int q = 1;
   for (; q < n; ++q) {
     sum_2 = sum_2 + q;
   }

   int sum_3 = 0;//sum_3是n的平方
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
     j = 1;
     for (; j <= n; ++j) {
       sum_3 = sum_3 +  i * j;
     }
   }

   return sum_1 + sum_2 + sum_3;
 }
  • 计算手段3:如果两段代码是嵌套的,那么满足乘法法则---如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)T2(n)=O(f(n))O(g(n))=O(f(n)*g(n)).

    • 如下满足乘法法则,即n方
int cal(int n) {
   int ret = 0;
   int i = 1;
   for (; i < n; ++i) {
     ret = ret + f(i);//n次
   }
 }

 int f(int n) {
  int sum = 0;
  int i = 1;
  for (; i < n; ++i) {
    sum = sum + i;//n 次
  }
  return sum;
 }

4.常见的算法复杂度,复杂度分为两类:多项式量级和非多项式量级.非多项式量级只有两个:O(2^n) 和 O(n!),我们把时间复杂度为非多项式量级的算法问题叫做NP问题

  • O(1) 常数级别的都归为O(1),比如下面的代码执行了3次,也是O(1),一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
int i = 8;
 int j = 6;
 int sum = i + j;
  • O(logn)---对数阶算法复杂度,可以简单的理解为一个数除以k(比如k是2,即log2N)多少次可以得到1,如下面的就是

    • 对于log2N,log3N和log10N都记作logN,。因为log3n 就等于 log32 * log2n 而log32是一个常数,所以可以忽略
 i=1;
 while (i <= n)  {
   i = i * 2;
 }
  • 线性阶O(N) O(M+N)
  • 线性对数阶 O(NlogN)
  • 平方阶 O(n^2) ,O(n^3) ...O(n^k)

5.对于空间复杂度,用的最多的是O(1),O(n),O(n^2)。相对简单,不做深入

目前遇到一个需求,需要用fiddler抓取idea插件的http请求。

解决办法,在idea64.exe.vmoptions和idea.exe.vmoptions加入下面的配置


-DproxySet=true
-Dhttp.proxyHost=127.0.0.1
-Dhttp.proxyPort=8888

最近研究http协议,对TCP/IP分层和做下对比,不得不说tcp/ip的设计者真的聪明,用分治的方法来处理复杂的网络处理。

1.TCP/IP分层可以总结为:应用层(如http,一般绑定到端口)向传输层(TCP)层要连接,传输层向IP层要ip路由,IP层向链路层要mac地址寻址。反之亦然。

  • 应用层(http,stmp)
  • 数据传输层(tcp,udp)
  • 网络层(ip)
  • 链路层(mac)
  1. OSI 是为了统一标准设计的
  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层(光缆,网线)

我们可以这么理解,tcp/ip去掉了下面物理层,合并了上面的3层

最近看java 代码优化,总结下java 代码常见调优策略

1.优化代码

  • 比如不要for循环来访问linkedlist,使用iterator

2.优化设计

  • 比如使用单例设计模式来优化对象的创建

3.优化算法

  • 这个可以去研究算法了,提高代码的执行效率

4.时间换空间

  • 空间要求苛刻的场景,不追求时间,节约空间-----比如String.intern,解决字符串的存储空间,访问上略微降低了效率

5.空间换时间

  • mysql的分库分表,也可以理解成一种空间换时间。mysql超过千万级,就会出现读写速度变慢。

6.参数调优

  • 针对业务场景,调节系统,jvm,servlet容器参数,比如直接将大对象放入老年代。

总结:调优策略只是其中一环,上线前我们要做性能基准测试(benchmark)--->自下而上排查性能问题--->确定调优策略(此文种的6种)--->除了调优,还要兜底,因为再怎么调,我们系统也是有极限的,那么兜底策略有限流熔断,还有扩容(根据流量实时扩容,或者预测性提前扩容,比如秒杀活动时,提前扩容。)