算法和数据结构

线性查找算法

1 定义

见名知意,意思是从一串连续的数据中,找到想要的数据,并返回该元素所在的位置。如果没有找到,返回-1。一个最为简单的理解,就是从数组中,找到和数据相等的数组元素,并返回其元素下标。没找到则返回-1。

2 时间复杂度

O(n),意味着随着数组元素越来越多,则查找的时间将越来越长。即查找时间与数据规模成正比。注意:时间复杂度通常是指最差的时间复杂度,而不能单独去最优的时间复杂度。如:有1,2,3,,,100000的数组,通过线性查找的方式,从中找数据1,立即就能找到。但是,不能说,此时的线性查找的时间复杂度是O(1)。

3 算法思路

用要查找的数据和数组的第1个元素相比,如果相等,则返回数组中对应的元素下标,并且结束查找。如果没找到,则用要查找的数据继续和第2个元素相比,如果相等,则返回数组中对应的元素下标,结束查找。如果没找到,则继续和第3个,4个,直到最后1个元素相比,如果数组中没有任何1个元素和要找的数据相等,则意味着没找到,最后返回一个-1,表示未找到。

4 代码实现

public class LinearSearch{
  public int search(int[] arr,int key){
       for(int i=0;i<arr.lenth;i++){
            if(arr[i]==key)
            return i;
       }
       return -1;
    }
  public static void main(String[] args){
    int[] arr={6,2,4,3,1,5};
    int key=1;
    LinearSearch ls=new LinearSearch();
    int index=ls.search(arr,key);
    System.out.println("index of "+key+" is: "+index);
  }
}

该代码实现中,有3个小问题:问题一, LinearSearch是一个线性搜索类,不应该通过实例化其对象来调用search()方法,而应该直接将该方法改为static,通过类名.方法名来直接调用,更直观;

问题二:为了通过类名.方法名来调动search()方法,我们应该将其构造方法私有化,这样,避免在类的外部通过实例化其对象来调用search()方法;

问题三:该线性查找方法search(),目前只能支持int[]数组,对于其它类型的数组,却无法支持线性查找,这显然不够优雅。我们应该实现一个方法,可以支持任意类型的数组的线性查找。于是,我们可以考虑将该方法改为带有泛型的方法。

5 代码改良

public class LinearSearch{
  //私有化构造方法,防止外面通过实例化本类的对象来调用search()方法
   private LinearSearch(){}
  
  //为了实现线性查找可以支持任意类型的函数,这里只需要将该方法改为泛型方法即可,而无需将该类改为泛型类
  //另外,改为泛型方法之后,就要通过equals()来判断是否相等,而不能再使用==比较对象是否相等了,因为==比较的是对象地址是否相等,而equals()比较的才是内容。
  public static <T> int search(T[] arr,T key){
    for(int i=0;i<arr.length;i++){
      if(arr[i].equals(key)){
        return i;
      }
    }
    return -1;
  }
  
  public static void main(String[] args){
    //由于方法是泛型方法,这里就必须要用类类型的数组来调用,而不能使用基本数据类型的数组
    Integer[] arr={6,2,4,3,1,5};
    int key=1;
    int index=LinearSearch.search(arr,key);
    System.out.println("index of "+key+" is: "+index);
  }
}

6 代码扩展

改良后的代码是可以支持泛型的线性搜索,比最开始的只是支持整型数组的线性搜索功能要强大很多。也有一点儿需要注意的地方,就是如果要更进一步支持自定义类的线性搜索的话,则自定义类必须要重写equals()方法。因为,我们的线性搜索是通过equals()来判断元素是否找到的,我们的算法不关心equals()方法具体应该怎么实现,该实现的工作应该由类的创建者去关心和维护的,不是我们的算法所要关心的。

自定义User类

  public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }
}

然后,就可以在LinearSearch类的main方法中,在User[]的数组中线性查找了。片段代码如下:

        User u1 = new User("Jim", 10);
        User u2 = new User("Kate", 8);
        User u3 = new User("Lilei", 12);
        User[] users = {u1, u2, u3};
        int zz = LinearSearch.search(users, new User("Kate", 8));
        System.out.println(zz);

7 性能测试

接下来,测试验证一下线性查找算法的性能,既然其时间复杂度是O(n),我们可以分别构建一个包含10万、100万、1000万的线性数组,且要查找的数据也分别为100000,1000000,10000000。可以让该算法分别线性的把每个数组的元素都从头到尾遍历一次。进而,验证其执行的时间是否也是线性增长的?

构建一个工具类,ArrayGenerator,用于生成线性数组,且每个元素的值就是其对应的下标值。即,生成元素为:{0,1,2,,,,9999}的数组

public class ArrayGenerator{
  //构造方法同样设置为private
  private ArrayGenerator(){}
  //接收1个整数n,构建1个长度为n的Integer[],并且元素值分别为0,1,2,...n-1
  public static Integer[] generateOrderedArray(int n){
    Integer[] arr=new Integer[n];
    for(int i=0;i<arr.length;i++){
      arr[i]=i;
    }
    return arr;
  }
}

完整的代码示例:

public class LinearSearch{
  //构造方法私有化,避免在类外部实例化该类的对象
  private LinearSearch(){}
  //公共的静态的支持泛型的线性查找法
  //传入一个T类型的数组arr和T类型的元素key,线性遍历该T数组,如果发现有元素和key值相等,则返回该元素在数组中的下标,意味着已经找到,结束查找。如果遍历完整个数组,都找不到和key值相等的元素,则意味着该数组中不包含该元素key,返回-1意味着查找失败。
  public static <T> int search(T[] arr,T key){
    for(int i=0;i<arr.length;i++){
      if(arr[i].equals(key)){
        return i;
      }
    }
    return -1;
  }
  
  public static void main(String[] args){
    //预定义1个数组,元素分别为10万,100万
    int[] dataSize = {100000,1000000};
    //foreach循环,相当于分别把10万,100万作为参数,用于创建长度也为10万、100万的线性有序数组
    for(int n:dataSize){
      Integer[] arr = ArrayGenerator.generateOrderedArray(n);
      long beginTime = System.currentTimeMillis(); 
      //调用线性查找法去遍历长度为10万、100万的数组,从中查找元素也为10万、100万的元素
      LinearSearch.search(arr,n);
      long endTime = System.currentTimeMillis(); 
      System.out.println("Array size: "+n+" key: "+n+" Time: "+(endTime-beginTime));
    }
    
    //支持自定义类User的线性查找
    User u1 = new User("Jim", 10);
    User u2 = new User("Kate", 8);
    User u3 = new User("Lilei", 12);
    User[] users = {u1, u2, u3};
    int zz = LinearSearch.search(users, new User("Kate", 8));
    System.out.println(zz);
  }
}

留言