Pythonはハフマンツリーを実装します
ハフマンツリーは、特別な二分木であり、重み付きパスの長さが最短の二分木であり、最適な二分木とも呼ばれます。
二分木のN個のリーフノードの重みとしてN個の重みを与えると、二分木が構築されます。二分木の重み付きパス長が最小に達すると、二分木はハフマンツリーと呼ばれます。
ハフマンツリーで重みが大きいノードは、ルートに近くなります。
ハフマンツリーは、主に情報コーディングとデータ圧縮の分野で使用され、最新の圧縮アルゴリズムの基礎となっています。
1.ホフマンツリーの関連用語
ホフマンツリーが重み付きパスの最小の長さを満たすために、重みは何ですか?パスは何ですか?加重パスの長さはどれくらいですか?
1.パス
ツリーでは、ノードから子ノードまたは子孫ノードまでのパスはパスと呼ばれます。
2.ノードの重み
特定のアプリケーションシナリオでは、バイナリツリーの各ノードは特定のビジネス上の意味に対応し、各ノードの重みは異なります。ノードの重み値は、ノードの重み値と呼ばれます。
次の図に示すように、ノードCの重みは5です。
3.ノードのパス長
ルートノードは二分木の最初のレベルにあり、二分木のL番目のレベルのノードのパス長はL-1です。
上の図に示すように、ノードFは二分木の第3レベルにあり、Fのパス長は3-1 = 2です。
ノードのパス長もこのように理解できます。子ノードへの各ノードのパスがパスユニットとして記録されている場合、ルートノードから開始して、ノードへのパスユニットの数はパス長と呼ばれます。ノード。
4.ノードの加重パス長
ノードの重みとノードのパス長の積は、ノードの重み付きパス長と呼ばれます。
上の図では、ノードDの重みは18、パス長は2であるため、ノードDの重み付きパス長は18 * 2 = 36です。
5.ツリーの加重パス長
ツリーのすべてのリーフノードの加重パス長の合計は、ツリーの加重パス長と呼ばれ、WPL(ツリーの加重パス長)として表されます。
上図に示すように、二分木の加重パス長はWPL = 18 * 2 + 7 * 2 + 6 * 2 + 17 * 2 = 96です。
第二に、ホフマンの木の構築プロセス
二分木のN個の葉ノードの重みとしてN個の重みが与えられると、二分木を構築して、異なる構造を持つさまざまな二分木を構築できます。異なる構造を持つ二分木の重み付きパス長は必ずしも同じではありません。二分木の加重パス長が最小の場合のみ、二分木はハフマンツリーになります。
たとえば、リーフノードの重みとして3、5、7、13の4つの重みが与えられた場合、4つのリーフノードを持つバイナリツリーは多くの異なる構造を持つことができます。以下は、そのうちの2つの例です。二分木加重パス長は3 * 3 + 5 * 2 + 7 * 2 + 13 * 2 = 59であり、右側の二分木の加重パス長は13 * 1 + 7 * 2 + 3 * 3です。 + 5 * 3 = 51。ハフマンツリーの特性上、重みの大きいノードはルートに近い、つまり重みの大きいノードのパスは短くなります。下図右の二分木はハフマンです。ツリー、およびツリーの重みは次のとおりです。パスの長さが最小に達しました。
では、与えられたリーフノードの重みに基づいてハフマンツリーを構築する方法は?ハフマンツリーを構築する前に、まずハフマンツリーのいくつかの一般的なプロパティを導出します。
1.構築された二分木がハフマンツリーであることを保証するために、重みの大きいノードのパスをできるだけ短くする必要があります。逆に、重みの小さいノードは、より高いレベルにしか存在できません。二分木であり、短いパスは大きな重みを持つノードを与えるです。ローカルの観点からは、各ノードのパスが、重みがそれよりも小さいノードより大きくないことが保証されている限り、欲張りアルゴリズムを使用できます。
2.ハフマンツリーに子ノードが1つしかないノードはありません。ハフマンツリーに子ノードが1つしかないノードがあるとすると、そのノードを削除すると、そのサブツリー内のすべてのリーフノードのパス長を1つ減らすことができ、重み付きパス長が小さい二分木は次のようになります。構築された、これはホフマンツリーの定義と同じであるため、仮説は成り立たない。
3.ハフマンツリーにリーフノードが2つしかない場合、2つのリーフノードのパスは等しく、両方とも1です。
これらのプロパティに従って、ハフマンツリーの構築を開始します。手順は次のとおりです。
1. N個のリーフノードを持つツリーをN個のツリーのフォレストと見なします(各ツリーには1つのルートノードしかありません)。
例として3、5、7、13を取り上げます。
2.フォレストからルートノードの重みが最小の2つのツリーを選択し、それらを新しいツリーの左右のサブツリーとして使用し(新しいツリーがハフマンツリーを満たすように構築されるように)、ルートノードの重みは新しいツリーは、その左右のサブツリーです。ルートノードの重みの合計。次に、マージされた2つのツリーがフォレストから削除され、新しいツリーがフォレストに追加されます。
それらから最小の3と5を選択し、それらをホフマンツリーにマージしてから、新しいツリーをフォレストに追加します。
3.フォレストにツリーが1つだけ残り、最後のツリーがホフマンツリーになるまで、手順2を繰り返します。
ハフマンツリーの構造が一意であることを保証するために、このペーパーでは、各マージ中に、ルートノードの重みが小さいツリーを左側のサブツリーとして使用し、ルートノードの重みが大きいツリーを右側のサブツリーとして使用します。ツリーの加重パス長が最小に達する限り、構造が何であれ、それはホフマンツリーであり、ホフマンツリーは一意ではないため、これは自分で決定できます。
引き続き最小の7と8を選択し、それらをマージします。
最終的なホフマンツリー構造は次のとおりです。
次に、ツリーの重み付きパスの長さがWPL = 13 * 1 + 7 * 2 + 3 * 3 + 5 * 3 = 51であることを確認します。重みが大きいほど、ノードのパスが短くなるため、これはハフマンツリーです。 。
3つ目は、Pythonがハフマンツリーを実装することです。
1.コードの準備
# coding=utf-8
class Node(object):
def __init__(self, data):
self.data = data
self.parent = None
self.left_child = None
self.right_child = None
self.is_in_tree = False
class HuffmanTree(object):
"""霍夫曼树"""
def __init__(self):
self.__root = None
self.prefix_branch = '├'
self.prefix_trunk = '|'
self.prefix_leaf = '└'
self.prefix_empty = ''
self.prefix_left = '─L─'
self.prefix_right = '─R─'
def is_empty(self):
return not self.__root
@property
def root(self):
return self.__root
@root.setter
def root(self, value):
self.__root = value if isinstance(value, Node) else Node(value)
def show_tree(self):
if self.is_empty():
print('空二叉树')
return
print('-' * 20)
print(self.__root.data)
self.__print_tree(self.__root)
print('-' * 20)
def __print_tree(self, node, prefix=None):
if prefix is None:
prefix, prefix_left_child = '', ''
else:
prefix = prefix.replace(self.prefix_branch, self.prefix_trunk)
prefix = prefix.replace(self.prefix_leaf, self.prefix_empty)
prefix_left_child = prefix.replace(self.prefix_leaf, self.prefix_empty)
if self.has_child(node):
if node.right_child is not None:
print(prefix + self.prefix_branch + self.prefix_right + str(node.right_child.data))
if self.has_child(node.right_child):
self.__print_tree(node.right_child, prefix + self.prefix_branch + ' ')
else:
print(prefix + self.prefix_branch + self.prefix_right)
if node.left_child is not None:
print(prefix + self.prefix_leaf + self.prefix_left + str(node.left_child.data))
if self.has_child(node.left_child):
prefix_left_child += ' '
self.__print_tree(node.left_child, self.prefix_leaf + prefix_left_child)
else:
print(prefix + self.prefix_leaf + self.prefix_left)
def has_child(self, node):
return node.left_child is not None or node.right_child is not None
まず、ハフマンツリーのノードを作成するために使用するノードクラスNodeを作成します。ハフマンツリーを構築するときは、ルートノードが最小の2つのツリーを連続して選択する必要があるため、ここで注意する必要があります。マージするフォレストなので、ノードにフラグビットis_in_treeを追加します。これがTrueの場合、ツリーがハフマンツリーにマージされており、繰り返しフェッチされないことを意味します。
ハフマンツリークラスHuffmanTreeを事前に実装するには、最初にメソッドshow_tree()を準備して、ハフマンツリーをツリー構造で出力します。
ホフマンツリーの構築プロセスに従って、ホフマンツリーの構築方法が実現されます。
def huffman(self, leavers):
"""构造霍夫曼树"""
if len(leavers) <= 0:
return
if len(leavers) == 1:
self.root = Node(leavers[0])
return
woods = list()
for i in range(len(leavers)):
woods.append(Node(leavers[i]))
while len(woods) < 2*len(leavers) - 1:
node1, node2 = Node(float('inf')), Node(float('inf'))
for j in range(len(woods)):
if node1.data > node2.data:
node1, node2 = node2, node1
if woods[j].data < node1.data and woods[j].is_in_tree is False:
node1, node2 = woods[j], node1
elif node1.data <= woods[j].data < node2.data and woods[j].is_in_tree is False:
node2 = woods[j]
parent_node = Node(node1.data + node2.data)
woods.append(parent_node)
parent_node.left_child, parent_node.right_child = node1, node2
self.root, node1.parent, node2.parent = parent_node, parent_node, parent_node
node1.is_in_tree, node2.is_in_tree = True, True
huffman(leavers):ハフマンツリーを構築します。ハフマンツリーを構築するとき、N個の重みが与えられ、N <= 0の場合は直接戻ります。N = 1の場合、この重みをルートノードの重みとして直接使用して、ハフマンツリーを構築します。N> = 2の場合、最初にこれらのN個の重みをルートノードの重みとして使用して、N個のツリーを含むフォレストを構築し、次に、マージするフォレストからルートノードの重みが最小の2つのツリーを選択し、木。
この方法では、次の点に注意してください。そうしないと、間違いを犯しやすくなります。
1.コードでの処理の便宜のために、マージされたツリーはリストウッドから削除されません(削除操作は、特に重みが等しい場合は非常に面倒です)が、ルートノードのフラグビットis_in_treeを変更することにより、 is_in_treeがTrueの場合、ツリーがマージされ、繰り返しフェッチされないことを意味します。
では、ハフマンツリーが構築されたと判断するにはどうすればよいでしょうか?森の中の1つのツリーのみのルートノードのフラグis_in_treeがFalseの場合、これは簡単に判断できません。マージするたびに、森を判断する必要があります。ルートノードフラグビット。上で分析したプロパティによると、欲張りアルゴリズムを使用して、ツリーがマージされるたびに、新しいツリーはローカルのハフマンツリーになり、ハフマンツリーに子ノードが1つしかないノードはありません。ハフマンツリーを構築する過程で、各ノードはツリーのルートノードとして森の森に追加されるため、森の長さが達すると、森の長さはホフマンツリーのノードの数と等しくなります。ホフマンツリー内のノードの総数が増えると、ハフマンツリーが構築されます。
ハフマンツリーでは、リーフノードを除いて、他のノードには2つの子ノードがあります。バイナリツリーの特性により、リーフノードの数がNの場合、2つの子ノードを持つノードの数はN + 1であるため、 N個のリーフノードを持つHuoFumanツリーのノード数は2 * N +1です。
2.重みが最小の2つのルートノードを取得するために、2つの変数node1とnode2を事前に宣言します。これらの2つの変数は、重みが大きい2つのノードに事前に割り当てることができます。正の無限大float(を直接使用することをお勧めします。 'inf')、森の最初と2番目の値が最初に割り当てられている場合、最初または2番目の値が最小値である場合、ループを介して毎回同じ値がフェッチされる可能性があります。
3.重みが最小の2つのルートノード値を探すときに、現在のノードノードの重みがnode1、node.data <node1.dataの重みよりも小さい場合、node1はnode2に割り当てられ、node現在のノードノードの重みがノード1の重み以上でノード2の重みよりも小さい場合、ノード1に割り当てられます。node1.data<= node.data <node2.dataの場合、ノードはノード2に割り当てられます。 。ここで2番目のケースを見落としがちです。
if __name__ == '__main__':
tree = HuffmanTree()
leavers = [11, 5, 7, 13, 17, 11]
tree.huffman(leavers)
tree.show_tree()
演算結果:
--------------------
64
├─R─39
| ├─R─22
| | ├─R─11
| | └─L─11
| └─L─17
└─L─25
├─R─13
└─L─12
├─R─7
└─L─5
--------------------
N個のリーフノードの重みが[11、5、7、13、17、11]であるとすると、ハフマンツリー構造は次の図のようになります。
ハフマンツリーの加重パス長は、WPL = 13 * 2 + 17 * 2 + 5 * 3 + 7 * 3 + 11 * 3 + 11 * 3 = 162です。