算法5-无序链表中的顺序查找、有序数组中的二分查找

各位读者,晚上好。这几篇博客陆续介绍下算法中的查找。本篇博客介绍的关于符号表的查找算法是循序渐进的,正因为前一个方法存在的缺点,所以用后一种方法改进;当2种方法都不满意,我们需要突破,寻找新的数据结构。

本博客代码示例均来自:算法 Algorithmes Forth Edition
[美] Robert Sedgewick Kevin Wayne 著 谢路云译

一、无序链表中的顺序查找

package com.cmh.algorithm;

/**
 * @author: 起舞的日子
 * @date: 2020/4/6 下午11:24
 * 符号表:基于无序链表的顺序查找
 * ST = Symbol Table
 */
public class SequentialSearchST<Key, Value> {

    private Node first;//链表首节点

    private class Node {
        //链表节点定义
        Key key;
        Value value;
        Node next;

        public Node(Key key, Value value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public Value get(Key key) {
        //根据给定的键返回对应的值
        for (Node x = first; x != null; x = x.next) {
            if (key.equals(x.key)) {
                return x.value;//命中  如果key是对象,要考虑对象对象等价性
            }
        }
        return null;//未命中
    }

    public void put(Key key, Value val) {
        //根据给定的键,找到则更新其值,否则在表中新建节点
        for (Node x = first; x != null; x = x.next) {
            if (key.equals(x.key)) {
                x.value = val;
                return;
            } else {
                first = new Node(key, val, first); //未命中,新建节点p
            }
        }
    }

    /**
     * 使用无序链表实现K,V查找和插入,最大问题是数据量很大时候,算法复杂度至少在O(N)级别,
     * 算法消耗在:比较的次数上
     */
}

二、无序链表中的顺序查找

package com.cmh.algorithm;

import edu.princeton.cs.algs4.Queue;

import static edu.princeton.cs.algs4.BinaryStdIn.isEmpty;

/**
 * @author: 起舞的日子
 * @date: 2020/4/6 下午11:40
 * 符号表:基于有序数组的二分查找
 */
public class BinarySearchST<Key extends Comparable<Key>, Value> {
    private Key[] keys;
    private Value[] vals;
    private int N;

    public BinarySearchST(int capacity) {
        //调整数组大小... 省略
        keys = (Key[]) new Comparable[capacity];
        vals = (Value[]) new Object[capacity];
    }

    public int size() {
        return N;
    }

    public Value get(Key key) {
        if (isEmpty()) {
            return null;
        } else {
            int i = rank(key);
            if (i < N && keys[i].compareTo(key) == 0) {
                return vals[i];
            } else {
                return null;
            }
        }
    }

    //基于有序数组的二分查找(无递归调用)
    public int rank(Key key) {
        int lo = 0, hi = N - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            int cmp = key.compareTo(keys[mid]);
            if (cmp < 0) {
                hi = mid - 1;
            } else if (cmp > 0) {
                lo = mid + 1;
            } else {
                return mid;
            }
        }
        return lo;
    }

    /**
     * 还可以用递归方式实现二分查找
     */
    public int rank(Key key, int lo, int hi) {
        if (hi < lo) {
            return lo;
        }
        int mid = lo + (hi - lo) / 2;
        int cmp = key.compareTo(keys[mid]);
        if (cmp < 0) {
            return rank(key, lo, mid - 1);
        } else if (cmp > 0) {
            return rank(key, mid + 1, hi);
        } else {
            return mid;
        }
    }

    public void put(Key key, Value val) {
        //查找键,找到则更新值,否则创建新元素
        int i = rank(key);
        if (i < N && keys[i].compareTo(key) == 0) {
            vals[i] = val;
            return;
        }
        for (int j = N; j > i; j--) {
            keys[j] = keys[j - 1];
            vals[j] = vals[j - 1];
        }
        keys[i] = key;
        vals[i] = val;
    }

    public Key min() {
        return keys[0];
    }

    public Key max() {
        return keys[N - 1];
    }

    public Key select(int k) {
        return keys[k];
    }

    public Key ceiling(Key key) {
        int i = rank(key);
        return keys[i];
    }

    public Iterable<Key> kes(Key lo, Key hi) {
        Queue<Key> q = new Queue<>();
        for (int i = rank(lo); i < rank(hi); i++) {
            q.enqueue(keys[i]);

        }
/*
        if(contains(hi)){
            q.enqueue(keys[rank(hi)]);

        }
*/
        return q;
    }
    /**
     * 总结:
     * 二分查找优点:
     * 1、查找算法复杂度是对数log级别的
     *
     * 缺点:
     * 1、数据需有序;
     * 2、把很大量的数据排序在很多情况下复杂度会大大提升;
     * 3、有些数据查找和插入是混合进行的,排序好的静态数据就不合适;
     *
     *
     * 所以需要一种新的数据结构,可以结合二分查找的优缺点:
     * 二叉查找树
     * 1)可以用二分法快速查找;
     * 2)可以类似链表快速插入
     * 下一篇博客介绍
     */
}

三、总结

我们知道,用二分法猜数,100以内的整数只需要7次即可猜中;即便很大的数字,猜中的次数不那么可怕,查找中就我目前知道的,没有比二分法更快的了;

不过,世界如果如此简单就好了。
1、二分法高效的前提是数据必须有序
如果给你100个无需的整数,你猜猜试一试?所需很多经典的排序算法就是先把数据高效排序后,然后就可以快速查找了。世界之所以复杂,是因为有很多数据:1是量大;2是把它们排序所花费的精力还不如不排序

2、二分法针对的是静态的数据
如果这个数据是动态的,还会有新数据不断写入,请问你怎么保证顺序?一直不停的排序嘛?也就是二分法无法应对不断有新数据插入的情况

因此,二叉查找树诞生了,下篇博客再会~

发布了181 篇原创文章 · 获赞 66 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/meiceatcsdn/article/details/105355075