1. 程式人生 > >Python-資料結構與演算法(十一、字典(對映)——基於兩種不同的底層實現)

Python-資料結構與演算法(十一、字典(對映)——基於兩種不同的底層實現)

保證一週更兩篇吧,以此來督促自己好好的學習!程式碼的很多地方我都給予了詳細的解釋,幫助理解。好了,幹就完了~加油!
宣告:本python資料結構與演算法是imooc上liuyubobobo老師java資料結構的python改寫,並添加了一些自己的理解和新的東西,liuyubobobo老師真的是一位很棒的老師!超級喜歡他~
如有錯誤,還請小夥伴們不吝指出,一起學習~
No fears, No distractions.

一、字典簡述

  1. 字典(也叫對映)
  2. 結構特點:儲存(鍵,值)資料對的資料結構(Key,Value)。根據鍵(Key),來尋找值(Value)
  3. 應用:花名冊(學號 -> 人)、車輛管理(車牌號 -> 車)、資料庫(id -> 資訊)、詞頻統計(單詞 -> 頻率)
  4. 字典容易使用二分搜尋樹或者連結串列來實現,比如用二分搜尋樹來實現,相應的更改一下Node包含的元素就好了,比如:
    用二分搜尋樹實現時:
# 這裡為了更清楚的展示Node類,用C++來寫的。
class Node{
    K key;
    V value;
    Node* left;
    Node* right
}

用連結串列實現時:

class Node{
    K key;
    V value;
    Node*
next; }
  1. 字典介面:
    void add(K, V) 新增元素
    V remove(K) 刪除元素(Key充當索引的作用)
    bool contains(K) 是否包含鍵值為K的元素
    V get(K) 獲取鍵值為K的Value
    int getSize() 獲取字典的大小
    bool isEmpty() 判斷字典是否為空
  2. 基於二分搜尋樹的字典是有順序性的,而基於連結串列實現的字典是無順序性的,但是一般不用連結串列來實現無順序性的字典,而用雜湊表,雜湊表後面會講到。

二、基於二分搜尋樹的集合

1. 實現

# -*- coding: utf-8 -*-
# Author:           Annihilation7
# Data: 2018-10-20 10:30am # Python version: 3.6 from queue.loopqueue import LoopQueue # 會用到我們以前實現的迴圈佇列 class Node: def __init__(self, key=None, value=None): """ Description: 節點類的建構函式 """ self.key = key # 鍵 self.value = value # value self.left = None # 指向左孩子的標籤 self.right = None # 指向右孩子的標籤 class BstDict: def __init__(self): """ Description: 基於二分搜尋樹的字典類的建構函式 """ self._root = None # 初始化根節點為None self._size = 0 # 初始化self._size def getSize(self): """ Description: 獲取字典內有效元素的個數 Returns: 有效元素的個數 """ return self._size def isEmpty(self): """ Description: 判斷字典是否為空 Returns: bool值,空為True """ return self._size == 0 def add(self, key, value): """ Description: 向字典中新增鍵-值對,若鍵已經存在字典中,那就更新這個鍵所對應的value 時間複雜度:O(logn) Params: - key: 待新增的鍵 -value: 待新增的鍵所對應的value """ self._root = self._add(self._root, key, value) # 呼叫self._add方法,常規套路 def contains(self, key): """ Description: 檢視字典的鍵中是否包含key 時間複雜度:O(logn) Params: - key: 待查詢的鍵值 Returns: bool值,存在為True """ return self._getNode(self._root, key) is not None # 呼叫self._getNode私有函式,看返回值是否是None def get(self, key): """ Description: 得到字典中鍵為key的value值 時間複雜度:O(logn) Params: - key: 待查詢的鍵值 Returns: 相應的value值。若key不存在,就返回None """ node = self._getNode(self._root, key) # 拿到鍵為key的Node if node: # 如果該Node不是None return node.value # 返回對應的value else: return None # 否則(此時不存在攜帶key的Node)返回None def set(self, key, new_value): """ Description: 將字典中鍵為key的Node的value設為new_value。注意,為防止與add函式發生混淆, 此函式預設使用者已經確信key在字典中,否則報錯。並不會有什麼新建Node的操作,因為這麼做為與add函式有相同的功能,就沒有意義了。 時間複雜度:O(logn) Params: - key: 將要被設定的Node的鍵 - new_value: 新的value值 """ node = self._getNode(self._root, key) if node is None: raise Exception('%s doesn\'t exist!' % key) #報錯就完事了 node.value = new_value def remove(self, key): """ Description: 將字典中鍵為key的Node刪除。注:若不存在攜帶key的Node,返回Node就好。這個remove函式和前面的二分搜尋樹的remove函式極為相似,不理解的可以去前面看一下 時間複雜度:O(logn) Params: - key: 待刪除的鍵 Returns: 被刪除節點的value """ ret = self._minimum(self._root) # 呼叫self._minimum函式儲存攜帶最小值的節點,便於返回 self._root = self._remove(self._root, key) # 呼叫self._remove函式 return ret.value # 返回攜帶最小值節點的value def printBstDict(self): """對字典進行列印操作,這裡使用廣度優先遍歷,因為比較直觀""" if self._root is None: return queue = LoopQueue() queue.enqueue(self._root) while not queue.isEmpty(): node = queue.dequeue() print('[Key: %s, Value: %s]' % (node.key, node.value)) if node.left: queue.enqueue(node.left) if node.right: queue.enqueue(node.right) # private method def _add(self, node, key, value): """ Description: 向以node為根的二叉搜尋樹中新增鍵-值對 Params: - node: 根節點 - key: 待新增的鍵 -value: 待新增的鍵所對應的value Returns: 新增後新的根節點 """ getNode = self._getNode(self._root, key) # 先判斷字典中是否存在攜帶這個鍵的Node if getNode is not None: # 如果已經存在 getNode.value = value # 將這個節點的value設為新傳入的value就好 return node # 返回根節點,不需要維護self._size哦 if node is None: # 遞迴到底的情況,該新增節點啦 self._size += 1 # 維護self._size return Node(key, value) # 將新節點返回 if key < node.key: # 待新增key小於node的key,向左子樹繼續前進。這裡的操作在二叉搜尋樹中詳細講過,就不BB了 node.left = self._add(node.left, key, value) elif key > node.key: # 待新增key大於node的key,向右子樹繼續前進。 node.right = self._add(node.right, key, value) return node # 將node返回,這樣在遞迴的迴歸過程中逐級返回,最終返回的是根節點 def _remove(self, node, key): """ Description: 將以node為根節點的字典中鍵為key的Node刪除。注:若不存在攜帶key的Node,返回Node就好。 時間複雜度:O(logn) Params: - node: 以node為根節點的字典 - key: 待刪除的節點所攜帶的鍵 Returns: 刪除操作後的根節點 """ if node is None: # 到頭了都沒找到key return None # 直接返回None if key < node.key: # 待刪除key小於當前Node的key node.left = self._remove(node.left, key) # 往左子樹遞迴 return node elif node.key < key: # 待刪除key大於當前Node的key node.right = self._remove(node.right, key) # 網右子樹遞迴 return node else: # 此時待刪除key和當前Node的key相等,別忘了有三種情況 if node.left is None: # 左子樹為空的情況 right_node = node.right # 記錄當前Node的右孩子 node.right = None # 當前Node的右孩子設為None,便於垃圾回收 self._size -= 1 # 維護self._size return right_node # 將被刪除Node的右孩子返回,即用右孩子來代替被刪除節點的位置 elif node.right is None: # 右子樹為空的情況,大同小異,不再贅述 left_node = node.left node.left = None self._size -= 1 return left_node else: # 這裡不再贅述了,不清楚的話回去看看二分搜尋樹那章,一樣的操作 successor = self._minimum(node.right) successor.right = self._removeMin(node.right) self._size += 1 successor.left = node.left node.left = node.right = None self._size -= 1 return successor def _minimum(self, node): """ Description: 返回以node為根的二分搜尋樹的攜帶最小值的節點,下面的這些操作在二分搜尋樹中均有涉及,如果看不懂回到那一章再理解下就好了 Params: - node: 根節點 Returns: 攜帶最小值的節點 """ if node.left is None: # 遞迴到底的情況,node的左孩子已經為空了 return node # 返回node return self._minimum(node.left) # 否則往左子樹繼續擼 def _removeMin(self, node): """ Description: 刪除以node為根的二分搜尋樹的攜帶最小值的節點,同理,返回刪除後的根節點 Params: - node: 根節點 Returns: 返回刪除操作後的樹的根節點 """ if node.left is None: # 遞迴到底的情況,node的左孩子已經為空 right_node = node.right # 記錄node的右孩子,為None也無所謂的 node.right = None # 將node的右孩子設為None,使其與樹斷絕聯絡,從而被垃圾回收 self._size -= 1 # 維護self._size return right_node # 返回node的右孩子,即用右孩子來代替當前的node node.left = self._removeMin(node.left) # 否則往node的左子樹繼續擼 return node # 返回node,從而最終返回根節點 def _getNode(self, node, key): """ Description: 設定一個根據key來找到以node為根的二叉搜尋樹的相對應的Node的私有函式,便於方便的實現其他函式 Params: - node: 傳入的根節點 - key: 帶查詢的key Returns: 攜帶key的節點Node, 若不存在攜帶key的節點,返回None """ if node is None: # 都到底了還沒有找到,直接返回None return None # 注意我們的二分搜尋樹依舊不包含重複的鍵哦~這也是字典的基本特點 if key < node.key: # 待查詢的key小於當前節點的key,向左子樹遞迴就完事了 return self._getNode(node.left, key) elif node.key < key: # 待查詢的key大於當前節點的key,向右子樹遞迴就完事了 return self._getNode(node.right, key) else: # 此時帶查詢的key和node.key是相等的,直接返回這個Node就好 return node

2. 測試

from DICT.bstdict import BstDict  # 我們的基於二分搜尋樹的字典類寫在bstdict.py檔案中

test_bst = BstDict()
print('初始Size: ', test_bst.getSize())
print('是否為空?', test_bst.isEmpty())

add_list = [15, 4, 25, 22, 3, 19, 23, 7, 28, 24]
print('待新增元素:', add_list)
for add_elem in add_list:
    test_bst.add(add_elem, str(add_elem) + 'x')

    ##################################################
    #                  15(15x)                       #
    #               /           \                    #
    #            4(4x)          25(x)                #
    #            /    \       /       \              #
    #         3(3x)   7(7x) 22(22x)  28(28x)         #
    #                        /   \                   #
    #                    19(19x)  23(23x)            #
    #                               \                #
    #                                24(24x)         #
    ##################################################

print('新增元素後列印集合:')
test_bst.printBstDict()
print('此時的Size: ', test_bst.getSize())
print('字典中是否包含鍵23?', test_bst.contains(23))
print('獲取鍵23的value:', test_bst.get(23))
print('將鍵為7的value設為"努力學習"並列印:')
test_bst.set(7, '努力學習')
test_bst.printBstDict()
print('刪除鍵22並列印:')
test_bst.remove(22)
test_bst.printBstDict()
print('此時的Size:', test_bst.getSize())

3. 輸出

初始Size:  0
是否為空? True
待新增元素: [15, 4, 25, 22, 3, 19, 23, 7, 28, 24]
新增元素後列印集合:
[Key: 15, Value: 15x]
[Key: 4, Value: 4x]
[Key: 25, Value: 25x]
[Key: 3, Value: 3x]
[Key: 7, Value: 7x]
[Key: 22, Value: 22x]
[Key: 28, Value: 28x]
[Key: 19, Value: 19x]
[Key: 23, Value: 23x]
[Key: 24, Value: 24x]
此時的Size:  10
字典中是否包含鍵23True
獲取鍵23的value: 23x
將鍵為7的value設為"努力e學習"並列印:
[Key: 15, Value: 15x]
[Key: 4, Value: 4x]
[Key: 25, Value: 25x]
[Key: 3, Value: 3x]
[Key: 7, Value: 努力學習]
[Key: 22, Value: 22x]
[Key: 28, Value: 28x]
[Key: 19, Value: 19x]
[Key: 23, Value: 23x]
[Key: 24, Value: 24x]
刪除鍵22並列印:
[Key: 15, Value: 15x]
[Key: 4, Value: 4x]
[Key: 25, Value: 25x]
[Key: 3, Value: 3x]
[Key: 7, Value: 努力學習]
[Key: 23, Value: 23x]
[Key: 28, Value: 28x]
[Key: 19, Value: 19x]
[Key: 24, Value: 24x]
此時的Size: 9

三、基於連結串列的字典

1. 實現

# -*- coding: utf-8 -*-
# Author:           Annihilation7
# Data:             2018-10-19   7:29 pm
# Python version:   3.6

class Node:
    def __init__(self, key=None, value=None, next=None):
        """
        Description: 節點的建構函式
        Params:
        - key: 傳入的鍵值,預設為None
        - value: 傳入的鍵所對應的value值,預設為None
        - next: 指向下一個Node的標籤,預設為None
        """
        self.key = key     
        self.value = value 
        self.next = next      # 下一個節點為None


class LinkedListDict:
    """以連結串列作為底層資料結構的字典類"""
    def __init__(self):
        """
        Description: 字典的建構函式
        """
        self._dummyhead = Node()  # 建立一個虛擬頭結點,前面講過,不再贅述
        self._size = 0            # 字典中有效元素的個數

    def getSize(self):
        """
        Description: 獲取字典中有效元素的個數
        Returns:
        有效元素的個數
        """
        return self._size 

    def isEmpty(self):
        """
        Description: 判斷字典是否為空
        Returns:
        bool值,空為True
        """
        return self._size == 0

    def contains(self, key):
        """
        Description: 檢視字典的鍵中是否包含key
        時間複雜度:O(n)
        Params:
        - key: 待查詢的鍵值
        Returns:
        bool值,存在為True
        """
        return self._getNode(key) is not None   # 呼叫self._getNode私有函式,看返回值是否是None

    def get(self, key):
        """
        Description: 得到字典中鍵為key的value值
        時間複雜度:O(n)
        Params:
        - key: 待查詢的鍵值
        Returns:
        相應的value值。若key不存在,就返回None
        """
        node = self._getNode(key)   # 拿到鍵為key的Node
        if node:                    # 如果該Node不是None
            return node.value       # 返回對應的value
        else:
            return None             # 否則(此時不存在攜帶key的Node)返回None

    def add(self, key, value):
        """
        Description: 向字典中新增key,value鍵值對。若字典中已經存在相同的key,更新其value,否咋在頭部新增Node,因為時間複雜度為O(1)
        時間複雜度:O(n)
        Params:
        - key: 待新增的鍵值
        - value: 待新增的鍵值的value
        """
        node = self._getNode(key)    # 先判斷字典中是否存在這個鍵
        if node != None:             # 已經存在
            node.value = value       # 更新這個Node的value
        else:
            self._dummyhead.next = Node(key, value, self._dummyhead.next)  # 否則在頭部新增,新增操作連結串列那一章有講,這裡不再贅述
            self._size += 1          # 維護self._size

    def set(self, key, new_value):
        """
        Description: 將字典中鍵為key的Node的value設為new_value。注意,為防止與add函式發生混淆,
        此函式預設使用者已經確信key在字典中,否則報錯。並不會有什麼新建Node的操作,因為這麼做為與add函式有相同的功能,就沒有意義了。
        時間複雜度:O(n)
        Params:
        - key: 將要被設定的Node的鍵
        - new_value: 新的value值
        """
        node = self._getNode(key)    # 找到攜帶這個key的Node
        if node is None:             # 沒找到
            raise Exception('%s doesn\'t exist!' % key)   #報錯就完事了
        node.value = new_value       # 找到了就直接將返回節點的value設為new_value

    def remove(self, key):
        """
        Description: 將字典中鍵為key的Node刪除。注:若不存在攜帶key的Node,返回Node就好。
        時間複雜度:O(n)
        Params:
        - key: 待刪除的鍵
        Returns:
        被刪除節點的value
        """
        pre = self._dummyhead         # 找到要被刪除節點的前一個節點(慣用手法,不再贅述)
        while pre.next is not None:   # pre的next只要不為空
            if pre.next.key == key:   # 如果找到了
                break                 # 直接break,此時pre停留在要被刪除節點的前一個節點
            pre = pre.next            # 否則往後擼
        
        if pre.next is not None:      # 此時找到了
            delNode = pre.next        # 記錄一下要被刪除的節點,方便返回其value
            pre.next = delNode.next   # 不再贅述,如果不懂就去看看連結串列那節吧。O(∩_∩)O
            delNode.next = None       # delNode的下一個節點設為None,使delNode完全與字典脫離,便於垃圾回收器回收
            self._size -= 1           # 維護self._size
            return delNode.value      # 返回被刪除節點的value
        
        return None                   # 此時pre的next是None!說明並沒有找到這個key,返回None就好了。

    def printLinkedListDict(self):
        """列印字典元素"""
        cur = self._dummyhead.next 
        while cur != None:
            print('[Key: %s, Value: %s]' % (cur.key, cur.value), end='-->')
            cur = cur.next
        print('None')
  
    # private functions
    def _getNode(self, key):
        """
        Description: 一個輔助函式,是私有函式。功能就是返回要查詢的鍵的Node,若key不存在就返回None
        時間複雜度:O(n)
        Params:
        - key: 要查詢的鍵值
        Returns:
        返回帶查詢key的節點,若不存在返回None
        """
        cur = self._dummyhead.next   # 記錄當前節點
        while cur != None:           # cur沒到頭       
            if cur.key == key:       # 找到了
                return cur