Python - 二叉樹, 堆, headq 模塊


二叉樹

概念

二叉樹是n(n>=0)個結點的有限集合,該集合或者為空集(稱為空二叉樹),

或者由一個根結點和兩棵互不相交的、分別稱為根結點的左子樹和右子樹組成。

 

特點

每個結點最多有兩顆子樹,所以二叉樹中不存在度大於2的結點

左子樹和右子樹是有順序的,次序不能任意顛倒

即使樹中某結點只有一棵子樹,也要區分它是左子樹還是右子樹

性質

1)在二叉樹的第 i 層上最多有 2 i-1 個節點 。(i>=1)
2)二叉樹中如果深度為k,那么最多有 2 k-1 個節點。 (k>=1)
3)n 0=n 2+1 n 0表示度數為0的節點數,n 2表示度數為2的節點數。
4)在完全二叉樹中,具有n個節點的完全二叉樹的深度為[log 2n]+1,其中[log 2n]是向下取整。
5)若對含 n 個結點的完全二叉樹從上到下且從左至右進行 1 至 n 的編號,則對完全二叉樹中任意一個編號為 i 的結點有如下特性:
(1) 若 i=1,則該結點是二叉樹的根,無雙親, 否則,編號為 [i/2] 的結點為其雙親結點;
(2) 若 2i>n,則該結點無左孩子,  否則,編號為 2i 的結點為其左孩子結點;
(3) 若 2i+1>n,則該結點無右孩子結點,  否則,編號為2i+1 的結點為其右孩子結點。

 

 

類型 

斜樹

所有的結點都只有左子樹的二叉樹叫左斜樹。

所有結點都是只有右子樹的二叉樹叫右斜樹。

這兩者統稱為斜樹。

滿二叉樹

在一棵二叉樹中。

如果所有分支結點都存在左子樹和右子樹,

並且所有葉子都在同一層上,這樣的二叉樹稱為滿二叉樹。

滿二叉樹的特點有:

  1)葉子只能出現在最下一層。出現在其它層就不可能達成平衡。

  2)非葉子結點的度一定是2。

  3)在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子數最多。

完全二叉樹

對一顆具有n個結點的二叉樹按層編號

如果編號為 i(1<=i<=n) 的結點與同樣深度的滿二叉樹中編號為 i 的結點在二叉樹中位置完全相同

則這棵二叉樹稱為完全二叉樹。

特點

1)葉子結點只能出現在最下層和次下層。
2)最下層的葉子結點集中在樹的左部。
3)倒數第二層若存在葉子結點,一定在右部連續位置。
4)如果結點度為1,則該結點只有左孩子,即沒有右子樹。
5)同樣結點數目的二叉樹,完全二叉樹深度最小。
:滿二叉樹一定是完全二叉樹,但反過來不一定成立。

 


存儲結構

順序存儲

二叉樹的順序存儲結構就是使用一維數組存儲二叉樹中的結點,

並且結點的存儲位置,就是數組的下標索引。

 

二叉鏈表

既然順序存儲不能滿足二叉樹的存儲需求,那么考慮采用鏈式存儲。

由二叉樹定義可知,二叉樹的每個結點最多有兩個孩子。

因此,可以將結點數據結構定義為一個數據和兩個指針域。

二叉樹實現

定義一個左子樹, 定義一個柚子樹, 以及值

 class BinTree:  
     def __init__(self,value=None,left=None,right=None):  
         self.value=value  
         self.left=left    #左子樹
         self.right=right  #右子樹

二叉樹遍歷

先序遍歷

先處理根, 之后是左子樹, 然后是右子樹

中序遍歷

先處理左子樹, 之后是根, 然后是右子樹

后序遍歷

先處理左子樹, 之后是右子樹, 最后是根

代碼示例

def preTraverse(root):
    '''
    先序遍歷
    '''
    if root is not None:
        print(root.value)
        preTraverse(root.left)
        preTraverse(root.right)


def midTraverse(root):
    '''
    中序遍歷
    '''
    if root is not None:
        midTraverse(root.left)
        print(root.value)
        midTraverse(root.right)


def afterTraverse(root):
    '''
    后序遍歷
    '''
    if root is not None:
        afterTraverse(root.left)
        afterTraverse(root.right)
        print(root.value)

最大堆 / 最小堆

 

 最大 / 小堆是一棵完全二叉樹,非葉子結點的值不大 / 小於左孩子和右孩子的值

構建原理

初始數組為:9,3,7,6,5,1,10,2

按照完全二叉樹,將數字依次填入。

填入后,找到最后一個結點(本示例為數字2的節點),從它的父節點(本示例為數字6的節點)

開始調整。根據性質,小的數字往上移動;至此,第1次調整完成。

注意,被調整的節點,還有子節點的情況,需要遞歸進行調整。

第二次調整,是數字6的節點數組下標小1的節點(比數字6的下標小1的節點是數字7的節點),

用剛才的規則進行調整。以此類推,直到調整到根節點。

 

 

 

 

元素插入

以上個最小堆為例,插入數字0。

數字0的節點首先加入到該二叉樹最后的一個節點,依據最小堆的定義,自底向上,遞歸調整。

元素刪除

代碼實現

最小堆

class MinHeap(object):
    """最小堆"""

    def __init__(self):
        self.data = []  # 創建堆
        self.count = len(self.data)  # 元素數量

    # def __init__(self, arr):
    #   self.data = copy.copy(arr)
    #   self.count = len(self.data)
    #   i = self.count / 2
    #   while i >= 1:
    #       self.shiftDown(i)
    #       i -= 1

    def size(self):
        return self.count

    def isEmpty(self):
        return self.count == 0

    def insert(self, item):
        # 插入元素入堆
        self.data.append(item)
        self.count += 1
        self.shiftup(self.count)

    def shiftup(self, count):
        # 將插入的元素放到合適位置,保持最小堆
        while count > 1 and self.data[(count / 2) - 1] > self.data[count - 1]:
            self.data[(count / 2) - 1], self.data[count - 1] = self.data[count - 1], self.data[(count / 2) - 1]
            count /= 2

    def extractMin(self):
        # 出堆
        if self.count > 0:
            ret = self.data[0]
            self.data[0], self.data[self.count - 1] = self.data[self.count - 1], self.data[0]
            self.data.pop()
            self.count -= 1
            self.shiftDown(1)
            return ret

    def shiftDown(self, count):
        # 將堆的索引位置元素向下移動到合適位置,保持最小堆
        while 2 * count <= self.count:
            # 證明有孩子
            j = 2 * count
            if j + 1 <= self.count:
                # 證明有右孩子
                if self.data[j] < self.data[j - 1]:
                    j += 1
            if self.data[count - 1] <= self.data[j - 1]:
                # 堆的索引位置已經小於兩個孩子節點,不需要交換了
                break
            self.data[count - 1], self.data[j - 1] = self.data[j - 1], self.data[count - 1]
            count = j

最大堆

"""
最大堆
"""


class MaxHeap(object):
    # def __init__(self):
    #   self.data = []  # 創建堆
    #   self.count = len(self.data)  # 元素數量

    def __init__(self, arr):
        self.data = copy.copy(arr)
        self.count = len(self.data)
        i = self.count / 2
        while i >= 1:
            self.shiftDown(i)
            i -= 1

    def size(self):
        return self.count

    def isEmpty(self):
        return self.count == 0

    def insert(self, item):
        # 插入元素入堆
        self.data.append(item)
        self.count += 1
        self.shiftup(self.count)

    def shiftup(self, count):
        # 將插入的元素放到合適位置,保持最大堆
        while count > 1 and self.data[(count / 2) - 1] < self.data[count - 1]:
            self.data[(count / 2) - 1], self.data[count - 1] = self.data[count - 1], self.data[(count / 2) - 1]
            count /= 2

    def extractMax(self):
        # 出堆
        if self.count > 0:
            ret = self.data[0]
            self.data[0], self.data[self.count - 1] = self.data[self.count - 1], self.data[0]
            self.data.pop()
            self.count -= 1
            self.shiftDown(1)
            return ret

    def shiftDown(self, count):
        # 將堆的索引位置元素向下移動到合適位置,保持最大堆
        while 2 * count <= self.count:
            # 證明有孩子
            j = 2 * count
            if j + 1 <= self.count:
                # 證明有右孩子
                if self.data[j] > self.data[j - 1]:
                    j += 1
            if self.data[count - 1] >= self.data[j - 1]:
                # 堆的索引位置已經大於兩個孩子節點,不需要交換了
                break
            self.data[count - 1], self.data[j - 1] = self.data[j - 1], self.data[count - 1]
            count = j

heapq 模塊

官方文檔    這里

Python 自帶的用於堆結構方便的模塊

創建堆 - 最小堆

單個添加創建堆 - heappush

import heapq

data = [1,5,3,2,8,5]
heap = []

for n in data:
    heapq.heappush(heap, n)

對已存在的序列轉化為堆 - heapify

import heapq

data = [1,5,3,2,8,5]
heapq.heapify(data)

對多個序列轉化為堆 - merge

import heapq

num1 = [32, 3, 5, 34, 54, 23, 132]
num2 = [23, 2, 12, 656, 324, 23, 54]
num1 = sorted(num1)
num2 = sorted(num2)

res = heapq.merge(num1, num2)
print(list(res))  # [2, 3, 5, 12, 23, 23, 23, 32, 34, 54, 54, 132, 324, 656]

創建堆 - 最大堆

用法同最小堆, 創建的時候將所有的值取相反數

然后在取出堆頂, 在進行取反, 即可獲得原值

import heapq

data = [1, 5, 3, 2, 8, 5]
li = []
for i in data:
    heapq.heappush(li, -i)

print(li)  # [-8, -5, -5, -1, -2, -3]
print(-li[0])  # 8

訪問堆內容

查看最小值 - [0]

import heapq

data = [1, 5, 3, 2, 8, 5]
heapq.heapify(data)

print(data[0]) # 1

彈出最小值 - heappop

會改變原數據, 類似於列表的 pop

import heapq

data = [1, 5, 3, 2, 8, 5]
heapq.heapify(data)

print(heapq.heappop(data))  # 1
print(data)  # [2, 5, 3, 5, 8]

向堆內推送值 - heappush

import heapq

data = [1, 5, 3, 2, 8, 5]
heapq.heapify(data)

heapq.heappush(data, 10)
print(data)  # [1, 2, 3, 5, 8, 5, 10]

彈出最小值並加入一個值 - heappushpop

 彈出最小值, 添加新值, 且自動排序保持是最小堆

import heapq

data = [1, 5, 3, 2, 8, 5]
heapq.heapify(data)

print(heapq.heappushpop(data, 1))  # 1
print(data)  # [1, 2, 3, 5, 8, 5]

彈出最小值並加入一個值 - heapreplace

彈出最小值, 添加新值, 且自動排序保持是最小堆

是 heappushpop 的高效版本, 在py3 中適用

import heapq

data = [1, 5, 3, 2, 8, 5]
heapq.heapify(data)

print(heapq.heapreplace(data, 10))  # 1
print(data)  # [2, 5, 3, 10, 8, 5]

k 值問題 - nlargest / nsmallest 

找出堆中最小 / 大的 k 個值

import heapq

data = [1, 5, 3, 2, 8, 5]

heapq.heapify(data)
print(data)  # [1, 2, 3, 5, 8, 5]
print(heapq.nlargest(2, data))  # [8, 5]
print(heapq.nsmallest(2, data))  # [1, 2]

 可以接收一個參數 key , 用於指定選項進行選取

用法類似於 sorted 的 key

import heapq
from pprint import pprint
portfolio = [
    {'name': 'IBM', 'shares': 100, 'price': 91.1},
    {'name': 'AAPL', 'shares': 50, 'price': 543.22},
    {'name': 'FB', 'shares': 200, 'price': 21.09},
    {'name': 'HPQ', 'shares': 35, 'price': 31.75},
    {'name': 'YHOO', 'shares': 45, 'price': 16.35},
    {'name': 'ACME', 'shares': 75, 'price': 115.65}
]
cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
pprint(cheap)
pprint(expensive)

"""
輸出:
[{'name': 'YHOO', 'price': 16.35, 'shares': 45},
 {'name': 'FB', 'price': 21.09, 'shares': 200},
 {'name': 'HPQ', 'price': 31.75, 'shares': 35}]
[{'name': 'AAPL', 'price': 543.22, 'shares': 50},
 {'name': 'ACME', 'price': 115.65, 'shares': 75},
 {'name': 'IBM', 'price': 91.1, 'shares': 100}]
"""

 


免責聲明!

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



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