算法之二分查找/JAVA

文章目录

    • 1.0 字典
      • 1.0.1 有序数组中的二分查找

1.0 字典

字典是一种抽象数据类型,它有一个索引和对应的数据,通过索引我们可以在O(1)时间内找到数据。其中索引称为,数据称为

字典遵循了以下的规则:

  • 不重复
  • 不为空
  • 每个只对应一个

因为字典是一个可以通过键快速定位值的数据结构,所以用于查询是异常方便的,但是字典中的数据并不是一直不变,其中存在着更新、插入、删除等等操作,怎么在加入这些操作后依旧保障字典的优良特性是我们需要研究的问题。首先我们对更新插入和删除作为说明。

  • 更新实质上是一种插入,首先我们遍历键,找到我们需要更新的键,将其值替换成我们想要更新的值。而当我们无法找到我们需要更新的键,就创建一个新的键并写入值,即为插入。而删除有两种方式,一种是将需要删除的键的值置null,然后再删除值(延迟删除),另一种就是直接删除键(立即删除)。

字典作为一种能够快速查询的数据结构,会有如下的一些特性,如何在下面的情况下仍旧保障字典的高级特性是我们需要解决的问题。

  • 混合使用查找和插入的操作
  • 大量不同的键
  • 查找操作比插入操作多得多
  • 虽然不可预测,查找和插入的作用模式并非随机。

想到储存键和值我们首先会想到链表,但是每次查找链表找到对应的数据的时间复杂度为O(N),这种代价太高了,所以我们可以选用数组,那么如何储存键和值呢?我们可以用两个平行的数组来实现,key[i]中储存着键,value(i)中储存对应的值。

1.0.1 有序数组中的二分查找

因为是有序数组,所以我们在查找和插入时都要保障其有序性,当我们可以找到这个键的时候,我们返回其数组下标,当我们找不到这个键的时候,我们返回小于这个键的第一个数组的下标,便于插入。

因为我们不确定key的类型,所以我们将其声明为范型,并实现Comparable接口。查找代码如下

//Key是一个泛型,并且这个泛型已经实现了Comparable接口
public class BinarySearch<Key extends Comparable<Key>,Value> {
    private Key[] keys;
    private Value[] values;
    private int N;

    //初始化数组
    public BinarySearch(int capacity){
        keys = (Key[]) new Comparable[capacity];
        values = (Value[]) new Object[capacity];
    }

    public int size(){
        return N;
    }

    public int rank(Key key){
        int low = 0;
        int high =N-1;
        while (low <= high){
            int mid = low + (high - low)/2;
            int cmp = key.compareTo(keys[mid]);
            //如果key小于keys的中位数
            if (cmp < 0){
                high = mid - 1;
            } else if (cmp > 0){
                low = mid + 1;
            } else {
                //mid为找到
                return mid;
            }
        }
        //如果没找到
        return low;
    }
}

我们已经用二分查找实现了查找,那么获取数据以及插入(更新)数据就变的很简单了代码如下:

//获取数据
public Value get(Key key){
  int i = rank(key);
  //如果i不越界且keys【i】== key 则存在,都则不存在则返回null
  if (i < N && keys[i].compareTo(key) == 0){
    return values[i];
  } else {
    return null;
  }
}

//插入/更新数据
public void put(Key key , Value value){
  int i = rank(key);
  //如果i不越界且keys【i】== key 则存在,更改数据,否则插入
  if (i < N && keys[i].compareTo(key) == 0){
    values[i] = value;
  } else {
    for (int j = N; j < i; j--) {
      keys[j] = keys[j-1];
      values[j] = values[j-1];
    }
    keys[i] = key;
    values[i] = value;
    N++;
  }
}

通过二分查找,我们将查找的时候的比较次数从N降低到了lgN+1,当N很大时,对于效率的提升是非常显著的。这一定程度上解决了我们上文的的问题。下面我们对比一下顺序查找和二分查找。

算法 最坏情况下的成本(查找) 最坏情况下的成本(插入) 平均情况下的成本(查找) 平均情况下的成本(插入) 是否高效支持有序性相关操作
顺序查找(无序链表) N N N/2 N
二分查找(有序数组) lgN 2N lgN N

我们可以看出使用二分查找成功将查找成本降低到对数级别。

你可能感兴趣的:(算法思想,数据结构,java,链表,二分法)