Algorithm-search 查詢演算法 python

娃哈哈店長發表於2020-01-04

python 查詢演算法- 順序查詢 二分查詢 分塊查詢 雜湊表查詢等
Search Algorithm-Sequential Search Binary Search Block Search Hash Table Search.etc.

順序查詢

順序查詢又稱為線性查詢,是一種最簡單的查詢方法。適用於線性表的順序儲存結構和鏈式儲存結構。該演算法的時間複雜度為O(n)。

基本思路
從第一個元素m開始逐個與需要查詢的元素x進行比較,當比較到元素值相同(即m=x)時返回元素m的下標,如果比較到最後都沒有找到,則返回-1。

優缺點
缺點:是當n 很大時,平均查詢長度較大,效率低;
優點:是對錶中資料元素的儲存沒有要求。另外,對於線性連結串列,只能進行順序查詢。

演算法實現

  • 最基礎的遍歷無序列表的查詢演算法
  • 時間複雜度O(n)

def sequential_search(lis, key):
  length = len(lis)
  for i in range(length):
    if lis[i] == key:
      return i
    else:
      return False

if __name__ == '__main__':
  LIST = [1, 5, 8, 123, 22, 54, 7, 99, 300, 222]
  result = sequential_search(LIST, 123)
  print(result)

二分查詢

二分查詢(Binary Search),是一種在有序陣列中查詢某一特定元素的查詢演算法。查詢過程從陣列的中間元素開始,如果中間元素正好是要查詢的元素,則查詢過程結束;如果某一特定元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半中查詢,而且跟開始一樣從中間元素開始比較。如果在某一步驟陣列為空,則代表找不到。
這種查詢演算法每一次比較都使查詢範圍縮小一半。'

演算法描述
給予一個包含 n個帶值元素的陣列A

  1. 令 L為0 , R為 n-1 ;
  2. 如果L>R,則搜尋以失敗告終 ;
  3. 令 m (中間值元素)為 ⌊(L+R)/2⌋;
  4. 如果 Am<T,令 L為 m + 1 並回到步驟二 ;
  5. 如果 Am>T,令 R為 m - 1 並回到步驟二;

複雜度分析
時間複雜度:折半搜尋每次把搜尋區域減少一半,時間複雜度為 O(logn)
空間複雜度:O(1)

演算法實現

  • 針對有序查詢表的二分查詢演算法
def binary_search(lis, key):
  low = 0
  high = len(lis) - 1
  time = 0
  while low < high:
    time += 1
    mid = int((low + high) / 2)
    if key < lis[mid]:
      high = mid - 1
    elif key > lis[mid]:
      low = mid + 1
    else:
      # 列印折半的次數
      print("times: %s" % time)
      return mid
  print("times: %s" % time)
  return False

if __name__ == '__main__':
  LIST = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
  result = binary_search(LIST, 99)
  print(result)

插值查詢

插值查詢是根據要查詢的關鍵字key與查詢表中最大最小記錄的關鍵字比較後的 查詢方法,其核心就在於插值的計算公式 (key-a[low])/(a[high]-a[low])*(high-low)。
時間複雜度o(logn)但對於表長較大而關鍵字分佈比較均勻的查詢表來說,效率較高。

演算法思想
基於二分查詢演算法,將查詢點的選擇改進為自適應選擇,可以提高查詢效率。當然,差值查詢也屬於有序查詢。
注:對於表長較大,而關鍵字分佈又比較均勻的查詢表來說,插值查詢演算法的平均效能比折半查詢要好的多。反之,陣列中如果分佈非常不均勻,那麼插值查詢未必是很合適的選擇。
複雜度分析
時間複雜性:如果元素均勻分佈,則O(log log n)),在最壞的情況下可能需要 O(n)。
空間複雜度:O(1)。

演算法實現

  • 插值查詢演算法
def binary_search(lis, key):
  low = 0
  high = len(lis) - 1
  time = 0
  while low < high:
    time += 1
    # 計算mid值是插值演算法的核心程式碼
    mid = low + int((high - low) * (key - lis[low])/(lis[high] - lis[low]))
    print("mid=%s, low=%s, high=%s" % (mid, low, high))
    if key < lis[mid]:
      high = mid - 1
    elif key > lis[mid]:
      low = mid + 1
    else:
      # 列印查詢的次數
      print("times: %s" % time)
      return mid
  print("times: %s" % time)
  return False

if __name__ == '__main__':
  LIST = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
  result = binary_search(LIST, 444)
  print(result)

斐波那契查詢

斐波那契數列,又稱黃金分割數列,指的是這樣一個數列:1、1、2、3、5、8、13、21、····,在數學上,斐波那契被遞迴方法如下定義:F(1)=1,F(2)=1,F(n)=f(n-1)+F(n-2) (n>=2)。該數列越往後相鄰的兩個數的比值越趨向於黃金比例值(0.618)。

斐波那契查詢就是在二分查詢的基礎上根據斐波那契數列進行分割的。在斐波那契數列找一個等於略大於查詢表中元素個數的數F[n],將原查詢表擴充套件為長度為Fn,完成後進行斐波那契分割,即F[n]個元素分割為前半部分F[n-1]個元素,後半部分F[n-2]個元素,找出要查詢的元素在那一部分並遞迴,直到找到。

複雜度分析
最壞情況下,時間複雜度為O(log2n),且其期望複雜度也為O(log2n)。
演算法實現

  • 斐波那契查詢演算法
  • 時間複雜度O(log(n))
def fibonacci_search(lis, key):
  # 需要一個現成的斐波那契列表。其最大元素的值必須超過查詢表中元素個數的數值。
  F = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,
     233, 377, 610, 987, 1597, 2584, 4181, 6765,
     10946, 17711, 28657, 46368]
  low = 0
  high = len(lis) - 1

  # 為了使得查詢表滿足斐波那契特性,在表的最後新增幾個同樣的值
  # 這個值是原查詢表的最後那個元素的值
  # 新增的個數由F[k]-1-high決定
  k = 0
  while high > F[k]-1:
    k += 1
  print(k)
  i = high
  while F[k]-1 > i:
    lis.append(lis[high])
    i += 1
  print(lis)

  # 演算法主邏輯。time用於展示迴圈的次數。
  time = 0
  while low <= high:
    time += 1
    # 為了防止F列表下標溢位,設定if和else
    if k < 2:
      mid = low
    else:
      mid = low + F[k-1]-1

    print("low=%s, mid=%s, high=%s" % (low, mid, high))
    if key < lis[mid]:
      high = mid - 1
      k -= 1
    elif key > lis[mid]:
      low = mid + 1
      k -= 2
    else:
      if mid <= high:
        # 列印查詢的次數
        print("times: %s" % time)
        return mid
      else:
        print("times: %s" % time)
        return high
  print("times: %s" % time)
  return False

if __name__ == '__main__':
  LIST = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
  result = fibonacci_search(LIST, 444)
  print(result)

樹表查詢

1、二叉樹查詢演算法。

二叉查詢樹是先對待查詢的資料進行生成樹,確保樹的左分支的值小於右分支的值,然後在就行和每個節點的父節點比較大小,查詢最適合的範圍。 這個演算法的查詢效率很高,但是如果使用這種查詢方法要首先建立樹。

演算法思想
二叉查詢樹(BinarySearch Tree)或者是一棵空樹,或者是具有下列性質的二叉樹:

1)若任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
2)若任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
3)任意節點的左、右子樹也分別為二叉查詢樹。

二叉查詢樹性質:對二叉查詢樹進行中序遍歷,即可得到有序的數列。

複雜度分析

它和二分查詢一樣,插入和查詢的時間複雜度均為O(logn),但是在最壞的情況下仍然會有O(n)的時間複雜度。原因在於插入和刪除元素的時候,樹沒有保持平衡。

演算法實現

  • 二叉樹查詢 Python實現
class BSTNode:
    """
    定義一個二叉樹節點類。
    以討論演算法為主,忽略了一些諸如對資料型別進行判斷的問題。
    """
    def __init__(self, data, left=None, right=None):
        """
        初始化
        :param data: 節點儲存的資料
        :param left: 節點左子樹
        :param right: 節點右子樹
        """
        self.data = data
        self.left = left
        self.right = right

class BinarySortTree:
    """
    基於BSTNode類的二叉查詢樹。維護一個根節點的指標。
    """
    def __init__(self):
        self._root = None

    def is_empty(self):
        return self._root is None

    def search(self, key):
        """
        關鍵碼檢索
        :param key: 關鍵碼
        :return: 查詢節點或None
        """
        bt = self._root
        while bt:
            entry = bt.data
            if key < entry:
                bt = bt.left
            elif key > entry:
                bt = bt.right
            else:
                return entry
        return None

    def insert(self, key):
        """
        插入操作
        :param key:關鍵碼 
        :return: 布林值
        """
        bt = self._root
        if not bt:
            self._root = BSTNode(key)
            return
        while True:
            entry = bt.data
            if key < entry:
                if bt.left is None:
                    bt.left = BSTNode(key)
                    return
                bt = bt.left
            elif key > entry:
                if bt.right is None:
                    bt.right = BSTNode(key)
                    return
                bt = bt.right
            else:
                bt.data = key
                return

    def delete(self, key):
        """
        二叉查詢樹最複雜的方法
        :param key: 關鍵碼
        :return: 布林值
        """
        p, q = None, self._root     # 維持p為q的父節點,用於後面的連結操作
        if not q:
            print("空樹!")
            return
        while q and q.data != key:
            p = q
            if key < q.data:
                q = q.left
            else:
                q = q.right
            if not q:               # 當樹中沒有關鍵碼key時,結束退出。
                return
        # 上面已將找到了要刪除的節點,用q引用。而p則是q的父節點或者None(q為根節點時)。
        if not q.left:
            if p is None:
                self._root = q.right
            elif q is p.left:
                p.left = q.right
            else:
                p.right = q.right
            return
        # 查詢節點q的左子樹的最右節點,將q的右子樹連結為該節點的右子樹
        # 該方法可能會增大樹的深度,效率並不算高。可以設計其它的方法。
        r = q.left
        while r.right:
            r = r.right
        r.right = q.right
        if p is None:
            self._root = q.left
        elif p.left is q:
            p.left = q.left
        else:
            p.right = q.left

    def __iter__(self):
        """
        實現二叉樹的中序遍歷演算法,
        展示我們建立的二叉查詢樹.
        直接使用python內建的列表作為一個棧。
        :return: data
        """
        stack = []
        node = self._root
        while node or stack:
            while node:
                stack.append(node)
                node = node.left
            node = stack.pop()
            yield node.data
            node = node.right

if __name__ == '__main__':
    lis = [62, 58, 88, 48, 73, 99, 35, 51, 93, 29, 37, 49, 56, 36, 50]
    bs_tree = BinarySortTree()
    for i in range(len(lis)):
        bs_tree.insert(lis[i])
    # bs_tree.insert(100)
    bs_tree.delete(58)
    for i in bs_tree:
        print(i, end=" ")
    # print("\n", bs_tree.search(4))

2、平衡查詢樹之2-3查詢樹(2-3 Tree)
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樹的查詢效率與樹的高度是息息相關的。
距離來說,對於1百萬個節點的2-3樹,樹的高度為12-20之間,對於10億個節點的2-3樹,樹的高度為18-30之間。
  對於插入來說,只需要常數次操作即可完成,因為他只需要修改與該節點關聯的節點即可,不需要檢查其他節點,所以效率和查詢類似。


    演算法實現
class Node(object):
    def __init__(self,key):
        self.key1=key
        self.key2=None
        self.left=None
        self.middle=None
        self.right=None
    def isLeaf(self):
        return self.left is None and self.middle is None and self.right is None
    def isFull(self):
        return self.key2 is not None
    def hasKey(self,key):
        if (self.key1==key) or (self.key2 is not None and self.key2==key):
            return True
        else:
            return False
    def getChild(self,key):
        if key<self.key1:
            return self.left
        elif self.key2 is None:
            return self.middle
        elif key<self.key2:
            return self.middle
        else:
            return self.right
class 2_3_Tree(object):
    def __init__(self):
        self.root=None
    def get(self,key):
        if self.root is None:
            return None
        else:
            return self._get(self.root,key)
    def _get(self,node,key):
        if node is None:
            return None
        elif node.hasKey(key):
            return node
        else:
            child=node.getChild(key)
            return self._get(child,key)
    def put(self,key):
        if self.root is None:
            self.root=Node(key)
        else:
            pKey,pRef=self._put(self.root,key)
            if pKey is not None:
                newnode=Node(pKey)
                newnode.left=self.root
                newnode.middle=pRef
                self.root=newnode
    def _put(self,node,key):
        if node.hasKey(key):
            return None,None
        elif node.isLeaf():
            return self._addtoNode(node,key,None)
        else:
            child=node.getChild(key)
            pKey,pRef=self._put(child,key)
            if pKey is None:
                return None,None
            else:
                return self._addtoNode(node,pKey,pRef)

    def _addtoNode(self,node,key,pRef):
        if node.isFull():
            return self._splitNode(node,key,pRef)
        else:
            if key<node.key1:
                node.key2=node.key1
                node.key1=key
                if pRef is not None:
                    node.right=node.middle
                    node.middle=pRef
            else:
                node.key2=key
                if pRef is not None:
                    node.right=Pref
            return None,None
    def _splitNode(self,node,key,pRef):
        newnode=Node(None)
        if key<node.key1:
            pKey=node.key1
            node.key1=key
            newnode.key1=node.key2
            if pRef is not None:
                newnode.left=node.middle
                newnode.middle=node.right
                node.middle=pRef
        elif key<node.key2:
            pKey=key
            newnode.key1=node.key2
            if pRef is not None:
                newnode.left=Pref
                newnode.middle=node.right
        else:
            pKey=node.key2
            newnode.key1=key
            if pRef is not None:
                newnode.left=node.right
                newnode.middle=pRef
        node.key2=None
        return pKey,newnode

3、平衡查詢樹之紅黑樹(Red-Black Tree)

紅黑樹的定義
紅黑樹是一種具有紅色和黑色連結的平衡查詢樹,同時滿足:
① 紅色節點向左傾斜 ;
②一個節點不可能有兩個紅色連結;
③整個樹完全黑色平衡,即從根節點到所以葉子結點的路徑上,黑色連結的個數都相同。

紅黑樹的性質
整個樹完全黑色平衡,即從根節點到所以葉子結點的路徑上,黑色連結的個數都相同(2-3樹的第2)性質,從根節點到葉子節點的距離都相等)。

複雜度分析
最壞的情況就是,紅黑樹中除了最左側路徑全部是由3-node節點組成,即紅黑相間的路徑長度是全黑路徑長度的2倍。
下圖是一個典型的紅黑樹,從中可以看到最長的路徑(紅黑相間的路徑)是最短路徑的2倍:

演算法實現

#紅黑樹
from random import randint

RED = 'red'
BLACK = 'black'

class RBT:
    def __init__(self):
       # self.items = []
        self.root = None
        self.zlist = []

    def LEFT_ROTATE(self, x):
        # x是一個RBTnode
        y = x.right
        if y is None:
            # 右節點為空,不旋轉
            return
        else:
            beta = y.left
            x.right = beta
            if beta is not None:
                beta.parent = x

            p = x.parent
            y.parent = p
            if p is None:
                # x原來是root
                self.root = y
            elif x == p.left:
                p.left = y
            else:
                p.right = y
            y.left = x
            x.parent = y

    def RIGHT_ROTATE(self, y):
        # y是一個節點
        x = y.left
        if x is None:
            # 右節點為空,不旋轉
            return
        else:
            beta = x.right
            y.left = beta
            if beta is not None:
                beta.parent = y

            p = y.parent
            x.parent = p
            if p is None:
                # y原來是root
                self.root = x
            elif y == p.left:
                p.left = x
            else:
                p.right = x
            x.right = y
            y.parent = x

    def INSERT(self, val):

        z = RBTnode(val)
        y = None
        x = self.root
        while x is not None:
            y = x
            if z.val < x.val:
                x = x.left
            else:
                x = x.right

        z.PAINT(RED)
        z.parent = y

        if y is None:
            # 插入z之前為空的RBT
            self.root = z
            self.INSERT_FIXUP(z)
            return

        if z.val < y.val:
            y.left = z
        else:
            y.right = z

        if y.color == RED:
            # z的父節點y為紅色,需要fixup。
            # 如果z的父節點y為黑色,則不用調整
            self.INSERT_FIXUP(z)

        else:
            return

    def INSERT_FIXUP(self, z):
        # case 1:z為root節點
        if z.parent is None:
            z.PAINT(BLACK)
            self.root = z
            return

        # case 2:z的父節點為黑色
        if z.parent.color == BLACK:
            # 包括了z處於第二層的情況
            # 這裡感覺不必要啊。。似乎z.parent為黑色則不會進入fixup階段
            return

        # 下面的幾種情況,都是z.parent.color == RED:
        # 節點y為z的uncle
        p = z.parent
        g = p.parent  # g為x的grandpa
        if g is None:
            return
            #   return 這裡不能return的。。。
        if g.right == p:
            y = g.left
        else:
            y = g.right

        # case 3-0:z沒有叔叔。即:y為NIL節點
        # 注意,此時z的父節點一定是RED
        if y == None:
            if z == p.right and p == p.parent.left:
                # 3-0-0:z為右兒子,且p為左兒子,則把p左旋
                # 轉化為3-0-1或3-0-2的情況
                self.LEFT_ROTATE(p)
                p, z = z, p
                g = p.parent
            elif z == p.left and p == p.parent.right:
                self.RIGHT_ROTATE(p)
                p, z = z, p

            g.PAINT(RED)
            p.PAINT(BLACK)
            if p == g.left:
                # 3-0-1:p為g的左兒子
                self.RIGHT_ROTATE(g)
            else:
                # 3-0-2:p為g的右兒子
                self.LEFT_ROTATE(g)

            return

        # case 3-1:z有黑叔
        elif y.color == BLACK:
            if p.right == z and p.parent.left == p:
                # 3-1-0:z為右兒子,且p為左兒子,則左旋p
                # 轉化為3-1-1或3-1-2
                self.LEFT_ROTATE(p)
                p, z = z, p
            elif p.left == z and p.parent.right == p:
                self.RIGHT_ROTATE(p)
                p, z = z, p

            p = z.parent
            g = p.parent

            p.PAINT(BLACK)
            g.PAINT(RED)
            if p == g.left:
                # 3-1-1:p為g的左兒子,則右旋g
                self.RIGHT_ROTATE(g)
            else:
                # 3-1-2:p為g的右兒子,則左旋g
                self.LEFT_ROTATE(g)

            return

        # case 3-2:z有紅叔
        # 則塗黑父和叔,塗紅爺,g作為新的z,遞迴呼叫
        else:
            y.PAINT(BLACK)
            p.PAINT(BLACK)
            g.PAINT(RED)
            new_z = g
            self.INSERT_FIXUP(new_z)

    def DELETE(self, val):
        curNode = self.root
        while curNode is not None:
            if val < curNode.val:
                curNode = curNode.left
            elif val > curNode.val:
                curNode = curNode.right
            else:
                # 找到了值為val的元素,正式開始刪除

                if curNode.left is None and curNode.right is None:
                    # case1:curNode為葉子節點:直接刪除即可
                    if curNode == self.root:
                        self.root = None
                    else:
                        p = curNode.parent
                        if curNode == p.left:
                            p.left = None
                        else:
                            p.right = None

                elif curNode.left is not None and curNode.right is not None:
                    sucNode = self.SUCCESOR(curNode)
                    curNode.val, sucNode.val  = sucNode.val, curNode.val
                    self.DELETE(sucNode.val)

                else:
                    p = curNode.parent
                    if curNode.left is None:
                        x = curNode.right
                    else:
                        x = curNode.left
                    if curNode == p.left:
                        p.left = x
                    else:
                        p.right = x
                    x.parent = p
                    if curNode.color == BLACK:
                        self.DELETE_FIXUP(x)

                curNode = None
        return False

    def DELETE_FIXUP(self, x):
        p = x.parent
        # w:x的兄弟結點
        if x == p.left:
            w = x.right
        else:
            w = x.left

        # case1:x的兄弟w是紅色的
        if w.color == RED:
            p.PAINT(RED)
            w.PAINT(BLACK)
            if w == p.right:
                self.LEFT_ROTATE(p)
            else:
                self.RIGHT_ROTATE(p)

        if w.color == BLACK:
            # case2:x的兄弟w是黑色的,而且w的兩個孩子都是黑色的
            if w.left.color == BLACK and w.right.color == BLACK:
                w.PAINT(RED)
                if p.color == BLACK:
                    return
                else:
                    p.color = BLACK
                    self.DELETE_FIXUP(p)

            # case3:x的兄弟w是黑色的,而且w的左兒子是紅色的,右兒子是黑色的
            if w.left.color == RED and w.color == BLACK:
                w.left.PAINT(BLACK)
                w.PAINT(RED)
                self.RIGHT_ROTATE(w)

            # case4:x的兄弟w是黑色的,而且w的右兒子是紅
            if w.right.color == RED:
                p.PAINT(BLACK)
                w.PAINT(RED)
                if w == p.right:
                    self.LEFT_ROTATE(p)
                else:
                    self.RIGHT_ROTATE(p)

    def SHOW(self):
        self.DISPLAY1(self.root)
        return self.zlist

    def DISPLAY1(self, node):
        if node is None:
            return
        self.DISPLAY1(node.left)
        self.zlist.append(node.val)
        self.DISPLAY1(node.right)

    def DISPLAY2(self, node):
        if node is None:
            return
        self.DISPLAY2(node.left)
        print(node.val)
        self.DISPLAY2(node.right)

    def DISPLAY3(self, node):
        if node is None:
            return
        self.DISPLAY3(node.left)
        self.DISPLAY3(node.right)
        print(node.val)

class RBTnode:
    '''紅黑樹的節點型別'''
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        self.parent = None

    def PAINT(self, color):
        self.color = color

def zuoxuan(b, c):
    a = b.parent
    a.left = c
    c.parent = a
    b.parent = c
    c.left = b

if __name__ == '__main__':
    rbt=RBT()
    b = []

    for i in range(100):
        m = randint(0, 500)
        rbt.INSERT(m)
        b.append(m)

    a = rbt.SHOW()
    b.sort()
    equal = True
    for i in range(100):
        if a[i] != b[i]:
            equal = False
            break

    if not equal:
        print('wrong')
    else:
        print('OK!')

4、B樹和B+樹(B Tree/B+ Tree)
B樹簡介

B 樹可以看作是對2-3查詢樹的一種擴充套件,即他允許每個節點有M-1個子節點。
①根節點至少有兩個子節點;
②每個節點有M-1個key,並且以升序排列;
③位於M-1和M key的子節點的值位於M-1 和M key對應的Value之間;
④非葉子結點的關鍵字個數=指向兒子的指標個數-1;
⑤非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] ;
⑥其它節點至少有M/2個子節點;
⑦所有葉子結點位於同一層;
如:(M=3)

B樹演算法思想
B-樹的搜尋,從根結點開始,對結點內的關鍵字(有序)序列進行二分查詢,如果命中則結束,否則進入查詢關鍵字所屬範圍的兒子結點;重複,直到所對應的兒子指標為空,或已經是葉子結點;

B樹的特性
1.關鍵字集合分佈在整顆樹中;
2.任何一個關鍵字出現且只出現在一個結點中;
3.搜尋有可能在非葉子結點結束;
4.其搜尋效能等價於在關鍵字全集內做一次二分查詢;
5.自動層次控制;
由於限制了除根結點以外的非葉子結點,至少含有M/2個兒子,確保了結點的至少利用率,其最底搜尋效能為O(LogN)

B+ 樹簡介

B+樹是B-樹的變體,也是一種多路搜尋樹:
1.其定義基本與B-樹同,除了:
2.非葉子結點的子樹指標與關鍵字個數相同;
3.非葉子結點的子樹指標P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹
4.B-樹是開區間;
5.為所有葉子結點增加一個鏈指標;
6.所有關鍵字都在葉子結點出現;

如:(M=3)

B+樹演算法思想

B+的搜尋與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其效能也等價於在關鍵字全集做一次二分查詢;

B+樹的特性
1.所有關鍵字都出現在葉子結點的連結串列中(稠密索引),且連結串列中的關鍵字恰好是有序的;
2.不可能在非葉子結點命中;
3.非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是儲存(關鍵字)資料的資料層;
4.更適合檔案索引系統;

演算法實現

  • B樹查詢
class BTree:  #B樹
    def __init__(self,value):
        self.left=None
        self.data=value
        self.right=None

    def insertLeft(self,value):
        self.left=BTree(value)
        return self.left

    def insertRight(self,value):
        self.right=BTree(value)
        return self.right

    def show(self):
        print(self.data)

def inorder(node):  #中序遍歷:先左子樹,再根節點,再右子樹
    if node.data:
        if node.left:
            inorder(node.left)
        node.show()
        if node.right:
            inorder(node.right)

def rinorder(node):  #倒中序遍歷
    if node.data:
        if node.right:
            rinorder(node.right)
        node.show()
        if node.left:
            rinorder(node.left)

def insert(node,value):
    if value > node.data:
        if node.right:
            insert(node.right,value)
        else:
            node.insertRight(value)
    else:
        if node.left:
            insert(node.left,value)
        else:
            node.insertLeft(value)

if __name__ == "__main__":

    l=[88,11,2,33,22,4,55,33,221,34]
    Root=BTree(l[0])
    node=Root
    for i in range(1,len(l)):
        insert(Root,l[i])

    print("中序遍歷(從小到大排序 )")
    inorder(Root)
    print("倒中序遍歷(從大到小排序)")
    rinorder(Root)

5、樹表查詢總結
  二叉查詢樹平均查詢效能不錯,為O(logn),但是最壞情況會退化為O(n)。在二叉查詢樹的基礎上進行最佳化,我們可以使用平衡查詢樹。平衡查詢樹中的2-3查詢樹,這種資料結構在插入之後能夠進行自平衡操作,從而保證了樹的高度在一定的範圍內進而能夠保證最壞情況下的時間複雜度。但是2-3查詢樹實現起來比較困難,紅黑樹是2-3樹的一種簡單高效的實現,他巧妙地使用顏色標記來替代2-3樹中比較難處理的3-node節點問題。紅黑樹是一種比較高效的平衡查詢樹,應用非常廣泛,很多程式語言的內部實現都或多或少的採用了紅黑樹。
  除此之外,2-3查詢樹的另一個擴充套件——B/B+平衡樹,在檔案系統和資料庫系統中有著廣泛的應用。

分塊查詢 (log(m)+N/m)

要求是順序表,分塊查詢又稱索引順序查詢,它是順序查詢的一種改進方法。


演算法思想
將n個資料元素"按塊有序"劃分為m塊(m ≤ n)。
每一塊中的結點不必有序,但塊與塊之間必須"按塊有序";
即第1塊中任一元素的關鍵字都必須小於第2塊中任一元素的關鍵字;
而第2塊中任一元素又都必須小於第3塊中的任一元素,……

演算法流程
1、先選取各塊中的最大關鍵字構成一個索引表;
2、查詢分兩個部分:先對索引表進行二分查詢或順序查詢,以確定待查記錄在哪一塊中;
3、在已確定的塊中用順序法進行查詢。

複雜度分析
時間複雜度:O(log(m)+N/m)

# 分塊查詢是對順序查詢、二分查詢的綜合最佳化,效能介於兩者之間
# 基本原理:
# 1.將序列分為m塊,塊內部無序、外部有序
# 2.選取各塊最大元素構成索引,對索引進行二分查詢,找到所在的塊
# 3.在確定塊中用順序查詢
# 關鍵點:構建外部無序、內部有序的多子塊

import random
Range = 20
Length = 9
flag = 0
pos = -1
tabNum = 3
tabPos = -1

list = random.sample(range(Range),Length)
goal = random.randint(0,Range)
print('search ',goal,', in list:')

# 子表建立,選擇序列前m個元素排序後建立索引,根據索引建立子表
list_index = []                               #使用二維列表表示多個子序列
for i in range(tabNum):                      #在列表中新增m個列表
    list_index.append([])

for i in range(1,tabNum):                    #向第1-m子列表新增原序列的前m-1個元素作為索引,留出第一個子列表盛放最大索引,
    list_index[i].append(list[i-1])           # 但會出現最大值在第二個子列表中,第一子列表為空的情況
for i in range(1,tabNum-1):                  #將新增元素的子列表中的元素降序排列
    for j in range(1,tabNum-i):
        if list_index[j]<list_index[j+1]:
            list_index[j],list_index[j+1] = list_index[j+1],list_index[j]
# print(list_index)

for i in range(tabNum-1,Length):             #將其餘元素新增到各子列表,比索引大則放到前一個子列表中,其餘放入最後一個索引中
    for j in range(1,tabNum):
        if list[i]>list_index[j][0]:
            list_index[j-1].append(list[i])
            break
    else:
        list_index[tabNum-1].append(list[i])
# print(list_index)
if len(list_index[0]) > 1:                   #提取第一個子列表的最大值最為索引
    for i in range(len(list_index[0])-1,0,-1):
        if list_index[0][i]>list_index[0][i-1]:
            list_index[0][i],list_index[0][i-1]  =  list_index[0][i-1],list_index[0][i]
print(list_index)                            #顯示構造的子列表

for i in range(tabNum-1,-1,-1):             #將給定元素與各子列表進行比較,確定給定元素位置
    if len(list_index[i]) != 0 and goal<list_index[i][0]:
        for j in range(len(list_index[i])):
            if list_index[i][j] == goal:
                tabPos = i+1
                pos = j+1
                flag = 1

if flag:
    print("find in ",tabPos,"list ",pos,"th place")
else:
    print("not found")

# 更改索引選取,並對索引使用升序排序

import random
Range = 20
Length = 9
flag = 0
pos = -1
tabNum = 3
tabPos = -1

list = random.sample(range(Range),Length)
goal = random.randint(0,Range)
print('search ',goal,', in list:')

# 子表建立,選擇序列前m個元素排序後建立索引,根據索引建立子表
list_index = []                               #使用二維列表表示多個子序列
for i in range(tabNum):                      #在列表中新增m個列表
    list_index.append([])
for i in range(tabNum):                      #將前m個元素升序
    for j in range(tabNum-1-i):
        if list[j]>list[j+1]:
            list[j],list[j+1] = list[j+1],list[j]
for i in range(tabNum-1):                    #向前1-m子列表新增原序列的前m-1個元素作為索引,留出第m個子列表盛放最大索引,
    list_index[i].append(list[i])
for i in range(tabNum-1,Length):             #將其餘元素新增到各子列表,比索引小則放到本子列表中,其餘放入最後一個索引中
    for j in range(tabNum-1):
        if list[i]<list_index[j][0]:
            list_index[j].append(list[i])
            break
    else:
        list_index[tabNum-1].append(list[i])

for i in range(len(list_index[tabNum-1])-1,0,-1):       #一次方向冒泡,將最大值提前
    if list_index[tabNum-1][i]>list_index[tabNum-1][i-1]:
        list_index[tabNum-1][i],list_index[tabNum-1][i-1]  =  list_index[tabNum-1][i-1],list_index[tabNum-1][i]
print(list_index)                            #顯示構造的子列表

for i in range(tabNum):                     #將給定元素與各子列表進行比較,確定給定元素位置
    if goal<list_index[i][0]:
        for j in range(len(list_index[i])):
            if list_index[i][j] == goal:
                tabPos = i+1
                pos = j+1
                flag = 1
                break
        break

if flag:
    print("find in ",tabPos,"list ",pos,"th place")
else:
    print("not found")

雜湊查詢

雜湊表就是一種以鍵-值(key-indexed) 儲存資料的結構,只要輸入待查詢的值即key,即可查詢到其對應的值。

演算法思想
雜湊的思路很簡單,如果所有的鍵都是整數,那麼就可以使用一個簡單的無序陣列來實現:將鍵作為索引,值即為其對應的值,這樣就可以快速訪問任意鍵的值。這是對於簡單的鍵的情況,我們將其擴充套件到可以處理更加複雜的型別的鍵。

演算法流程

1)用給定的雜湊函式構造雜湊表;
2)根據選擇的衝突處理方法解決地址衝突;
常見的解決衝突的方法:拉鍊法和線性探測法。
3)在雜湊表的基礎上執行雜湊查詢。

複雜度分析
  單純論查詢複雜度:對於無衝突的Hash表而言,查詢複雜度為O(1)(注意,在查詢之前我們需要構建相應的Hash表)。

演算法實現

  • 忽略了對資料型別,元素溢位等問題的判斷。
class HashTable:
  def __init__(self, size):
    self.elem = [None for i in range(size)] # 使用list資料結構作為雜湊表元素儲存方法
    self.count = size # 最大表長

  def hash(self, key):
    return key % self.count # 雜湊函式採用除留餘數法

  def insert_hash(self, key):
    """插入關鍵字到雜湊表內"""
    address = self.hash(key) # 求雜湊地址
    while self.elem[address]: # 當前位置已經有資料了,發生衝突。
      address = (address+1) % self.count # 線性探測下一地址是否可用
    self.elem[address] = key # 沒有衝突則直接儲存。

  def search_hash(self, key):
    """查詢關鍵字,返回布林值"""
    star = address = self.hash(key)
    while self.elem[address] != key:
      address = (address + 1) % self.count
      if not self.elem[address] or address == star: # 說明沒找到或者迴圈到了開始的位置
        return False
    return True

if __name__ == '__main__':
  list_a = [12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34]
  hash_table = HashTable(12)
  for i in list_a:
    hash_table.insert_hash(i)

  for i in hash_table.elem:
    if i:
      print((i, hash_table.elem.index(i)), end=" ")
  print("\n")

  print(hash_table.search_hash(15))
  print(hash_table.search_hash(33))
本作品採用《CC 協議》,轉載必須註明作者和本文連結
文章!!首發於我的部落格Stray_Camel(^U^)ノ~YO

相關文章