Python實現數據結構和算法


一、算法

1.算法的時間復雜度

大 O 記法,是描述算法復雜度的符號
O(1)
  常數復雜度,最快速的算法。
  取數組第 1000000 個元素
  字典和集合的存取都是 O(1)
  數組的存取是 O(1)

O(logN)
  對數復雜度
  假設有一個有序數組,以二分法查找

O(n)
  線性復雜度
  假設有一個數組,以遍歷的方式在其中查找元素 最壞情況是全部過一遍

O(nlogn)
  求兩個數組交集,其中一個是有序數組
  A 數組每一個元素都要在 B 數組中進行查找操作
  每次查找如果使用二分法則復雜度是 logN

O(N^2)
  平方復雜度
  求兩個無序數組的交集

2.常見問題
"""
常見問題:
O(1)和O(n)區別是什么?
    O(1) 是確定的
    O(n) 是隨着數據規模增長而增長的  最壞情況是全部過一遍
    
如果第一次就找到了 還是O(n)嗎?
    我們衡量的是性質 不是特例 以最壞情況來計算
    
復雜度就意味着查詢時間的多少嗎? 常數復雜度是直接取第一百萬個元素還是按順序來?
    程序中循環的次數,而非真正意義上的時間,因為不同的機器可能執行相同的程序所耗時不同
    常數復雜度是說 執行的時間 是確定的
    
數組的長度不一樣,存取元素的時間復雜度是不一樣的嗎?
    數組是一個隨機存取的數據結構
    無論多長, 存取元素的時間都是確定的, O(1)
"""
# 都是O(1)
# def fun1():
#     for i in range(1000000):
#         print(i)
#     for i in range(1000000):
#         print(i*2)
#
# def fun2():
#     for i in range(1000000):
#         print(i)
#         print(i*2)

二、數據結構基礎

1.數組

  連續的一塊內存,存取元素時間是 O(1)插入、刪除是 O(n)

2.鏈表

  像火車,車廂掛車廂

  以下標方式讀取元素的時間是 O(n)

  插入、刪除是 O(1)

  棧和隊列是鏈表的改良型

  棧:先進后出(壓彈夾)

  隊列:先進先出(銀行取號排隊)

class Node():
    def __init__(self, element=None):
        self.e = element
        self.next = None

head = Node()
n1 = Node(111)
n2 = Node(222)
n3 = Node(333)
n1.next = n2
n2.next = n3

head.next = n1

def append(node, element):
    """
    我們往 node 的末尾插入一個元素
    :param node: 是一個 Node 實例
    :param element: 任意類型的元素
    """
    n = node
    # 這里不應該使用 while n.next: 這樣的隱含類型轉換來判定
    while n.next is not None:
        n = n.next
    # n 現在是最后一個元素
    new_node = Node(element)
    n.next = new_node

def prepend(head, element):
    n = Node(element)
    n.next = head.next
    head.next = n

def pop(head):
    """
    pop 是 stack 的兩個操作之一
    push 入棧
    pop 出棧

    現在我們有這么 3 個函數
    append 添加一個元素到末尾
    prepend 添加一個元素到開頭
    pop 刪除並返回末尾的元素

    prepend + pop 就實現了 隊列 的 入隊(enqueue)和出隊(dequeue)操作
    append + pop 就實現了 棧 的 入棧(push) 和 出棧(pop)操作
    """
    tail = head 
    while tail.next is not None:
        tail = tail.next
    # 現在 tail 是最后一個元素了
    n = head
    while n.next is not tail:
        n = n.next
    # 現在 n 是 tail 之前的元素了
    n.next = None
    return tail.e


def log_list(node):
    n = node
    s = ''
    while n is not None:
        s += (str(n.e) + ' > ')
        n = n.next
    print(s)

# log_list(n1)
# append(n1, 'gua')
# log_list(n1)
# prepend(head, 'hello')
# log_list(head)
# print('pop head ', pop(head))
# log_list(head)
鏈表

 

"""
 
隊列的特點是「先進先出」,一般有這幾個操作

# enqueue 將一個元素存入隊列中
# dequeue 將一個元素從隊列中取出,並在隊列中刪除它

# empty 查看棧是否是空的


可以把隊列看做排隊,銀行叫號機就是隊列,先取號的先入隊,叫號的時候也就先出隊

"""


# Node類是一個節點,有兩個屬性,一個存儲元素,一個存儲指向另一個節點的引用
class Node():
    def __init__(self, element=None, next=None):
        self.element = element
        self.next = next

    # 這個函數是會在print的時候被自動調用,就是把這個Node顯示出來
    def __repr__(self):
        return str(self.element)


class Myqueue():
    # 初始化函數,自動被調用
    # 初始化Queue()類的時候,它有2個屬性,分別指向頭尾
    def __init__(self):
        self.head = Node()
        self.tail = self.head
    
    # 如果head的next屬性為空,則說明隊列是空的
    def empty(self):
        return self.head.next is None

    # 創建一個node
    # 讓tail.next指向它
    # 讓tail指向它,tail現在就是新的隊尾了
    def enqueue(self, element):
        n = Node(element)
        self.tail.next = n
        self.tail = n

    # 取出head.next指向的元素,如果隊列不是空的,就讓head.next指向node.next,這樣node就不在隊列中了
    def dequeue(self):
        node = self.head.next
        if not self.empty():
            self.head.next = node.next
        return node


# 測試函數
def test():
    q = Myqueue()

    q.enqueue(1)
    q.enqueue(2)
    q.enqueue(3)
    q.enqueue(4)

    print(q.dequeue())
    print(q.dequeue())
    print(q.dequeue())
    print(q.dequeue())


if __name__ == '__main__':
    # 運行測試函數
    test()
Myqueue
"""
 數據結構的核心是,一個結構只有特定的操作,可以實現特定的功能

 棧的特點是「先進后出」,一般有這幾個操作
# push 將一個元素存入棧中
# pop  將一個元素從棧中取出,並在棧中刪除它

# top  將一個元素從棧中取出
# is_empty 查看棧是否是空的

"""


# Node類是一個節點,有兩個屬性,一個存儲元素,一個存儲指向另一個節點的引用
class Node():
    def __init__(self, element=None, next=None):
        self.element = element
        self.next = next

    # 這個函數是會在print的時候被自動調用,就是把這個Node顯示出來
    def __repr__(self):
        return str(self.element)


class Mystack():
    # 初始化函數,自動被調用
    # 初始化Stack()類的時候,它有一個head屬性,值是一個空的Node
    def __init__(self):
        self.head = Node()
    
    # 如果head的next屬性為空,則說明棧是空的
    def is_empty(self):
        return self.head.next is None

    # 創建一個node,並讓它指向當前head.next指向的元素,再把head.next指向它
    def push(self, element):
        self.head.next = Node(element, self.head.next)

    # 取出head.next指向的元素,如果棧不是空的,就讓head.next指向node.next,這樣node就不在棧中了
    def pop(self):
        node = self.head.next
        if not self.is_empty():
            self.head.next = node.next
        return node

    # head.next就是棧里面第一個元素
    def top(self):
        return self.head.next


# 測試函數
def test():
    s = Mystack()

    s.push(1)
    s.push(2)
    s.push(3)
    s.push(4)

    print(s.pop())
    print(s.pop())
    print(s.pop())
    print(s.pop())


if __name__ == '__main__':
    # 運行測試函數
    test()
Mystack
3.字典(哈希表 對象 Map)

  把字符串轉為數字作為下標存儲到數組中

  字符串轉化為數字的算法是 O(1)

  所以字典的存取操作都是 O(1)

  除非對數據有順序要求,否則字典永遠是最佳選擇

  字符串轉化為數字的算法

    1,確定數據規模,這樣可以確定容器數組的大小 Size

    2,把字符當作 N 進制數字得到結果

      'qwe' 被視為 q* 1 + w * 10 + e* 100 得到結果 n

      n % Size 作為字符串在數組中的下標

      通常 Size 會選一個 素數

    3, 當下標沖突(沖突屬於叫做碰撞)的時候, 我們有標准解決碰撞的辦法

      鏈接法

class HashTable(object):
    def __init__(self):
        # table 是用來存儲數據的數組
        # 先讓它有 10007 個格子好了
        # 上課的時候說過, 這個尺寸最好選素數
        # 這樣可以得到更為合理的下標分布
        self.table_size = 10007
        self.table = [0] * self.table_size

    # 這個魔法方法是用來實現 in  not in 語法的
    def __contains__(self, item):
        return self.has_key(item)
        
    def has_key(self, key):
        """
        檢查一個 key 是否存在, 時間很短, 是 O(1)
        如果用 list 來存儲, 需要遍歷, 時間是 O(n)
        """
        index = self._index(key)
        # 取元素
        v = self.table[index]
        if isinstance(v, list):
            # 檢查是否包含我們要找的 key
            for kv in v:
                if kv[0] == key:
                    return True
        # 如果得到的是 int 0 說明沒找到, 返回 False
        # 如果得到的是 list 但是遍歷結果沒有我們要找的 key 也是沒找到
        return False

    def _insert_at_index(self, index, key, value):
        # 檢查下標處是否是第一次插入數據
        v = self.table[index]
        data = [key, value]
        # 也可以用這個判斷 if v == 0:
        if isinstance(v, int):
            # 如果是第一次, 得到的是 int 0
            # 那么就插入一個 list 來存, 以后相同 key 的元素都放這里面
            # 注意我們把 key value 作為一個數組保存進去了, 這是因為
            # 會出現相同 hash 值的 key
            # 這時候就需要比較原始信息來找到相應的數據
            self.table[index] = [data]
        else:
            # 如果不是, 得到的會是 list, 直接 append
            self.table[index].append(data)

    def add(self, key, value):
        """
        add 函數往 hashtable 中加入一對元素
        我們先只支持字符串當 key; value是uuid生成的隨機字符串
        """
        # 先計算出下標
        index = self._index(key)
        # 在下標處插入元素
        self._insert_at_index(index, key, value)

    def get(self, key, default_value=None):
        """
        這個和 dict 的 get 函數一樣
        """
        index = self._index(key)
        # 取元素
        v = self.table[index]
        if isinstance(v, list):
            # 檢查是否包含我們要找的 key
            for kv in v:
                if kv[0] == key:
                    return kv[1]
        # 如果得到的是 int 0 說明沒找到, 返回 default_value
        # 如果得到的是 list 但是遍歷結果沒有我們要找的 key 也是沒找到
        return default_value

    def _index(self, key):
        # 先計算出下標
        return self._hash(key) % self.table_size

    def _hash(self, s):
        """
        # 輸入一個字符串,生成一串對應的數字
        下划線開始的函數被我們視為私有函數
        但實際上還是可以在外部調用, 這只是一個給自己看的標記
        """
        n = 1
        f = 1
        for i in s:
            n += ord(i) * f
            f *= 10
        return n


def test():
    import uuid
    names = [
        'gua',
        'xiao',
        'name',
        'web',
        'python',
    ]
    ht = HashTable()
    for key in names:
        value = uuid.uuid4()
        ht.add(key, value)
        print('add 元素', key, value)
    for key in names:
        v = ht.get(key)
        print('get 元素', key, v)
    print('魔法方法', 'gua' in ht)


if __name__ == '__main__':
    test()
hash
4.搜索樹
class Tree(object):
    def __init__(self, element=None):
        self.element = element
        self.left = None
        self.right = None

    def traversal(self):
        """
        樹的遍歷, 是一個遞歸操作
        """
        print(self.element)
        if self.left is not None:
            self.left.traversal()
        if self.right is not None:
            self.right.traversal()

    def reverse(self):
        self.left, self.right = self.right, self.left
        if self.left is not None:
            self.left.reverse()
        if self.right is not None:
            self.right.reverse()


def test():
    # 手動構建二叉樹
    # 為什么手動這么麻煩呢, 因為一般都是自動生成的
    # 這里只需要掌握性質就好
    t = Tree(0)
    left = Tree(1)
    right = Tree(2)
    t.left = left
    t.right = right
    # 遍歷
    t.traversal()


if __name__ == '__main__':
    test()
tree

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM