数据结构-前置概念 - 指南

时间和空间复杂度

时间复杂度

概念

算法的时间复杂度是一个科学函数,定量描述了算法的运行时间算法中基本操作的执行次数就是算法的时间复杂度

大O的渐进表示法

常数1取代运行时间中所有的加法常数,仅保留最高阶项,且最高阶项的系数恒为1

有些算法的的时间复杂度存在最好,平均和最坏情况(默认关注情况)。

计算时间复杂度:

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;
}

递归的时间复杂度=递归的次数*每次递归的执行次数

long factorial(int N) {
 return N < 2 ? N : factorial(N-1) * N;
}

O(N)->忽略-1

int fibonacci(int N) {
 return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
}

O(2^N)

总结:计算时间复杂度要结合思想,不能一味的观察代码

空间复杂度

对一个算法在运行过程中临时占用储存空间大小的量度,计算的是变量的个数(额外的内存),也是用大O渐进表示法

1.冒泡排序的空间复杂度是O(1)

2.

int[] 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;
}

O(N)

3.

long factorial(int N) {
 return N < 2 ? N : factorial(N-1)*N;
}

每一次递归都会给函数开辟内存

O(N)

常遇到复杂度有O(1),O(logN),O(N),O(N*logN),O(N^2)   未特殊声明,log都是以2为底

包装类

在Java中由于基本类型不是继承于Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型

除了Integer和Character,其余基本类型的包装类都是首字母大写   

装箱/装包:把基本数据类型变为包装类型

        int a=10;
        Integer a1=Integer.valueOf(a);//显式装箱
        Integer a2=new Integer(a);
        Integer a3=a;//隐式装箱
        Integer a4=(Integer)a;

拆箱/拆包:包装类型变为基本数据类型

        int a=10;
        Integer a1=Integer.valueOf(a);
        int a2= a1.intValue();//显式拆箱
        int a3=a1;//自动拆箱
        int a4=(int)a1;

面试题:

            Integer a=100;
            Integer b=100;
            System.out.println(a==b);
            Integer c=200;
            Integer d=200;
            System.out.println(c==d);

原因:

Integer装箱的源码:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

其中IntegerCache.low =-128  IntegerCache.higt =127

可以发现,凡是符合[-128,127]的256个数据都储存在了数组里,那么a和b的地址就是一样的,c和d的地址就不一样

想要比较值可以用.equals()

泛型

泛型是JDK1.5引入的新的语法,通俗讲就是:适用于许多许多类型。从代码上讲,就是对类型实现了参数化

引出泛型

泛型的主要目的是:指定当前容器,要持有什么类型的对象。让编译器去做检查

语法

声明:

class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
    // 这里可以使用类型参数
}

使用:

泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
MyArray list = new MyArray();

裸类型:

表示一个泛型类但是没有带着类型实参

MyArray list = new MyArray();

这是为了兼容老版本的API所保留的机制,我们不要去主动的使用

class Myarray{
    public Object[] array=new Object[10];
    public void setArray(int pos,T value){
        array[pos]=value;
    }
    public T getArray(int pos){
        return (T)array[pos];
    }
}
 public static void main(String[] args){
            Myarray myarray1 =new Myarray();
            myarray1.setArray(0,22);
        Myarray myarray2 =new Myarray();
            myarray2.setArray(0,"Hello");
            String str=(String)myarray2.getArray(0);
    }

注意:

1.类名后<T>表示占位符,表示当前类是一个泛型类,类型必须是包装类

2.泛型是将数据类型参数化,进行传递

3.new 对象时后面<>可以不写类型

4.泛型目前为止的优点是:数据类型参数化,编译时自动进行类型检查和转换

了解:类型的形参一般使用一个大写字母表示

E:Element

K:Key

V:Value

N:Number

T:Type

泛型是如何编译的

擦除机制

在编译过程中将所有的T替换为Object这种机制称为擦除机制,以下是介绍https://zhuanlan.zhihu.com/p/51452375https://zhuanlan.zhihu.com/p/51452375

此时尖括号当中的内容不参与类型的组成

Myarray myarray1 =new Myarray();
Myarray myarray2 =new Myarray();

myarray1和myarray2的类型就是Myarray类型

问题解答:

问:T[] ts =new T[5];编译T替换为Object,不是相当于:Object[] ts = new Object[5]吗?

答:1.Java在运行时会严格检查元素特性,而泛型编译后会发生“类型擦除”,泛型T的具体类型信息会被丢弃(如果没有指定边界,那被擦除为Object),这意味着创建数组时无法知道T是什么类型

问:类型擦除,一定是把T变成Object吗?

答:不一定。无界类型擦除为Object有界类型擦除为对应的上界,多边界泛型擦除为第一个上界类型

泛型的上界

class 泛型类名称<类型形参 extends 类型边界> {
   ...
}
//
public class MyArray {
   ...
}

此时只接受Number或者Number的子类作为E的类型实参

MyArray l1; // 正常,因为 Integer 是 Number 的子类型
MyArray l2; // 编译错误,因为 String 不是 Number 的子类型

没有指定E的边界那么可以视为 E extends Object

复杂示例:

public class Person implements Comparable{
   public String name;
    public int age;
  public int compareTo(Person o){
      return this.name.compareTo(o.name);
  }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Max>{
    public T toMax(T[] arrays){
        T tmp=arrays[0];
        for(int i=1;i max=new Max<>();
            Integer i=max.toMax(array);
            Max max2=new Max<>();
            System.out.println(max2.toMax(ps));
            System.out.println(i);
    }
}

泛型方法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
//静态的泛型方法 需要在static后用<>声明泛型类型参数
class Max{
    public static>  T toMax(T[] arrays){//静态方法
        T tmp=arrays[0];
        for(int i=1;itoMax(ps));//不使用类型推导
    }
}

JDK17新增的特性

var关键字

从Java10开始,var被引用,用于局部变量

         var num =10;
         var str="Hello";

自动识别后面的类型,更加简洁

注意:var不能声明字段(属于类的变量),方法参数,返回值且必须初始化(不能初始化为null)

密封类

sealed修饰也表示密封类

sealed class Animal permits Dog, Dog1 {
}
//Dog1也是密封类
final class Dog1 extends Animal{
}
//Dog无限制,任何类都可继承
non-sealed class Dog extends Animal{
}

sealed修饰的类必须有子类,而且子类必须是final,sealed,non-sealed

没有写permits表示都允许

接口的私有方法

Java8,接口可以有默认方法,Java9之后,接口内可以实现私有方法,仅为接口提供服务

instanceof

允许在判断类型的同时,声明一个变量

         Object obj="Hello";
         if(obj instanceof String str){
             str="Hello";
         }

posted @ 2025-12-03 10:34  gccbuaa  阅读(11)  评论(0)    收藏  举报