# -*- coding=utf-8 -*-

# from sorting_algorithms import SortingAlgorithms


class SearchingAlgorithms(object):
    '''
    七大查找算法 Python 版，所有待查序列里都是按主键来查找，即没有主键相同的两项。
    所有的查找算法的空间复杂度都比较低，因为比较之后无需存储比较结果，也无需存储被比较的值。
    时间复杂度根据被查找的值波动变化非常大，所以使用平均查找长度 ASL 来衡量算法性能。

    静态查找主要包括：顺序查找，折半二分查找，斐波那契查找，插值查找
    树查找主要包括：次优查找树，二叉查找树，平衡二叉树，2-3树，红黑树，b树，b+树，键树

    按照分块思想的查找有：索引顺序查找，键值查找


    '''

    def __init__(self):
        pass

    def sequential_search(self, lists, key):
        '''
        顺序查找算法，最简单的一种，静态查找，等概分布，针对没有排序的序列而言。平均查找长度 ASL = (n+1)/2
        设立哨兵的方法，避免每次都检测是否检测完整个表，本函数没写哨兵。
        :param lists: 待查链表，顺序表等等。
        :param key: 待查关键字
        :return: 如果查到了，返回索引位置，如果没有查到，返回 -1
        '''
        for i in xrange(0, len(lists)):
            if lists[i] == key:
                return i
        return -1

    def binary_search(self, lists, key):
        '''
        二分查找法，又叫折半查找，应用于等概分布的有序序列，利用了判定树，平均查找长度 ASL = (log2 n+1) - 1
        '''
        if lists == []:
            return -1
        if len(lists) == 1:
            if key == lists[0]:
                return 0
            else:
                return -1
        lists = sorted(lists)  # 先排序
        print lists  # [-6, 2, 3, 4, 9, 10, 13, 47, 78, 90, 111, 125, 345, 908, 999]
        low, high = 0, len(lists) - 1
        while low < high:
            mid = (low + high) / 2
            if lists[mid] > key:
                high = mid - 1
            if lists[mid] < key:
                low = mid + 1
            if lists[mid] == key:
                return mid
        return -1

    def insertion_search(self, lists, key):
        '''
        插值查找，二分折半查找的改进型，应用于等概分布，其实计算 mid 值消耗的时间也比较多，因为有很多乘除法运算。
        适用于有序的，均匀分布的，非常大数据量的查找。
        '''
        if lists == []:
            return -1
        if len(lists) == 1:
            if key == lists[0]:
                return 0
            else:
                return -1
        lists = sorted(lists)  # 先排序
        low, high = 0, len(lists) - 1
        while low < high:
            if key - lists[low] < 0:  # 以免出现回退
                return -1
            mid = low + (key - lists[low]) / (lists[high] - lists[low]) * (high - low)
            if lists[mid] > key:
                high = mid - 1
            if lists[mid] < key:
                low = mid + 1
            if lists[mid] == key:
                return mid
        return -1

    def fibonacci_search(self, lists, key):
        '''
        斐波那契查找算法，插值查找的一种，针对有序静态查找表来做。整体性能优于折半查找，但是在最坏情况下，比折半查找要差。
        相较于插值查找，不需要做乘除法运算，应用于等概分布
        '''
        if lists == []:
            return -1
        if len(lists) == 1:
            if key == lists[0]:
                return 0
            else:
                return -1
        lists = sorted(lists)  # 先排序

        # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34,...]
        def fibonacci(n):
            if n == 0:
                return 0
            if n == 1:
                return 1
            f = [0]
            f.append(1)
            for i in xrange(2, n + 1):
                f.append(f[i - 1] + f[i - 2])
            return f.pop()

        low, high = 0, len(lists)-1
        i = 1
        while fibonacci(i) < len(lists):
            i += 1
        if key == lists[high]+1:
            return -1
        for _ in xrange(len(lists), fibonacci(i)):
            lists.append(lists[high]+1)
        high = len(lists) - 1
        #print "fibonacci: ", i, high, fibonacci(i)
        while low <= high:
            mid = fibonacci(i-1) + low
            if lists[mid] == key:
                return mid
            if lists[mid] < key:
                low = mid + 1
                i -= 2
            if lists[mid] > key:
                high = mid - 1
                i -= 1
        return -1

    def binary_search_tree_search(self, lists, key):
        '''
        二叉查找树，又称为二叉排序树，对于每一个节点，其左子节点一定比其小，右子节点一定比其大。一般采用二叉链表作为存储结构。
        由于构建 BST 的过程是个动态的过程，所以二叉查找树是一个动态查找表的查找。
        二叉树如果中序遍历，其结果就是有序数组。
        平均查找长度是 2(1+ 1/n)(ln n), 时间复杂度为 o(log2 n)，在最坏情况下，是 o(n)，此时需要平衡化处理。
        一、每次添加一个结点，一定是在 BST的叶子节点上，每次插入都只需修改某个叶子节点的指针即可。
        二、每次删除一个结点，
            1、若是叶子节点，由于其没有子树，所以只需修改其父节点的指针即可。
            2、若是只有左子树或者右子树，则分情况讨论，若该结点是其双亲结点的左子树结点，则让这颗子树成为其父节点的左子树；
               若该结点是其双亲结点的右子树结点，则让这颗子树成为其父结点的右子树。
            3、若是左右子树都存在，则有两种处理方法，具体需要看图和代码来理解。
        '''

    def balanced_binary_tree_search(self, lists, key):
        '''
        平衡二叉树，又叫 AVL 树，左右子树都是平衡二叉树，且左右子树的深度之差不超过 1。
        由于二叉查找树性能恶化的原因在于树趋近于链表，也就是左右不平衡，所以平衡二叉树能够改善 BST的不稳定性能。
        其平均查找时间同样是 o(log2 n)，且效果比 BST 稳定。
        将二叉排序树改造成平衡二叉树的方法，也就是插入和删除的时候保持平衡，较为复杂，书本上有就不细说了。

        平衡二叉树分为 平衡2-3树和平衡红黑树，效率都比较高，但是真复杂啊。红黑树是 2-3树的一种简单实现。

        平衡2-3树：
            "2-3查找树定义"：和二叉树不一样，2-3树运行每个节点保存1个或者两个的值。对于普通的2节点(2-node)，他保存1个key和左右两个自己点。
        对应3节点(3-node)，保存两个Key，2-3查找树的定义如下：
            1）要么为空，要么：
            2）对于2节点，该节点保存一个key及对应value，以及两个指向左右节点的节点，左节点也是一个2-3节点，所有的值都比key要小，右节点也
               是一个2-3节点，所有的值比key要大。
            3）对于3节点，该节点保存两个key及对应value，以及三个指向左中右的节点。左节点也是一个2-3节点，所有的值均比两个key中的最小的
               key还要小；中间节点也是一个2-3节点，中间节点的key值在两个跟节点key值之间；右节点也是一个2-3节点，节点的所有key值比两个
               key中的最大的key还要大。

            "2-3查找树的性质"：
            1）如果中序遍历2-3查找树，就可以得到排好序的序列；
            2）在一个完全平衡的2-3查找树中，根节点到每一个为空节点的距离都相同。（这也是平衡树中“平衡”一词的概念，根节点到叶节点的最长距离
               对应于查找算法的最坏情况，而平衡树中根节点到叶节点的距离都一样，最坏情况也具有对数复杂度。

        平衡红黑树：
            2-3查找树能保证在插入元素之后能保持树的平衡状态，最坏情况下即所有的子节点都是2-node，树的高度为lgn，从而保证了最坏情况下的时
        间复杂度。但是2-3树实现起来比较复杂，于是就有了一种简单实现2-3树的数据结构，即红黑树（Red-Black Tree）。

　　         基本思想：红黑树的思想就是对2-3查找树进行编码，尤其是对2-3查找树中的3-nodes节点添加额外的信息。红黑树中将节点之间的链接分为
        两种不同类型，红色链接，他用来链接两个2-nodes节点来表示一个3-nodes节点。黑色链接用来链接普通的2-3节点。特别的，使用红色链接的两个
        2-nodes来表示一个3-nodes节点，并且向左倾斜，即一个2-node是另一个2-node的左子节点。这种做法的好处是查找的时候不用做任何修改，和
        普通的二叉查找树相同。

            “红黑树的定义”：红黑树是一种具有红色和黑色链接的平衡查找树，同时满足：
            1、红色节点向左倾斜
            2、一个节点不可能有两个红色链接
            3、整个树完全黑色平衡，即从根节点到所以叶子结点的路径上，黑色链接的个数都相同。
　　         下图可以看到红黑树其实是2-3树的另外一种表现形式：如果我们将红色的连线水平绘制，那么他链接的两个2-node节点就是2-3树中的一个
             3-node节点了。

            “红黑树的性质”：整个树完全黑色平衡，即从根节点到所以叶子结点的路径上，黑色链接的个数都相同（2-3树的第2）性质，从根节点到叶子节
        点的距离都相等）。
            “复杂度分析”：最坏的情况就是，红黑树中除了最左侧路径全部是由3-node节点组成，即红黑相间的路径长度是全黑路径长度的2倍。


        '''

    def b_tree_search(self,lists, key):
        '''
        B树查找：B树（B-tree）是一种树状数据结构，它能够存储数据、对其进行排序并允许以O(log n)的时间复杂度运行进行查找、顺序读取、插入和\
删除的数据结构。B树，概括来说是一个节点可以拥有多于2个子节点的二叉查找树。与自平衡二叉查找树不同，B树为系统最优化大块数据的读和写操作。\
B-tree算法减少定位记录时所经历的中间过程，从而加快存取速度。普遍运用在数据库和文件系统。
        “B树定义”：
        B树可以看作是对 2-3查找树的一种扩展，即他允许每个节点有 M-1个子节点。根节点至少有两个子节点，每个节点有 M-1个key，并且以升序排\
列，位于 M-1和 M key的子节点的值位于 M-1 和 M key对应的 Value之间，其它节点至少有 M/2个子节点。

        B+树定义：
        B+树是对B树的一种变形树，它与B树的差异在于：
        1、有k个子结点的结点必然有k个关键码；
        2、非叶结点仅具有索引作用，跟记录有关的信息均存放在叶结点中。
        3、树的所有叶结点构成一个有序链表，可以按照关键码排序的次序遍历全部记录。

        B和 B+树的区别在于，B+树的非叶子结点只包含导航信息，不包含实际的值，所有的叶子结点和相连的节点使用链表相连，便于区间查找和遍历。

        B+ 树的优点在于：
        由于B+树在内部节点上不好含数据信息，因此在内存页中能够存放更多的key。 数据存放的更加紧密，具有更好的空间局部性。因此访问叶子几点上\
关联的数据也具有更好的缓存命中率。B+树的叶子结点都是相链的，因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连，\
所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻，所以缓存命中性没有B+树好。
  　    但是B树也有优点，其优点在于，由于B树的每一个节点都包含key和value，因此经常访问的元素可能离根节点更近，因此访问也更迅速。

        B/B+树常用于文件系统和数据库系统中，它通过对每个节点存储个数的扩展，使得对连续的数据能够进行较快的定位和访问，能够有效减少查找时\
间，提高存储的空间局部性从而减少IO操作。它广泛用于文件系统及数据库中，如：
        Windows：HPFS文件系统；
        Mac：HFS，HFS+文件系统；
        Linux：ResiserFS，XFS，Ext3FS，JFS文件系统；
        数据库：ORACLE，MYSQL，SQLSERVER等中。
        '''

    def digital_search_tree_search(self, lists, key):
        '''
        键树，又称数字查找树，利用的基本思想类似于我们查英文字典。也就是把序列中的关键字拆分成若干部分，这些部分可作为每一层键树种的结点。\
组合起来之后即可得到要查询的关键字，最典型的应用也是查找英文单词。事先将所有的英文单词组织成一颗键树，达到效率最高。
        键树的存储结构：
        1、多重链表，最容易想到的存储结构；
        2、孩子兄弟链表来表示键树，左子节点是孩子，右子节点是兄弟。两种表示方法即普通树和二叉树之间的互换。
        '''

    def hash_search(self, lists, key):
        '''
        什么是哈希表（Hash）？
        我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数（哈希函数， 也叫做散列函数），使得每个元素的关键字都与一个函数值（即数\
组下标）相对应，于是用这个数组单元来存储这个元素；也可以简单的理解为，按照关键字为每一个元素"分类"，然后将这个元素存储在相应"类"所对应的地\
方。但是，不能够保证每个元素的关键字与函数值是一一对应的，因此极有可能出现对于不同的元素，却计算出了相同的函数值，这样就产生了"冲突"，换句话\
说，就是把不同的元素分在了相同的"类"之中。后面我们将看到一种解决"冲突"的简便做法。
        总的来说，"直接定址"与"解决冲突"是哈希表的两大特点。

        什么是哈希函数？
        哈希函数的规则是：通过某种转换关系，使关键字适度的分散到指定大小的的顺序结构中，越分散，则以后查找的时间复杂度越小，空间复杂度越高。
        算法思想：哈希的思路很简单，如果所有的键都是整数，那么就可以使用一个简单的无序数组来实现：将键作为索引，值即为其对应的值，这样就可\
以快速访问任意键的值。这是对于简单的键的情况，我们将其扩展到可以处理更加复杂的类型的键。

        算法流程：
    　　1）用给定的哈希函数构造哈希表；
    　　2）根据选择的冲突处理方法解决地址冲突；
    　　　　常见的解决冲突的方法：拉链法和线性探测法。
    　　3）在哈希表的基础上执行哈希查找。
    　　哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制，那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1)；如\
果没有时间限制，那么我们可以使用无序数组并进行顺序查找，这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需\
要调整哈希函数算法即可在时间和空间上做出取舍。
    　　
        复杂度分析：
    　　单纯论查找复杂度：对于无冲突的Hash表而言，查找复杂度为O(1)（注意，在查找之前我们需要构建相应的Hash表）。

    　　使用Hash，我们付出了什么？
    　　我们在实际编程中存储一个大规模的数据，最先想到的存储结构可能就是map，也就是我们常说的KV pair，经常使用Python的博友可能更有这种体\
会。使用map的好处就是，我们在后续处理数据处理时，可以根据数据的key快速的查找到对应的value值。map的本质就是Hash表，那我们在获取了超高查找\
效率的基础上，我们付出了什么？

    　　Hash是一种典型以空间换时间的算法，比如原来一个长度为100的数组，对其查找，只需要遍历且匹配相应记录即可，从空间复杂度上来看，假如数\
组存储的是byte类型数据，那么该数组占用100byte空间。现在我们采用Hash算法，我们前面说的Hash必须有一个规则，约束键与存储位置的关系，那么就需\
要一个固定长度的hash表，此时，仍然是100byte的数组，假设我们需要的100byte用来记录键与位置的关系，那么总的空间为200byte,而且用于记录规则\
的表大小会根据规则，大小可能是不定的。
        '''

search = SearchingAlgorithms()
l = [4, 90, 125, 2, 9, 47, 999, -6, 111, 908, 13, 78, 345, 3, 10,909]
print "Sequential search: ", search.sequential_search(l, 10)
print "Binary search: ", search.binary_search([101], 101)
print "Insertion search: ", search.insertion_search(l, 907)
print "Fibonacci search: ", search.fibonacci_search(l, 4)
print search.b_tree_search.__doc__
