zoukankan      html  css  js  c++  java
  • 数据结构和算法分析 引论+算法分析

    数学知识复习

    级数运算

    常用的有:

    递归算法

    递归一般可以条件性的拆分为:

    1. 基准情况:不用递归的那一部分。
    2. 不管推进:递归调用的递归是朝着一个基准情况的方向在推进。

    一个简单递归的例子:打印出正整数。基于一个只能打印一个0-9的数字的方法,打印正整数。

    如果定义可以打印0-9的数字的方法为g(x),打印正整数的那么递归的推进表达式可写为为:

    f(x) = f(x/10) + g(x%10)

    其中g(x)是基准情况。

    代码实现为:

    1. package com.zjf;
    2.  
    3. public class Recursive {
    4.  
    5.    public static void main(String[] args) {
    6.       printInt(15623);
    7.    }
    8.  
    9.    public static void printInt(int i)
    10.    {
    11.       if(i > 10)
    12.       {
    13.          printInt(i/10);
    14.       }
    15.       printDigit(i%10);
    16.  
    17.    }
    18.    public static void printDigit(int i )
    19.    {
    20.       if(9>= i && i >= 0)
    21.       {
    22.          System.out.print(i);
    23.       }
    24.       else
    25.       {
    26.          throw new IllegalArgumentException();
    27.       }
    28.    }
    29.  
    30. }

    注意,一个递归f(x),在递归调用f(x)的时候,必须要终止条件。如上面的  if(i > 10)。否则将会陷入死循环。

    泛型类型限界

    Comparable接口是泛型接口,涉及子类和超类的的情况下,我们一般在需要对比的超类级别上定义对比方法,子类如果覆盖了超类的对比方法,那么这个超类的不同子类之间就不能比较了,代码如下:

    1. package com.zjf;
    2.  
    3.  
    4. public class GenericTest {
    5.  
    6.    public static void main(String[] args) {
    7.       Person zjf = new Person("zjf",30);
    8.       Man zdw = new Man("zdw",27);
    9.       Woman xhj = new Woman("xhj",30);
    10.       System.out.println(zjf.compareTo(zdw));
    11.       System.out.println(zdw.compareTo(xhj));
    12.    }
    13.  
    14. }
    15.  
    16. class Person implements Comparable<Person>{
    17.    private String name;
    18.    private Integer age;
    19.  
    20.    public Person(String name, Integer age) {
    21.       super();
    22.       this.name = name;
    23.       this.age = age;
    24.    }
    25.  
    26.    @Override
    27.    public int compareTo(Person o) {
    28.       return this.age.compareTo(o.age);
    29.    }
    30. }
    31.  
    32. class Man extends Person{
    33.  
    34.    public Man(String name, Integer age) {
    35.       super(name, age);
    36.    }
    37.  
    38. }
    39.  
    40. class Woman extends Person{
    41.  
    42.    public Woman(String name, Integer age) {
    43.       super(name, age);
    44.    }
    45.  
    46. }

    运行结果没有问题,由于都是使用Person的对比方法,所以不同子类之间可以对比。

    如果此时Woman实现了Comparable< Woman>,那么Woman和Man就不能相互比较了。

    我们来实践一下:

    报错了,错误信息是:The interface Comparable cannot be implemented more than once with different arguments:

    Comparable<Person> and Comparable<Woman>

    因为在java中,Comparable<Person> and Comparable<Woman>和在运行时是一样的,都是Comparable,所以我们不能Woman继承了Person,其实已经实现了Comparable,这里等于再次实现了一次,编译上是通不过的。

    那么如果我们不再次实现Comparable,只是试图重写并覆盖超类的compareTo方法呢,如下:

    1. class Woman extends Person {
    2.  
    3.    public Woman(String name, Integer age) {
    4.       super(name, age);
    5.    }
    6.  
    7.    @Override
    8.    public int compareTo(Person o) {
    9.       // TODO Auto-generated method stub
    10.       return super.compareTo(o);
    11.    }
    12. }

    这样,即使要重写,参数也只能是Person类型。这里可以做强制转换。但是很不优雅了。

    同样,如果使用不加泛型的Comparable,那么所有的参数都是Object类型的,也不是很优雅。

    由于Java的泛型不能区分Comparable<Person> and Comparable<Woman>,所以只能如此了。

    不再纠结这个问题,现在,基于上面的设计,如果我们需要定义一个static的方法,用于实现取一个数组最大值的工具方法。

    第一种方法,使用非泛型方法:

    1. public static Comparable findMax(Comparable[] arr)
    2.    {
    3.       return ...;
    4.    }

    这种方法,返回类型只能是Comparable的,在使用的时候要强制转换。

    下面使用泛型方法重写:

    最简单的方法,使用<T extends Comparable>,代码如下:

    1. public static <T extends Comparable> T findMax(T[] arr)
    2.    {
    3.       T max = arr[0];
    4.       for(int i = 1; i<arr.length; i++)
    5.       {
    6.          if(max.compareTo(arr[i]) < 0)
    7.          {
    8.             max = arr[i];
    9.          }
    10.       }
    11.       return max;
    12.    }

    按照作者的说法,这种写法不太优雅。作者进一步又觉得改造成:

    <T extends Comparable<T>>

    但是这个不能表示Man,因为Man实现的 是Comparable<Person>,而不是Comparable<Man>.

    如下:

    所以作者又进一步改成了:

    <T extends Comparable<? super T>>

    这样作者就满足了。。

    事实上,我在实验的时候,如通过如下代码试验:

    1. public static void main(String[] args) {
    2.       Person zjf = new Person("zjf",30);
    3.       Man zdw = new Man("zdw",27);
    4.       Woman xhj = new Woman("xhj",30);
    5.       Person[] c = new Person[]{zjf,zdw,xhj};
    6.       findMax(c);
    7.    }

    上面的几种写法,编译器都是一样认可的。因为我声明数组的时候是用的Person。对于编译器来说,一个  Person对象满足了 <T extends Comparable>,<T extends Comparable<T>> , <T extends Comparable<? super T>>三种。

    思考:什么时候使用泛型方法?

    当一组操作针对多种类型参数时使用,比如上面的findMax方法,它要对一组继承自Comparable接口的数据进行处理。特别参数中是对数组和集合进行操作,其实结果返回的是集合中的具体类型

    运行时间计算

    一个简单的例子:

    1. public static long getSum(int n){
    2.       long sum = 0;
    3.       for(int i = 0;i< n;i++)
    4.       {
    5.          sum += n*n*n;
    6.       }
    7.       return n;
    8.    }

    时间复杂度在意的是随着N的扩大,极限情况下的复杂度。这里假设第5行的时间复杂度为10,第2行的为1,那么这方法的时间复杂度为10N + 1,我们记为O(N)。

    常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)

    最大子序列和问题求解

    最大子序列和:求一个数组中所有子序列中和最大的那个和。

    如-1,2,5,-6,3。最大子序列是2,5。和是7.

    以下所有算法都假设

    • 第一种算法:
    1. public static int getBigSum(int[] arr){
    2.       int sum = arr[0];
    3.       for(int i =0; i< arr.length; i++)
    4.       {
    5.          for(int j = i; j < arr.length; j++)
    6.          {
    7.             int sumTemp = 0;
    8.             for(int x = i; x <=j; x++ )
    9.             {
    10.                sumTemp += arr[x];
    11.             }
    12.             if(sumTemp > sum)
    13.             {
    14.                sum = sumTemp;
    15.             }
    16.          }
    17.       }
    18.       return sum;
    19.    }

    我们来看复杂度:

    第一层循环次数为N

    第二层循环次数为(1到N) = N + (N-1) + (N-2) + …1 = N*(N+1)/2 ≈ N2/2。

    第三层循环的次数为:(1到N) + (1到N-1) + (1到N-2) … + (1到1) = N2/2 + (N-1)2/2 + … =(N*(N+1)*(2N+1)/6)/2 = N3/6

    最终的算法复杂度是取第三层执行的次数,也就是O(N3)。

    • 第二种算法:
    1. ublic static int getBigSum(int[] arr){
    2.       int sum = arr[0];
    3.       for(int i =0; i< arr.length; i++)
    4.       {
    5.          for(int j = i; j < arr.length; j++)
    6.          {
    7.             int sumTemp = 0;
    8.             for(int x = i; x <=j; x++ )
    9.             {
    10.                sumTemp += arr[x];
    11.             }
    12.             if(sumTemp > sum)
    13.             {
    14.                sum = sumTemp;
    15.             }
    16.          }
    17.       }
    18.       return sum;
    19.    }

    我们来看复杂度:

    第一层循环次数为N

    第二层循环次数为(1到N) = N + (N-1) + (N-2) + …1 = N*(N+1)/2 ≈ N2/2。

    所以复杂度为O(N2)。

    • 第三种算法:
    1. public static int getBigSum3(int[] arr)
    2.    {
    3.       return getBigSumRec(arr,0,arr.length-1);
    4.    }
    5.  
    6.    /**
    7.     *
    8.     * @param arr 数组
    9.     * @param left 左下标
    10.     * @param right 右下标
    11.     * @return
    12.     */
    13.    public static int getBigSumRec(int[] arr,int left,int right){
    14.       //基准情况
    15.       if(left == right)
    16.       {
    17.          return arr[left];
    18.       }
    19.       //拆分为两半
    20.       int center = (left + right)/2;
    21.       //现在 有两种情况
    22.       //一种是最大值序列不包含中间的center下标的值 那么就是递归左侧getBigSumRec(arr,left,center)或者右侧getBigSumRec(arr,center + 1,right)
    23.       //一种是最大值序列包含中间的center下标的值 肯定不是上面两种情况了,而是:
    24.       //从center向左遍历到left的最大序列值 + center向右表里到right的最大值序列
    25.       //递归计算左侧
    26.       int maxLeftSum = getBigSumRec(arr,left,center);
    27.       //递归计算右侧
    28.       int maxRightSum = getBigSumRec(arr,center + 1,right);
    29.  
    30.       int maxLeftBorderSum = arr[center];
    31.       int leftBorderSum = 0;
    32.       for(int i = center; i >= left; i--)
    33.       {
    34.          leftBorderSum += arr[i];
    35.          if(leftBorderSum > maxLeftBorderSum )
    36.          {
    37.             maxLeftBorderSum = leftBorderSum;
    38.          }
    39.       }
    40.  
    41.       int maxRightBorderSum = arr[center+1];
    42.       int rightBorderSum = 0;
    43.       for(int i = center + 1; i <= right; i++)
    44.       {
    45.          rightBorderSum += arr[i];
    46.          if(rightBorderSum > maxRightBorderSum )
    47.          {
    48.             maxRightBorderSum = rightBorderSum;
    49.          }
    50.       }
    51.       return max3(maxLeftSum,maxRightSum,maxLeftBorderSum + maxRightBorderSum);
    52.    }
    53.    //返回三个数值中的最大值
    54.    private static int max3(int i, int j, int k) {
    55.       int max = i;
    56.       if(j > max)
    57.       {
    58.          max = j;
    59.       }
    60.       if(k > max)
    61.       {
    62.          max = k;
    63.       }
    64.       return max;
    65.    }

    因为第一层循环是是折半递归,所以复杂度为logN,第二层循环是两个for循环,复杂度为N。所以整个复杂度为O(NlogN)。

    • 第三种算法:
    1. public static int getBigSum4(int[] arr){
    2.       //最大序列和
    3.       int sum = arr[0];
    4.       //局部序列和
    5.       int sumTemp = 0;
    6.       //解释一下上面的两个初始值的设置
    7.       //因为只是sum对比和赋值 所以初始值不能设置为0 否则如果全是负值 那么结果会是0
    8.       //因为sumTemp的值是根据+计算出来的 所以初始这可以设置为0
    9.  
    10.       for(int i =0; i< arr.length; i++)
    11.       {
    12.          sumTemp += arr[i];
    13.          if(sumTemp > sum )
    14.          {
    15.             sum = sumTemp;
    16.          }
    17.          //如果局部序列和为负值 那么我们就可以舍弃它 重新开始一个局部序列了
    18.          //因为一个和为负值的局部序列和 不管下一个数值是正负 累加后都会小于下一个数值 所以我们直接从下一个数值开始一个新的序列
    19.          if(sumTemp < 0)
    20.          {
    21.             sumTemp = 0;
    22.          }
    23.       }
    24.       return sum;
    25.    }

    很明显,时间复杂度为O(N)。

    对数复杂度

    如果一个算法用常数时间O(1)将问题的大小削减为其一部分,通常为1/2,那么该算法的复杂度就是O(logN)。

    最简答的例子就是折半查找(二分查找):

    1. public static <T extends Comparable<? super T>> int binarySerch(T[] arr, T t) {
    2.       int left = 0;
    3.       int right = arr.length - 1;
    4.       while(left <= right)
    5.       {
    6.          int middle = (left + right)/2;
    7.          if(arr[middle].compareTo(t) > 0)
    8.          {
    9.             right = middle - 1;
    10.          }
    11.          else if(arr[middle].compareTo(t) < 0)
    12.          {
    13.             left = middle + 1;
    14.          }
    15.          else
    16.          {
    17.             return middle;
    18.          }
    19.       }
    20.       return -1;
    21.    }

    复杂度为O(logN)。

    另外一个例子,幂运算:

    1. public static long pow(long x, int y) {
    2.       long result = 0;
    3.       if (y == 0) {
    4.          return 1;
    5.       }
    6.       if (y == 1) {
    7.          return x;
    8.       }
    9.       // 偶数
    10.       if (y % 2 == 0) {
    11.          long temp = pow(x, y / 2);
    12.          return temp * temp;
    13.          //书上写的是 return power(x*x,y/2)
    14.       }
    15.       // 偶数
    16.       if (y % 2 == 1) {
    17.          long temp = pow(x, y / 2);
    18.          return temp * temp * x;
    19.          //书上写的是 return power(x*x,y/2) * x
    20.       }
    21.       return result;
    22.    }

    时间复杂度为O(logN)

    如果使用y遍循环,做x*x,那么时间复杂度是O(N)。

  • 相关阅读:
    Linux驱动之异常处理体系结构简析
    Linux驱动之按键驱动编写(查询方式)
    Linux驱动之LED驱动编写
    Linux驱动之建立一个hello模块
    Linux驱动之内核加载模块过程分析
    制作根文件系统之制作根文件系统步骤详解
    制作根文件系统之Busybox init进程的启动过程分析
    制作根文件系统之内核如何启动init进程
    制作根文件系统之内核挂接文件系统步骤分析
    Linux移植之tag参数列表解析过程分析
  • 原文地址:https://www.cnblogs.com/xiaolang8762400/p/7128843.html
Copyright © 2011-2022 走看看