数据结构初阶(算法的复杂度 + 包装类 + 泛型)

文章目录

  • 一、算法复杂度
    • 1. 算法效率
    • 2. 时间复杂度
      • (1) O的渐进表示法
    • 3. 空间复杂度
  • 二、包装
    • 2.1 为什么会出现包装
    • 2.2 分类
    • 2.3 装箱和拆箱
      • (1)装箱/装包
      • (2)拆箱/拆箱
  • 三、泛型
    • 3.1 泛型的基本概念
    • 3.2 泛型的使用
    • 3.3 泛型的编译
    • 3.4 泛型的上界

一、算法复杂度

1. 算法效率

  • 我们一般是通过算法的复杂度去判断一个算法的好坏,其评判的标准,一般是从时间、空间两个维度来衡量,即时间复杂度和空间复杂度
  • 时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间
  • 早期,我们更看重于空间复杂度,但随着技术的发展,我们现在更看重时间复杂度

2. 时间复杂度

  • 概念
    • 算法的时间是难以高效率地测出来的,所以我们通过一个函数去定量描述算法的时间复杂度
      • 不能掐表,因为运行的时间和电脑的硬件是有关系的
      • 时间是要运行的时候才能体现出来,不能每次都上机测试,所以时间复杂度是一个分析方式
      • 时间复杂度是一个估算的值
  • 计算:先算出执行的次数,将其转化为函数之后,用O的渐进表示法表示

(1) O的渐进表示法

  • 大O符号(Big O notation):是用于描述函数渐进行为的数学符号

  • 推导大O阶方法

    • 用常数1取代运行时间中的所有加法常数。
    • 在修改后的运行次数函数中,只保留最高阶项。
    • 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶
  • 效果:去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数

  • 结果形式

    • 最坏情况:任意输入规模的最大运行次数(上界)
    • 平均情况:任意输入规模的期望运行次数
    • 最好情况:任意输入规模的最小运行次数(下界)
      在实际中一般情况关注的是算法的最坏运行情况

3. 空间复杂度

概念:对一个算法在运行过程中临时占用存储空间大小的量度,在实际上算的是变量的个数
计算方式:求出变量的个数后,用大O渐进表示法表示

二、包装

2.1 为什么会出现包装

    在Java中,由于基本数据类型不是继承与Object的,为了在泛型代码中可以支持基本类型,Java中每一个基本数据类型都有一个对应的包装类
    对于基本类型来说,包装类中的属性和方法,可以让数据的转换等使用方法更加方便

2.2 分类

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

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

2.3 装箱和拆箱

(1)装箱/装包

  • 概念:基本类型------------>包装类型
  • 实现方式:新建一个 包装类型对象,将基本数据类型的值放入对象的某个属性中
  • 代码示例:
public static void main1(String[] args) {
    int a = 10;
    int b = 20;
    //自动装箱
    Integer i = a;//与Integer i = Integer.valueOf(a);是一样的,JVM会帮着调用一个valueOf方法
    Integer iiii = (Integer) b;

    显示装箱
    int c = 30;
    int d = 40;
    Integer iii = Integer.valueOf(c);//使用类调用的,说明是被static修饰的

    Integer ii = new Integer(d);
}

❤️Integer.valueOf方法介绍

  • 源代码
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  • 含义
    • 如果该 int 类型的值在-127 ~ 128 之间,会缓存在一个数组中,每个数对应的下标是 i + (-IntegerCache.low)
    • 如果不是在-127 ~ 128 之间,会new一个新的对象

(2)拆箱/拆箱

  • 概念:包装类型------------>类型类型
  • 实现方式:将包装类型对象的值取出,放到一个基本数据类型中
  • 代码示例
    public static void main2(String[] args) {
        Integer a = 10;
        //自动拆箱
        int ii = a;
        int iii = (int)a;

        //显示拆箱
        int i = a.intValue();   //用对象调用,说明这是一个类中的方法
        float f = a.floatValue();
    }

三、泛型

3.1 泛型的基本概念

  • 什么是泛型
    泛型就是适用于许多类型,从代码上讲,就是将类型作为参数
  • 泛型的目的
    虽然我们希望泛型适用于许多类型(什么类型的都可以接收),但是如果同时存有不同类型,数据的拿取就会变得很麻烦。
    所以更多情况下,我们还是希望泛型只能持有一种数据类型。
    泛型的主要目的,就是指定当前的容器,要持有什么类型的对象,在编译的时候,让编译器去做检查类型和类型的转换
  • 注意
    Java的泛型机制是在编译级别实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息

3.2 泛型的使用

<>里面一定是个引用类型

语法(1)------------>定义一个泛型类
class 泛型类名称<类型形参列表>{
       //这里可以使用类型参数
}

语法(2)------------>定义一个继承某个类的泛型类
class 泛型类名称<类型形参列表> extends 继承类(这里可以使用类型参数){
       //可以只使用部分类型参数
}

语法(3)------------>定义一个泛型类引用
泛型类<类型实参>变量名;      

语法(4)------------>实例化一个泛型类对象
new 泛型类<类型实参>(构造方法实参);      

语法(5)------------>裸类型
概念:是一个泛型类但是没有带着类型实参,意味着什么都能放,那取出来还要强转
注意:不要主动去使用裸类型,因为这是为了兼容老版本保留的机制

  • 写法注意
class MyArray4<T>{  //定义一个泛型类,T这边的效果就是传什么就是什么,T表示的是这是一个占位符,表示当前类是一个泛型类
    public Object[] objects = new Object[10];   //最好的写法
    //public T[] objects2 = new T[5];   错误,数组在new的时候,后面一定要跟一个具体的类型
    public T[] objects3 = (T[])new Object[5];   //这种可以,但是不好

    public Object getPos(int pos){
        return objects3[pos];
    }
}

public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();   //语法3+语法4,Integer表示指定传的是int类型的
        MyArray<Integer> myArray1 = new MyArray<Integer>();   //裸类型
        //效果和上一行是一样的,实例化对象那边的<>中的包装类型是可以省略的,因为可以根据上下文推导出实例化所需要的类型实参

        //数组在new的时候,后面一定要跟一个具体的类型,因为T这种占位符在运行的时候是不知道是什么的
        //T[] ts = new T[5];
    }
}

  • 规范写法(还是用Object接收,取的时候,强制转换就好了)
class MyArray1<T>{
    public Object[] objects = new Object[10];

    public T getPos(int pos){
        return (T)objects[pos];
    }

    public void setPos(int pos, T val){
        objects[pos] = val;
    }
}

public class Test{
    public static void main(String[] args){
        MyArray1<Integer> myArray1 = new MyArray1<>();
        myArray1.setPos(0, 20);
        System.out.println(myArray1.getPos(0));

        MyArray1<String> myArray2 = new MyArray1<>();
        myArray2.setPos(0, "aaa");
        System.out.println(myArray2.getPos(0));
    }
}

数据结构初阶(算法的复杂度 + 包装类 + 泛型)_第1张图片

3.3 泛型的编译

泛型的编译中需要遵循一个擦除机制
擦除机制就是在编译的过程当中把所有的T替换成Object这种机制,所以在运行的时候是没有T的,即没有泛型的概念

  • 既然T[5] = new T[5]在编译的时候,会把T替换成Object,不是相当于Object[5] = new Object[5]吗?为什么是错的?
    • :因为数组在new的时候后面一定需要的是一个具体的类型
  • 类型擦除,一定是把T变成Object吗?

❤️为什么不能实例化泛型数组
错误示例

class MyArray<T>{
	public T[]array = (T[])new Object[10];

	public T getPos(int pos){
		return this.array[pos];
	}
	
	public void setVal(int pos, T val){
		this.arrar[pos] = val;
	}
	
	public T[] getArray(){
		return array;
	}
}

public class Main{
	public static void main(String[]args){
		MyArrar<Integer> myArray1 = new MyArray<Integer>();
		Integer[] intgers = myArray1.getArray();   //不知道该用什么接收
	}
}

会报错,返回的Object数组里面,可能存放的是任何的数据类型,运行的时间直接传给Integer等类型的数组,会被编译器认为不安全
那怕是强转都不行,因为Object类型表示这里面什么类型都可以放,即可能有char、String等类型,全部都转为Integer会不安全

正确解法

class MyArray1<T>{
    public Object[] objects = new Object[10];

    public T getPos(int pos){
        return (T)objects[pos];
    }

    public void setPos(int pos, T val){
        objects[pos] = val;
    }

    public Object[] getArray(){
        return objects;
    }
}

public class Test{
    public static void main(String[] args){
        MyArray1<Integer> myArray1 = new MyArray1<>();
        myArray1.setPos(0, 20);
        System.out.println(myArray1.getPos(0));

        MyArray1<String> myArray2 = new MyArray1<>();
        myArray2.setPos(0, "aaa");
        System.out.println(myArray2.getPos(0));
    }
}

3.4 泛型的上界

概念:在定义泛型类时,对传入的类型变量通过类型边界作一定的约束
语法
         class 泛型类名称<类型形参 extends 类型边界>{
         }
示例
         class MyArray< E extends Number>{
         }
         E要么是Number的子类,要么就是Number

代码示例 ------------------> 写一个泛型类,求数组中的最大值

<>里面的一定是个引用类型,而引用类型是不可以通过大于和小于进行比较的,因为T在编译的时候会变成Object类,Object类是所有类的父类,本身并没有关联Comparable接口,所以这个泛型类要关联Comparable接口

class Alg<T extends Comparable<T>>{   //擦除为一个实现了这个接口的类型
    public T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            //引用类型不可以通过 大于和小于进行比较
            if(max.compareTo(array[i]) < 0 ) {
                max = array[i];
            }
        }
        return max;
    }
}

public class Test{
    public static void main(String[] args){
        Alg<Integer> alg = new Alg<>();
        Integer[] array = {1, 5, 2, 7, 19, 4};
        Integer max = alg.<Integer>findMax(array);  //可以通过array类判断传的是什么类型的数据
        System.out.println(max);
    }
}
//这是用静态泛型方法去写
class Alg2 {
    public static<T extends Comparable<T>> T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(max.compareTo(array[i]) < 0 ) {
                max = array[i];
            }
        }
        return max;
    }
}
public class Test2 {

    public static void main(String[] args) {
        Integer[] array = {1,5,2,7,19,4};
        Integer max = Alg2.<Integer>findMax(array);//可以通过array类判断传的是什么类型的数据
        System.out.println(max);
    }
}

数据结构初阶(算法的复杂度 + 包装类 + 泛型)_第2张图片

泛型方法

语法
方法限定符<类型形参列表> 返回值类型 方法名称(形参列表){
}

你可能感兴趣的:(数据结构初阶,算法,数据结构,java,学习,开发语言)