Python3.6實現常用數據結構和算法(鏈表和二叉樹經典問題,八大排序和三大查找)

前言

Python大法好,除了工作用的OC外,其他時間Python還是很好用的,比如刷題,寫腳本,美滋滋。。。

數據結構只是靜態的描述了數據元素之間的關係。

高效的程序需要在數據結構的基礎上設計和選擇算法。

程序 = 數據結構 + 算法

總結:算法是爲了解決實際問題而設計的,數據結構是算法需要處理的問題載體

抽象數據類型(ADT)的含義是指一個數學模型以及定義在此數學模型上的一組操作。即把數據類型和數據類型上的運算捆在一起,進行封裝。引入抽象數據類型的目的是把數據類型的表示和數據類型上運算的實現與這些數據類型和運算在程序中的引用隔開,使它們相互獨立。

最常用的數據運算有五種:

  • 插入
  • 刪除
  • 修改
  • 查找
  • 排序

 

順序表

在程序中,經常需要將一組(通常是同爲某個類型的)數據元素作爲整體管理和使用,需要創建這種元素組,用變量記錄它們,傳進傳出函數等。一組數據中包含的元素個數可能發生變化(可以增加或刪除元素)。

對於這種需求,最簡單的解決方案便是將這樣一組元素看成一個序列,用元素在序列裏的位置和順序,表示實際應用中的某種有意義的信息,或者表示數據之間的某種關係。

這樣的一組序列元素的組織形式,我們可以將其抽象爲線性表。一個線性表是某類元素的一個集合,還記錄着元素之間的一種順序關係。線性表是最基本的數據結構之一,在實際程序中應用非常廣泛,它還經常被用作更復雜的數據結構的實現基礎。

根據線性表的實際存儲方式,分爲兩種實現模型:

  • 順序表,將元素順序地存放在一塊連續的存儲區裏,元素間的順序關係由它們的存儲順序自然表示。
  • 鏈表,將元素存放在通過鏈接構造起來的一系列存儲塊中。

圖a表示的是順序表的基本形式,數據元素本身連續存儲,每個元素所佔的存儲單元大小固定相同,元素的下標是其邏輯地址,而元素存儲的物理地址(實際內存地址)可以通過存儲區的起始地址Loc (e0)加上邏輯地址(第i個元素)與存儲單元大小(c)的乘積計算而得,即:

Loc(ei) = Loc(e0) + c*i

故,訪問指定元素時無需從頭遍歷,通過計算便可獲得對應地址,其時間複雜度爲O(1)。

tip:數據存儲在內存中根據類型分配字節,例如32位機器中int是4個字節,char是一個字節,那麼一個字節就是八位 0000 0000,當我們把一個int類型存儲進去的時候,會佔有4個字節也就就是32位,用0填充,計算機去查找的時候,根據首地址偏移4個字節,組合起來就是對應的數組,如果char類型,偏移一個字節,拿出來匹配char類型

如果元素的大小不統一,則須採用圖b的元素外置的形式,將實際數據元素另行存儲,而順序表中各單元位置保存對應元素的地址信息(即鏈接)。由於每個鏈接所需的存儲量相同,通過上述公式,可以計算出元素鏈接的存儲位置,而後順着鏈接找到實際存儲的數據元素。注意,圖b中的c不再是數據元素的大小,而是存儲一個鏈接地址所需的存儲量,這個量通常很小。

圖b這樣的順序表也被稱爲對實際數據的索引,這是最簡單的索引結構。

可以看到圖a是存儲的實際同類型的數據,而圖b存儲的是不同類型的數據(但是存儲的是數據的指針 64位 8字節固定)

順序表實現和結構

一個順序表的完整信息包括兩部分,一部分是表中的元素集合,另一部分是爲實現正確操作而需記錄的信息,即有關表的整體情況的信息,這部分信息主要包括元素存儲區的容量和當前表中已有的元素個數兩項。

圖a爲一體式結構,存儲表信息的單元與元素存儲區以連續的方式安排在一塊存儲區裏,兩部分數據的整體形成一個完整的順序表對象。

一體式結構整體性強,易於管理。但是由於數據元素存儲區域是表對象的一部分,順序表創建後,元素存儲區就固定了。

圖b爲分離式結構,表對象裏只保存與整個表有關的信息(即容量和元素個數),實際數據元素存放在另一個獨立的元素存儲區裏,通過鏈接與基本表對象關聯。

一體式結構由於順序表信息區與數據區連續存儲在一起,所以若想更換數據區,則只能整體搬遷,即整個順序表對象(指存儲順序表的結構信息的區域)改變了。

分離式結構若想更換數據區,只需將表信息區中的數據區鏈接地址更新即可,而該順序表對象不變。

採用分離式結構的順序表,若將數據區更換爲存儲空間更大的區域,則可以在不改變表對象的前提下對其數據存儲區進行了擴充,所有使用這個表的地方都不必修改。只要程序的運行環境(計算機系統)還有空閒存儲,這種表結構就不會因爲滿了而導致操作無法進行。人們把採用這種技術實現的順序表稱爲動態順序表,因爲其容量可以在使用中動態變化。

Python中的順序表

Python中的list和tuple兩種類型採用了順序表的實現技術,具有前面討論的順序表的所有性質。

tuple是不可變類型,即不變的順序表,因此不支持改變其內部狀態的任何操作,而其他方面,則與list的性質類似。

list的基本實現技術

Python標準類型list就是一種元素個數可變的線性表,可以加入和刪除元素,並在各種操作中維持已有元素的順序(即保序),而且還具有以下行爲特徵:

  • 基於下標(位置)的高效元素訪問和更新,時間複雜度應該是O(1);

    爲滿足該特徵,應該採用順序表技術,表中元素保存在一塊連續的存儲區中。

  • 允許任意加入元素,而且在不斷加入元素的過程中,表對象的標識(函數id得到的值)不變。

    爲滿足該特徵,就必須能更換元素存儲區,並且爲保證更換存儲區時list對象的標識id不變,只能採用分離式實現技術。

在Python的官方實現中,list就是一種採用分離式技術實現的動態順序表。這就是爲什麼用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。

在Python的官方實現中,list實現採用瞭如下的策略:在建立空表(或者很小的表)時,系統分配一塊能容納8個元素的存儲區;在執行插入操作(insert或append)時,如果元素存儲區滿就換一塊4倍大的存儲區。但如果此時的表已經很大(目前的閥值爲50000),則改變策略,採用加一倍的方法。引入這種改變策略的方式,是爲了避免出現過多空閒的存儲位置。

 

OC中NSMutableArray的實現技術

之前也有文章探討過NSMutableArray的底層,分離式那是肯定的,動態擴容,不光如此,NSMutableArray還實現了環形緩衝區數據結構,讓插入保序的情況下儘可能少的移動內存地址進行插入或者刪除

NSMutableArray和NSDictionary底層實現

 

鏈表

爲什麼需要鏈表

可以根據上面的介紹順序表可以看出

1.順序表的構建需要預先知道數據大小來申請連續的存儲空間,

2.而在進行擴充時又需要進行數據的搬遷,所以使用起來並不是很靈活。

鏈表結構可以充分利用計算機內存空間,實現靈活的內存動態管理。

鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,但是不像順序表一樣連續存儲數據,而是在每一個節點(數據存儲單元)裏存放下一個節點的位置信息(即地址)。

單鏈表

單向鏈表也叫單鏈表,是鏈表中最簡單的一種形式,它的每個節點包含兩個域,一個信息域(元素域)和一個鏈接域。這個鏈接指向鏈表中的下一個節點,而最後一個節點的鏈接域則指向一個空值。

代碼實現

class Node(object):
    """節點創建"""

    def __init__(self, item):
        self.ele = item
        self.next = None


class SingleNodeList(object):
    """鏈表創建 可以帶參,也可以不帶"""

    def __init__(self, node=None):
        # 不帶參數就是None
        self.__head = node

    def is_empty(self):
        """鏈表是否爲空"""
        return self.__head is None

    def length(self):
        """鏈表長度"""
        cur = self.__head
        count = 0
        # 遍歷
        while cur is not None:
            cur = cur.next
            count += 1
        return count

    def travel(self):
        """遍歷整個鏈表"""
        cur = self.__head
        while cur is not None:
            print(cur.ele, end=" ")
            cur = cur.next
        print("")

    def add(self, item):
        """鏈表頭部添加元素"""
        node = Node(item)
        node.next = self.__head
        self.__head = node

    def append(self, item):
        """鏈表尾部添加元素"""
        if self.is_empty():
            self.__head = Node(item)
            return
        cur = self.__head
        while cur.next is not None:
            cur = cur.next
        cur.next = Node(item)

    def insert(self, pos, item):
        """
        指定位置添加元素
        :param pos: 0開始
        :param item: 插入元素
        :return: None
        """
        # 頭插
        if pos <= 0:
            self.add(item)
        # 尾插
        elif pos >= self.length():
            self.append(item)
        # 中間插入
        else:
            pre = self.__head
            index = 0
            while index < pos - 1:
                pre = pre.next
                index += 1
            # 插入節點先鏈接,然後再改變遊標原有next
            node = Node(item)
            node.next = pre.next
            pre.next = node

    def remove(self, item):
        """刪除節點"""
        pre = None
        cur = self.__head
        
        while cur is not None:
            # 匹配到
            if cur.ele == item:
                # 第一個元素單獨處理
                if cur == self.__head:
                    self.__head = cur.next
                else:
                    # 其他元素用pre指針和cur指針操作
                    pre.next = cur.next
                break
            else:
                # 往後移動
                pre = cur
                cur = cur.next

    def search(self, item):
        """查找節點是否存在"""
        cur = self.__head
        while cur is not None:
            if cur.ele == item:
                return True
            else:
                cur = cur.next
        return False


if __name__ == "__main__":
    nodeList = SingleNodeList()

    nodeList.append(1)
    nodeList.add(100)
    nodeList.append(6)
    nodeList.add((0))
    nodeList.insert(7, 200)
    nodeList.insert(-1, 300)
    nodeList.insert(11, 400)

    print(nodeList.is_empty())
    print(nodeList.length())
    nodeList.travel()

    nodeList.remove(0)
    nodeList.travel()

鏈表失去了順序表隨機讀取的優點,同時鏈表由於增加了結點的指針域,空間開銷比較大,但對存儲空間的使用要相對靈活。

鏈表與順序表的各種操作複雜度如下所示:

操作 鏈表 順序表
訪問元素 O(n) O(1)
在頭部插入/刪除 O(1) O(n)
在尾部插入/刪除 O(n) O(1)
在中間插入/刪除 O(n) O(n)

注意雖然表面看起來複雜度都是 O(n),但是鏈表和順序表在插入和刪除時進行的是完全不同的操作。鏈表的主要耗時操作是遍歷查找,刪除和插入操作本身的複雜度是O(1)。順序表查找很快,主要耗時的操作是拷貝覆蓋。因爲除了目標元素在尾部的特殊情況,順序表進行插入和刪除時需要對操作點之後的所有元素進行前後移位操作,只能通過拷貝和覆蓋的方法進行。

 

單向循環鏈表

單鏈表的一個變形是單向循環鏈表,鏈表中最後一個節點的next域不再爲None,而是指向鏈表的頭節點。

class Node(object):
    """節點創建"""

    def __init__(self, item):
        self.ele = item
        self.next = None


class SingleCycleNodeList(object):
    """循環單鏈表創建 可以帶參,也可以不帶"""

    def __init__(self, node=None):
        # 不帶參數就是None
        self.__head = node
        # 參node初始化的時候需要循環指
        if node:
            node.next = self.__head

    def is_empty(self):
        """鏈表是否爲空"""
        return self.__head is None

    def length(self):
        """鏈表長度"""
        if self.is_empty():
            return 0
        cur = self.__head
        count = 1
        # 遍歷
        while cur.next != self.__head:
            cur = cur.next
            count += 1
        return count

    def travel(self):
        """遍歷整個鏈表"""
        if self.is_empty()  :
            return
        cur = self.__head
        while cur.next != self.__head:
            print(cur.ele, end=" ")
            cur = cur.next
        print(cur.ele, end=" ")
        print("")

    def add(self, item):
        """鏈表頭部添加元素"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            node.next = self.__head
        # 遍歷拿到尾節點
        near = self.__head
        while near.next != self.__head:
            near = near.next

        node.next = self.__head
        self.__head = node
        near.next = self.__head

    def append(self, item):
        """鏈表尾部添加元素"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            node.next = self.__head
            return
        cur = self.__head
        while cur.next != self.__head:
            cur = cur.next
        cur.next = node
        node.next = self.__head

    def insert(self, pos, item):
        """
        指定位置添加元素
        :param pos: 0開始
        :param item: 插入元素
        :return: None
        """
        # 頭插
        if pos <= 0:
            self.add(item)
        # 尾插
        elif pos >= self.length():
            self.append(item)
        # 中間插入
        else:
            pre = self.__head
            index = 0
            while index < pos - 1:
                pre = pre.next
                index += 1
            # 插入節點先鏈接,然後再改變遊標原有next
            node = Node(item)
            node.next = pre.next
            pre.next = node

    def remove(self, item):
        """刪除節點"""
        if self.is_empty():
            return
        pre = None
        cur = self.__head

        while cur.next != self.__head:
            # 匹配到
            if cur.ele == item:
                # 頭刪除 需要遍歷獲取尾節點來重新定位新頭
                if cur == self.__head:
                    near = self.__head
                    while near.next != self.__head:
                        near = near.next

                    self.__head = cur.next
                    near.next = self.__head
                else:
                    # 其他元素用pre指針和cur指針操作 中間刪除
                    pre.next = cur.next
                return
            else:
                # 往後移動
                pre = cur
                cur = cur.next
        # 最後一個元素刪除
        if cur.ele == item:
            if cur == self.__head:
                self.__head = None
            else:
                pre.next = cur.next

    def search(self, item):
        """查找節點是否存在"""
        if self.is_empty():
            return False
        cur = self.__head
        while cur.next != self.__head:
            if cur.ele == item:
                return True
            else:
                cur = cur.next
        if cur.ele == item:
            return True
        return False


if __name__ == "__main__":
    nodeList = SingleCycleNodeList(Node(999))

    nodeList.append(1)
    nodeList.add(100)
    nodeList.append(6)
    nodeList.add((0))
    nodeList.insert(7, 200)
    nodeList.insert(-1, 300)
    nodeList.insert(11, 400)

    print(nodeList.is_empty())
    print(nodeList.length())
    nodeList.travel()

雙向鏈表

一種更復雜的鏈表是“雙向鏈表”或“雙面鏈表”。每個節點有兩個鏈接:一個指向前一個節點,當此節點爲第一個節點時,指向空值;而另一個指向下一個節點,當此節點爲最後一個節點時,指向空值。

class Node(object):
    """節點創建"""

    def __init__(self, item):
        self.ele = item
        self.next = None
        self.prev = None


class SingleNodeList(object):
    """雙鏈表創建 可以帶參,也可以不帶"""

    def __init__(self, node=None):
        # 不帶參數就是None
        self.__head = node

    def is_empty(self):
        """鏈表是否爲空"""
        return self.__head is None

    def length(self):
        """鏈表長度"""
        cur = self.__head
        count = 0
        # 遍歷
        while cur is not None:
            cur = cur.next
            count += 1
        return count

    def travel(self):
        """遍歷整個鏈表"""
        cur = self.__head
        while cur is not None:
            print(cur.ele, end=" ")
            cur = cur.next
        print("")

    def add(self, item):
        """鏈表頭部添加元素"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            return
        node.next = self.__head
        self.__head.prev = node
        self.__head = node

    def append(self, item):
        """鏈表尾部添加元素"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
            return
        cur = self.__head
        while cur.next is not None:
            cur = cur.next
        cur.next = node
        node.prev = cur

    def insert(self, pos, item):
        """
        指定位置添加元素
        :param pos: 0開始
        :param item: 插入元素
        :return: None
        由於是雙向鏈表,因此這裏就不需要pre指針,只需要一個cur指針即可
        """
        # 頭插
        if pos <= 0:
            self.add(item)
        # 尾插
        elif pos >= self.length():
            self.append(item)
        # 中間插入
        else:
            cur = self.__head
            index = 0
            while index < pos:
                cur = cur.next
                index += 1
            # 插入節點先鏈接,然後再改變遊標原有next
            node = Node(item)
            node.next = cur
            node.prev = cur.prev
            cur.prev.next = node
            cur.prev = node

    def remove(self, item):
        """刪除節點"""
        if self.is_empty():
            return
        cur = self.__head
        while cur is not None:
            # 匹配到
            if cur.ele == item:
                # 第一個元素單獨處理
                if cur == self.__head:
                    self.__head = cur.next
                    self.__head.prev = None

                else:
                    cur.prev.next = cur.next
                    if cur.next:
                        # 刪除尾部
                        cur.next.prev = cur.prev
                return
            else:
                cur = cur.next

    def search(self, item):
        """查找節點是否存在"""
        if self.is_empty():
            print("鏈表爲空")
            return
        cur = self.__head
        while cur is not None:
            if cur.ele == item:
                print("prev-%s,cur-%s,next-%s"%(cur.prev.ele if cur.prev is not None else "空", cur.ele, cur.next.ele if cur.next is not None else "空"))
                return True
            else:
                cur = cur.next
        return False


if __name__ == "__main__":
    nodeList = SingleNodeList()

    nodeList.append(1)
    nodeList.add(100)
    nodeList.append(6)
    nodeList.add((0))
    nodeList.insert(7, 200)
    nodeList.insert(-1, 300)
    nodeList.insert(11, 400)

    print(nodeList.is_empty())
    print(nodeList.length())
    nodeList.travel()

    nodeList.remove(0)
    nodeList.travel()

    nodeList.remove(400)
    nodeList.travel()

    nodeList.remove(4000)
    nodeList.travel()

    nodeList.remove(300)
    nodeList.travel()

    nodeList.remove(3)
    nodeList.travel()

    print(nodeList.search(200))

 

 


以上是基本數據結構,負責數據如何存儲,下方的高級抽象也就是合理使用基本數據類型,進行一定算法組合出來的高級抽象,提高API給用戶使用 


 

棧和隊列

棧(stack),有些地方稱爲堆棧,是一種容器,可存入數據元素、訪問元素、刪除元素,它的特點在於只能允許在容器的一端(稱爲棧頂端指標,英語:top)進行加入數據(英語:push)和輸出數據(英語:pop)的運算。沒有了位置概念,保證任何時候可以訪問、刪除的元素都是此前最後存入的那個元素,確定了一種默認的訪問順序。

由於棧數據結構只允許在一端進行操作,因而按照後進先出(LIFO, Last In First Out)的原理運作。

可以用順序表實現,也可以用鏈表實現,例如一個push的操作,如果用list實現,就是O(1),但是用鏈表實現,就是O(n),因此內存具體用哪種操作,都是取決於是否可以最合理的時間複雜度

class Stack(object):
    """棧"""
    def __init__(self):
         self.items = []

    def is_empty(self):
        """判斷是否爲空"""
        return self.items == []

    def push(self, item):
        """加入元素"""
        self.items.append(item)

    def pop(self):
        """彈出元素"""
        return self.items.pop()

    def peek(self):
        """返回棧頂元素"""
        return self.items[len(self.items)-1]

    def size(self):
        """返回棧的大小"""
        return len(self.items)

if __name__ == "__main__":
    stack = Stack()
    stack.push("mi")
    stack.push("xiyue")
    stack.push("nice")
    print(stack.size())
    print(stack.peek())
    print(stack.pop())
    print(stack.pop())
    print(stack.pop())

隊列

隊列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。

隊列是一種先進先出的(First In First Out)的線性表,簡稱FIFO。允許插入的一端爲隊尾,允許刪除的一端爲隊頭。隊列不允許在中間部位進行操作!假設隊列是q=(a1,a2,……,an),那麼a1就是隊頭元素,而an是隊尾元素。這樣我們就可以刪除時,總是從a1開始,而插入時,總是在隊列最後。這也比較符合我們通常生活中的習慣,排在第一個的優先出列,最後來的當然排在隊伍最後。

class Queue(object):
    """隊列"""
    def __init__(self):
        self.items = []

    def is_empty(self):
        return self.items == []

    def enqueue(self, item):
        """進隊列"""
        self.items.insert(0,item)

    def dequeue(self):
        """出隊列"""
        return self.items.pop()

    def size(self):
        """返回大小"""
        return len(self.items)

if __name__ == "__main__":
    q = Queue()
    q.enqueue("mi")
    q.enqueue("xiyue")
    q.enqueue("Hi")
    print (q.size())
    print (q.dequeue())
    print (q.dequeue())
    print (q.dequeue())

雙端隊列 

雙端隊列(deque,全名double-ended queue),是一種具有隊列和棧的性質的數據結構。

雙端隊列中的元素可以從兩端彈出,其限定插入和刪除操作在表的兩端進行。雙端隊列可以在隊列任意一端入隊和出隊。、

class Deque(object):
    """雙端隊列"""
    def __init__(self):
        self.items = []

    def is_empty(self):
        """判斷隊列是否爲空"""
        return self.items == []

    def add_front(self, item):
        """在隊頭添加元素"""
        self.items.insert(0,item)

    def add_rear(self, item):
        """在隊尾添加元素"""
        self.items.append(item)

    def remove_front(self):
        """從隊頭刪除元素"""
        return self.items.pop(0)

    def remove_rear(self):
        """從隊尾刪除元素"""
        return self.items.pop()

    def size(self):
        """返回隊列大小"""
        return len(self.items)


if __name__ == "__main__":
    deque = Deque()
    deque.add_front(1)
    deque.add_front(2)
    deque.add_rear(3)
    deque.add_rear(4)
    print (deque.size()) # 4
    # 2 1 4 3
    print (deque.remove_front()) # 2
    print (deque.remove_front()) # 1
    print (deque.remove_rear()) # 4
    print (deque.remove_rear()) # 3

 

基礎排序算法

下面兩個單獨寫出來了,一篇文章太多太長了

冒泡排序 選擇排序 插入排序 快速排序 希爾排序 歸併排序 

#  冒泡
class BubbtleSort():
    def sort(self, list):
        for i in range(len(list) - 1, 0, -1):
            for j in range(0, i):
                if list[j] > list[j + 1]:
                    list[j], list[j + 1] = list[j + 1], list[j]


# 選擇
class selectSort():
    def sort(self, list):
        for i in range(0, len(list) - 1):
            min_index = i
            for j in range(i + 1, len(list)):
                if list[j] < list[min_index]:
                    min_index = j
            list[i], list[min_index] = list[min_index], list[i]


# 插入排序
class insertSort():
    def sort(self, list):
        # 1-6
        for i in range(1, len(list)):
            for j in range(i, 0, -1):
                if list[j] < list[j - 1]:
                    list[j], list[j - 1] = list[j - 1], list[j]

# 希爾排序
class xierSort():
    def sort(self, list):
        n = len(list)
        gap = n // 2

        while gap > 0:
            for i in range(gap, len(list)):
                for j in range(i, 0, -gap):
                    if list[j] < list[j - gap]:
                        list[j], list[j - gap] = list[j - gap], list[j]

            gap //= 2

# 快排
class quickSort():
    def sort(self, list, first, last):
        if first >= last:
            return
        mid = list[first]
        low = first
        high = last

        while low < high:
            while list[high] >= mid and low < high:
                high -= 1
            list[low] = list[high]
            while list[low] < mid and low < high:
                low += 1
            list[high] = list[low]
        list[low] = mid

        self.sort(list, first, low - 1)
        self.sort(list, low + 1, last)


# 歸併排序
class merginSort():
    def sort(self, list):
        n = len(list)
        if n <= 1:
            return list
        mid = n // 2

        left_li = self.sort(list[:mid])
        right_li = self.sort(list[mid:])

        result = []
        left = 0
        right = 0

        while left < len(left_li) and right < len(right_li):
            # print(right_li)
            # print(left_li)
            if left_li[left] <= right_li[right]:
                result.append(left_li[left])
                left += 1
            else:
                result.append(right_li[right])
                right += 1
        result.extend(left_li[left:])
        result.extend(right_li[right:])

        return result


bubble = merginSort()
a = [54, 26, 83, 17, 77, 17, 31]
print(a)
b = bubble.sort(a)
print(a)
print(b)

花了幾分鐘寫了下,你看看,python是不是刷題好幫手,這美感和代碼量,爽 

經典排序算法詳細介紹

 

二叉樹和二分法

二叉樹和二分法介紹

 

鏈表經典題目

題目列表: 以下是單鏈表基本結構,而且這些是核心思路,一些None,1個和2個的極端狀況沒考慮

class Node(object):
    """節點創建"""
    def __init__(self, item):
        self.ele = item
        self.next = None


def append(head,ele):
    cur = head
    if cur is None:
        cur = Node(ele)
        return
    while cur.next is not None:
        cur = cur.next
    cur.next = Node(ele)



# 打印
def travel(head):
    cur = head
    while cur is not None:
        print(cur.ele, end=" ")
        cur = cur.next

1. 求單鏈表中結點的個數

# 節點個數
def node_list_count(head):
    count = 0
    cur = head
    while cur is not None:
        cur = cur.next
        count += 1
    return count

2. 將單鏈表反轉

# 翻轉單鏈表
def reverse_list_node(head):

    pre = None
    while head is not None:
        cur = head
        head = cur.next
        cur.next = pre
        pre = cur
    return cur

3. 查找單鏈表中的倒數第K個結點(k > 0)

最普遍的方法是,先統計單鏈表中結點的個數,然後再找到第(n-k)個結點。注意鏈表爲空,k爲0,k爲1,k大於鏈表中節點個數時的情況。時間複雜度爲O(n)。代碼略。 這裏主要講一下另一個思路,這種思路在其他題目中也會有應用。 主要思路就是使用兩個指針,先讓前面的指針走到正向第k個結點,這樣前後兩個指針的距離差是k-1,之後前後兩個指針一起向前走,前面的指針走到最後一個結點時,後面指針所指結點就是倒數第k個結點。 

# 查找單鏈表中的倒數第K個結點(k > 0)
def last_index_node(head, k):

    # 這裏有個越界判斷
    print('倒數第%s個'%k)

    pre = head
    cur = head
    while k > 1 and pre is not None:
       pre = pre.next
       k -= 1

    while pre.next is not None:
        pre = pre.next
        cur = cur.next
    return cur.ele

4. 查找單鏈表的中間結點

此題可應用於上一題類似的思想。也是設置兩個指針,只不過這裏是,兩個指針同時向前走,前面的指針每次走兩步,後面的指針每次走一步,前面的指針走到最後一個結點時,後面的指針所指結點就是中間結點,即第(n/2+1)個結點。注意鏈表爲空,鏈表結點個數爲1和2的情況。時間複雜度O(n)。參考代碼如下: 

# 查找單鏈表的中間結點
def search_mid_node(head):

    # 需要自己再添加1 2 和空的情況

    # 核心代碼
    pre = head
    cur = head
    while pre.next is not None:
        pre = pre.next
        cur = cur.next
        # 這裏考慮偶數或者奇數
        if pre.next is not None:
            pre = pre.next
    return cur.ele

5. 從尾到頭打印單鏈表 (棧結構)

# 從尾到頭打印單鏈表
def reverse_travel(head):
    cur = head
    list = []
    while cur is not None:
        list.append(cur)
        cur = cur.next

    while list:
        node = list.pop()
        print(node.ele, end=" ")

6. 已知兩個單鏈表pHead1 和pHead2 各自有序,把它們合併成一個鏈表依然有序

這個類似歸併排序。尤其注意兩個鏈表都爲空,和其中一個爲空時的情況。只需要O(1)的空間。時間複雜度爲O(max(len1, len2))。參考代碼如下: 

# 合併兩個順序鏈表
def mergin_node_list(headerNode1, headerNode2):
    if headerNode1 is None:
        return headerNode2
    if headerNode2 is None:
        return headerNode1
    temp = None
    if headerNode1.ele < headerNode2.ele:
        temp = headerNode1
        temp.next = mergin_node_list(headerNode1.next,headerNode2)
    else:
        temp = headerNode2
        temp.next = mergin_node_list(headerNode1, headerNode2.next)
    return temp

7. 判斷一個單鏈表中是否有環

這裏也是用到兩個指針。如果一個鏈表中有環,也就是說用一個指針去遍歷,是永遠走不到頭的。因此,我們可以用兩個指針去遍歷,一個指針一次走兩步,一個指針一次走一步,如果有環,兩個指針肯定會在環中相遇。時間複雜度爲O(n)。參考代碼如下 

# 判斷鏈表是否有環
def hasCircle(head):

    cur = head
    pre = head

    while pre is not None and pre.next is not None:
        pre = pre.next.next
        cur = cur.next
        if pre == cur:
            return True
    return False

 

8. 判斷兩個單鏈表是否相交

如果兩個鏈表相交於某一節點,那麼在這個相交節點之後的所有節點都是兩個鏈表所共有的。也就是說,如果兩個鏈表相交,那麼最後一個節點肯定是共有的。先遍歷第一個鏈表,記住最後一個節點,然後遍歷第二個鏈表,到最後一個節點時和第一個鏈表的最後一個節點做比較,如果相同,則相交,否則不相交。時間複雜度爲O(len1+len2),因爲只需要一個額外指針保存最後一個節點地址,空間複雜度爲O(1)。參考代碼如下:

# 判斷兩個單鏈表是否相交
def isIntersected(head1,head2):

    p1 = head1
    p2 = head2

    while head1.next is not None:
        p1 = p1.next

    while head2.next is not None:
        p2 = p2.next

    return p1 == p2

9. 求兩個單鏈表相交的第一個節點

對第一個鏈表遍歷,計算長度len1,同時保存最後一個節點的地址。 對第二個鏈表遍歷,計算長度len2,同時檢查最後一個節點是否和第一個鏈表的最後一個節點相同,若不相同,不相交,結束。 兩個鏈表均從頭節點開始,假設len1大於len2,那麼將第一個鏈表先遍歷len1-len2個節點,此時兩個鏈表當前節點到第一個相交節點的距離就相等了,然後一起向後遍歷,知道兩個節點的地址相同。 時間複雜度,O(len1+len2)。參考代碼如下:

# 判斷兩個相交的鏈表的第一個節點
def getFirstCommondNode(head1,head2):

    p1 = head1
    len1 = 1
    p2 = head2
    len2 = 1

    while head1.next is not None:
        p1 = p1.next
        len1 += 1

    while head2.next is not None:
        p2 = p2.next
        len2 += 1

    if p1 != p2:
        return None

    node1 = head1
    node2 = head2
    if len1 > len2:
        k = len1 - len2

        while k > 0:
            node1 = node1.next
            k -= 1
    else:
        k = len2 - len1
        while k > 0:
            node2 = node2.next
            k -= 1

    while node1 != node2:
        node1 = node1.next
        node2 = node2.next
    return node1

 

10. 已知一個單鏈表中存在環,求進入環中的第一個節點

首先判斷是否存在環,若不存在結束。在環中的一個節點處斷開(當然函數結束時不能破壞原鏈表),這樣就形成了兩個相交的單鏈表,求進入環中的第一個節點也就轉換成了求兩個單鏈表相交的第一個節點。參考代碼如下:

# 已知一個單鏈表中存在環,求進入環中的第一個節點
def getFirstNodeInCircle(head):

    fast = head
    slow = head

    while fast.next is not None and fast is not None:
        fast = fast.next.next
        slow = slow.next
        if slow == fast:
            break

    fox = slow
    p1 = head
    p2 = fox.next


    node1 = p1
    len1 = 1
    node2 = p2
    len2 = 1

    while node1 != fox:
        node1 = node1.next
        len1 += 1

    while node2 != fox:
        node2 = node2.next
        len2 += 1

    if len1 < len2:
        k = len1 - len2
        while k > 0:
            len1 = len1.next
            k -= 1
    else:
        k = len2 - len1
        while k > 0:
            len2 = len2.next
            k -= 1

    while node1 != node2:
        node1 = node1.next
        node2 = node2.next

    return node1

 

二叉樹經典題目

https://blog.csdn.net/lsjseu/article/details/10907037

這東西理解起來費勁,弄幾個簡單的題目,剩下的可以看鏈接

class Binary_Tree_Node():
    def __init__(self,root=None, left=None,right=None):
        self.root = root
        self.left_tree = left
        self.right_tree = right

1. 求二叉樹中的節點個數
遞歸解法:
(1)如果二叉樹爲空,節點個數爲0
(2)如果二叉樹不爲空,二叉樹節點個數 = 左子樹節點個數 + 右子樹節點個數 + 1

# 節點個數
def getNodeNum(root):
    if root is None:
        return 0
    return getNodeNum(root.left_tree) + getNodeNum(root.right_tree) + 1

2. 求二叉樹的深度
遞歸解法:
(1)如果二叉樹爲空,二叉樹的深度爲0
(2)如果二叉樹不爲空,二叉樹的深度 = max(左子樹深度, 右子樹深度) + 1

# 計算二叉樹和
def getSum(pRoot):
    if pRoot is None:
        return 0
    return getSum(pRoot.left_tree) + getSum(pRoot.right_tree) + pRoot.root

# 深度
def getDepthNum(root):
    if root is None:
        return 0

    left_d = getDepthNum(root.left_tree)
    right_d = getDepthNum(root.right_tree)

    return (left_d + 1) if left_d > right_d else (right_d + 1)

3. 前序遍歷,中序遍歷,後序遍歷
前序遍歷遞歸解法:
(1)如果二叉樹爲空,空操作
(2)如果二叉樹不爲空,訪問根節點,前序遍歷左子樹,前序遍歷右子樹
參考代碼如下:

# 前序
def preTravel(pRoot):
    if pRoot is None:
        return
    print(pRoot.root)
    preTravel(pRoot.left_tree)
    preTravel(pRoot.right_tree)

# 中序
def midTravel(pRoot):
    if pRoot is None:
        return
    preTravel(pRoot.left_tree)
    print(pRoot.root)
    preTravel(pRoot.right_tree)

# 後續
def backTravel(pRoot):
    if pRoot is None:
        return
    preTravel(pRoot.left_tree)
    preTravel(pRoot.right_tree)
    print(pRoot.root)

4.分層遍歷二叉樹(按層次從上往下,從左往右)

相當於廣度優先搜索,使用隊列實現。隊列初始化,將根節點壓入隊列。當隊列不爲空,進行如下操作:彈出一個節點,訪問,若左子節點或右子節點不爲空,將其壓入隊列。

# 廣度遍歷
def widthTravel(pRoot):

    if pRoot is None:
        return
    queue = [pRoot]

    while queue:
        node = queue.pop(0)
        print(node.root)
        if node.left_tree is not None:
            queue.append(node.left_tree)
        if node.right_tree is not None:
            queue.append(node.right_tree)
    return

5. 求二叉樹第K層的節點個數
遞歸解法:
(1)如果二叉樹爲空或者k<1返回0
(2)如果二叉樹不爲空並且k==1,返回1
(3)如果二叉樹不爲空且k>1,返回左子樹中k-1層的節點個數與右子樹k-1層節點個數之和
參考代碼如下:


# 二叉樹第k層節點個數
def getNodeNumWithLevel(pRoot, k):
    if k is None and k < 1:
        return 0
    if k == 1:
        return 1
    left = getNodeNumWithLevel(pRoot.left_tree, k-1)
    right = getNodeNumWithLevel(pRoot.right_tree, k-1)
    return left + right

6. 求二叉樹中葉子節點的個數
遞歸解法:
(1)如果二叉樹爲空,返回0
(2)如果二叉樹不爲空且左右子樹爲空,返回1
(3)如果二叉樹不爲空,且左右子樹不同時爲空,返回左子樹中葉子節點個數加上右子樹中葉子節點個數
參考代碼如下:

# 二叉樹中葉子節點個數
def getLeafNodeNum(pRoot, k):
    if pRoot is None:
        return 0
    if pRoot.left_tree is None and pRoot.right_tree is None:
        return 1

    left = getLeafNodeNum(pRoot.left_tree)
    right = getLeafNodeNum(pRoot.right_tree)
    return left+right

7. 判斷兩棵二叉樹是否結構相同
不考慮數據內容。結構相同意味着對應的左子樹和對應的右子樹都結構相同。
遞歸解法:
(1)如果兩棵二叉樹都爲空,返回真
(2)如果兩棵二叉樹一棵爲空,另一棵不爲空,返回假
(3)如果兩棵二叉樹都不爲空,如果對應的左子樹和右子樹都同構返回真,其他返回假
參考代碼如下

# 判斷兩個二叉樹結構是否相同
def sameTreeStruct(pRoot1, pRoot2):
    if pRoot1 is None and pRoot2 is None:
        return True
    elif pRoot1 is None or pRoot2 is None:
        return False
    else:
        left = sameTreeStruct(pRoot1.left_tree, pRoot2.left_tree)
        right = sameTreeStruct(pRoot1.right_tree, pRoot2.right_tree)
        return left and right

8. 求二叉樹的鏡像
遞歸解法:
(1)如果二叉樹爲空,返回空
(2)如果二叉樹不爲空,求左子樹和右子樹的鏡像,然後交換左子樹和右子樹

# 求二叉樹的鏡像
def mirror(pRoot):
    if pRoot is None:
        return
    left = mirror(pRoot.left_tree)
    right = mirror(pRoot.right_tree)
    pRoot.left_tree = right
    pRoot.right_tree = left
    return pRoot

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章