一、基于无序链表的顺序查找
没啥讲的,就是顺序找。
/**
* 顺序查找(基于无序链表)
* 未命中和命中都需要N次比较
* 命中最坏需要N次比较
* @author MaoLin Wang
* @date 2020/3/118:01
*/
public class SequentialSearchST<Key, Value> {
/**
* 首节点
*/
private Node first;
private int size;
private class Node {
private Key key;
private Value value;
private Node next;
public Node(Key key, Value value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
/**
* 根据key查询对应的值,一个个往下遍历直到找到相等的key,返回对应的值,否则返回null
* @param key
* @return
*/
public Value get(Key key) {
for (Node x = first; x != null; x = x.next) {
if (key.equals(x.key)) {
return x.value;
}
}
return null;
}
/**
* 加入一个元素
* @param key
* @param value
*/
public void put(Key key, Value value) {
for (Node x = first; x != null; x = x.next) {
//key已存在,更新对应的值
if (key.equals(x.key)) {
x.value = value;
return;
}
}
//key不存在,新添加一个节点
first = new Node(key, value, first);
size++;
}
public boolean isEmpty() {
return size == 0;
}
private int size() {
return size;
}
public boolean contains(Key key) {
if (key == null) throw new IllegalArgumentException("argument to contains() is null");
return get(key) != null;
}
/**
* 删除key对应的节点
* @param key
*/
public void delete(Key key) {
if (key == null) throw new IllegalArgumentException("argument to delete() is null");
first = delete(first, key);
}
/**
* 递归查找,直到找到相等的key,正常删除链表节点
* @param x
* @param key
* @return
*/
private Node delete(Node x, Key key) {
if (x == null) return null;
if (key.equals(x.key)) {
size--;
return x.next;
}
x.next = delete(x.next, key);
return x;
}
public Iterable<Key> keys() {
Queue<Key> queue=new Queue<>();
while (first!=null){
queue.enqueue(first.key);
first=first.next;
}
return queue;
}
}
优点:
适用小型问题
缺点:
对大型符号表很慢
二、基于有序数组的二分查找
查找很简单,先让目标值与中值比较,如果比中值小,则在左半边继续查找,如果比中值大,则继续要右半边查找。
这里定义了rank()方法,返回比目标key小的key的数量
/**
* 返回小于给定键key的key的数量
* 1.找到给定key,则返回对应的mid坐标
* 2.找不到给定key,返回left指针,此时left指向大于等于给定key的最小位置
* 两种情况返回的都是小鱼给定key的key的数量
* @param key
* @return
*/
public int rank(Key key){
int left=0,right=size-1;
while (left<=right){
int mid=left+(right-left)/2;
int result=keys[mid].compareTo(key);
if (result<0){
left=mid+1;
}else if (result > 0){
right=mid-1;
}else {
return mid;
}
}
return left;
}
基本数据结构如下:
使用泛型Key保存键,泛型Value保存值
public class BinarySearchST<Key extends Comparable<Key>,Value> {
private Key[] keys;
private Value[] values;
private int size;
public BinarySearchST(int size) {
keys= (Key[]) new Comparable[size];
values= (Value[]) new Comparable[size];
}
}
查找操作:
public Value get(Key key){
if (isEmpty()){
return null;
}
int i=rank(key);
//如果key存在,且i<size,则keys[i]一定等于key,返回对应的value
if (i<size && keys[i].compareTo(key)==0){
return values[i];
}else {
return null;
}
}
添加一对数据
public void put(Key key,Value value){
if (key==null){
return;
}
//value等于null,默认不存储null,删除对应的key-value
if (value==null){
delete(key);
return;
}
int i=rank(key);
if (i<size && keys[i].compareTo(key)==0){
//存在key,则更新值
values[i]=value;
}else {
if (size == keys.length) resize(2*keys.length);
//不存在,则将从i到size的所有元素向后移动一个位置,再将新的值加入到位置i
for (int j = size; j > i; j--) {
keys[j]=keys[j-1];
values[j]=values[j-1];
}
keys[i]=key;
values[i]=value;
size++;
}
}
删除一对数据:
public void delete(Key key){
if (key==null)throw new IllegalArgumentException("key为空");
if (isEmpty()) return;
//待删除key的位置
int i=rank(key);
if (i<size && keys[i].compareTo(key)==0){
//如果key存在,则将i+1到size-1的元素向左移动一位
for (int j = i; j < size-1 ; j++) {
keys[j]=keys[j+1];
values[j]=values[j+1];
}
//置最后一位为null
size--;
keys[size]=null;
values[size]=null;
return ;
}
}
其他完整代码:
/**
* N个键的有序数组最多需要N+1次比较
* @author MaoLin Wang
* @date 2020/3/118:43
*/
public class BinarySearchST<Key extends Comparable<Key>,Value> {
private Key[] keys;
private Value[] values;
private int size;
public BinarySearchST(int size) {
keys= (Key[]) new Comparable[size];
values= (Value[]) new Comparable[size];
}
public int Size(){
return size;
}
public Value get(Key key){
if (isEmpty()){
return null;
}
int i=rank(key);
if (i<size && keys[i].compareTo(key)==0){
return values[i];
}else {
return null;
}
}
public void put(Key key,Value value){
if (key==null){
return;
}
if (value==null){
delete(key);
return;
}
int i=rank(key);
if (i<size && keys[i].compareTo(key)==0){
//存在key,则更新值
values[i]=value;
}else {
if (size == keys.length) resize(2*keys.length);
//不存在,则将从i到size的所有元素向后移动一个位置,再将新的值加入到位置i
for (int j = size; j > i; j--) {
keys[j]=keys[j-1];
values[j]=values[j-1];
}
keys[i]=key;
values[i]=value;
size++;
}
}
/**
* 返回小于给定键key的key的数量
* @param key
* @return
*/
public int rank(Key key){
int left=0,right=size-1;
while (left<=right){
int mid=left+(right-left)/2;
int result=keys[mid].compareTo(key);
if (result<0){
left=mid+1;
}else if (result > 0){
right=mid-1;
}else {
return mid;
}
}
return left;
}
public boolean isEmpty(){
return size==0;
}
public Key minKey(){
return keys[0];
}
public Key maxKey(){
return keys[size-1];
}
public Key selectKey(int k){
return keys[k];
}
/**
* 向上取整 找出大于等于该键的最小键
* @param key
* @return
*/
public Key ceiling(Key key){
int i=rank(key);
return keys[i];
}
/**
* 向下取整 找出小于等于该键的最小键
* @param key
* @return
*/
public Key floor(Key key){
if (key==null)throw new IllegalArgumentException("key为空");
int i=rank(key);
if (i<size && keys[i].compareTo(key)==0){
return keys[i];
}else if (i==0){
return null;
}else {
return keys[i-1];
}
}
public void delete(Key key){
if (key==null)throw new IllegalArgumentException("key为空");
if (isEmpty()) return;
int i=rank(key);
if (i<size && keys[i].compareTo(key)==0){
for (int j = i; j < size-1 ; j++) {
keys[j]=keys[j+1];
values[j]=values[j+1];
}
size--;
keys[size]=null;
values[size]=null;
return ;
}
}
private void resize(int capacity) {
Key[] tempKey = (Key[]) new Comparable[capacity];
Value[] tempValue = (Value[]) new Object[capacity];
for (int i = 0; i < size; i++) {
tempKey[i] = keys[i];
tempValue[i] = values[i];
}
values = tempValue;
keys = tempKey;
}
public void delMin(){
delete(minKey());
}
public void delMax(){
delete(maxKey());
}
public Iterable<Key> keys(Key lo,Key hi){
Queue<Key>queue=new Queue<>();
int right = rank(hi);
for (int i = rank(lo); i < right; i++) {
queue.enqueue(keys[i]);
}
if (contains(hi)){
queue.enqueue(keys[right]);
}
return queue;
}
public boolean contains(Key key) {
if (key == null) throw new IllegalArgumentException("argument to contains() is null");
return get(key) != null;
}
public static void main(String[] args) {
BinarySearchST<String,Integer> binarySearchST=new BinarySearchST<>(5);
binarySearchST.put("a",3);
binarySearchST.put("d",2);
binarySearchST.put("c",1);
binarySearchST.put("b",0);
binarySearchST.put("e",4);
System.out.println(binarySearchST.size);
binarySearchST.delete("b");
String s = binarySearchST.selectKey(3);
System.out.println(s);
}
}
二分查找平均情况下的插入成本为 lgN,最坏情况下为lgN,一般情况比顺序查找快的多
但是在键是随机的情况下,构造一个有序数组所需要访问数组的次数是数组长度的平方级别。
因此最坏情况下,插入的成本为2N
优点:
最优的查找效率和控件需求,能够进行有序性相关的操作
缺点:
插入操作慢