第16章「欲張りアルゴリズム」:ハフマンツリー、Python実装

お元気ですか

最適化問題を解決するためのアルゴリズムは、通常、一連のステップを経る必要があり、各ステップは複数の選択肢に直面します。多くの最適化問題では、動的計画法アルゴリズムを使用して最適な解を見つけるのは少し注意が必要であり、より単純で効率的なアルゴリズムを使用できます。欲張りアルゴリズムはそのようなアルゴリズムであり、各ステップで最も見栄えの良い選択を行います。つまり、常に局所的に最適な選択を行います。

貪欲な選択の性質

ローカル最適(貪欲)選択を行うことでグローバル最適解を構築できます。選択を行うときは、サブ問題の解を考慮することなく、現在の問題で直接最適選択を行います。貪欲アルゴリズムは通常、トップダウンで、特定の問題インスタンスを小さくするために何度も選択を行います。

ハフマン符号化

ハフマン符号化は、データを非常に効果的に圧縮できます。100,000文字のデータファイルを圧縮するとします。次の図は、ファイル内の文字とその頻度を示しています。つまり、ファイル内には6つの異なる文字しかなく、その中に文字aaがあります。45,000回登場しました。
ここに画像の説明を挿入します
固定長エンコーディングを使用して3ビットコードワードを指定すると、ファイルを300,000ビットの長さにエンコードできますが、上の表に示す可変長エンコーディングを使用すると、ファイルのエンコードに使用されるのは224,000ビットのみです。(45 ∗ 1 + 13 ∗ 3 + 12 ∗ 3 + 16 ∗ 3 + 9 ∗ 4 + 5 ∗ 4)∗ 1000 = 224000(45 * 1 + 13 * 3 + 12 * 3 + 16 * 3 + 9 * 4 + 5 * 4)* 1000 = 2240004 51+1 33+1 23+1 63+94+54 1 0 0 0=2 2 4 0 0 0に対応する二分木は次のように表され、各リーフノードは文字とその出現頻度をマークします。各内部ノードは、そのサブツリー内のリーフノードの頻度の合計でマークされます。(a)固定長コーディングに対応a = 000、...、f = 101 a = 000、...、f = 101a=0 0 0 f=1 0 1、(b)の二分木は、最適なプレフィックスコードa = 0、b = 101、...、F = 1100 a = 0、b = 101、...、f = 1100に対応します。a=0 b=1 0 1 f=1 1 0 0バイナリツリー。

ここに画像の説明を挿入します
qian'zhuiプレフィックスコードに対応するツリーTTが与えられますT、アルファベットCCの場合、ファイルをエンコードするために必要なバイナリビット数を簡単に計算できます。内のすべての文字CのCCc、属性c。freqc.freqを許可しますC FのR E Qの手段CCcの頻度がファイルに表示されます。dT(c)d_T(c)とします。dTc ccを意味しますツリー内のcの葉ノードの深さ。次に、エンコードされたファイルにはB(T)= ∑c∈Ccが必要です。周波数DT(c)B(T)= \ sum_(c \ in C)c.freq.d_T(c)B T =C CΣc FのR EのQ dTc バイナリビット、B(T)B(T)B T )はTTとして定義されますTの価格。

ハフマンコードの構築

ハフマンは、ハフマンコードと呼ばれる最適なプレフィックスコードを構築するための欲張りアルゴリズムを設計しました。CCを想定Cnnですn文字のセット、および各文字c∈Cc\ in CcCは、属性cを持つオブジェクトです。freqc.freqC FのR EのQは、文字の発生頻度を与えます。アルゴリズムは、対応する最適なコーディング二分木TTをボトムアップで構築します。T∣ C ∣ | C |から始まります| C |リーフ・ノードで開始し、実行| C | - 1 | C | -1C 1回の「マージ」操作により、最終的なバイナリツリーが作成されます。次の図に示すように、ハフマンアルゴリズムの実行プロセス:
ここに画像の説明を挿入します
Pythonは次のようにハフマンコーディングを実装します。

# -*-coding:utf8 -*-
import sys

#构造节点
class Node(object):
	def __init__(self, key, code=0, parent=None,lchild=None, rchild=None):
		self.key = key
		self.code = code
		self.parent = parent
		self.lchild = lchild
		self.rchild = rchild


class HuffmanTree(object):
	def __init__(self,root=None):
		self.huffman_tree = []
		self.root = root
	#找两个key值最小的节点
	def find2node(self, flag):
		mini = []
		for i in range(len(self.huffman_tree)):
			if i in flag:
				continue
			if len(mini) < 2:
				mini.append(i)
			else:
				if self.huffman_tree[i].key < max([self.huffman_tree[mini[0]].key, self.huffman_tree[mini[1]].key]):
					pos = 0 if self.huffman_tree[mini[0]].key > self.huffman_tree[mini[1]].key else 1
					mini[pos] = i
		# let lchird.key < rchild.key
		if self.huffman_tree[mini[0]].key > self.huffman_tree[mini[1]].key:
			temp = mini[0]
			mini[0] = mini[1]
			mini[1] = temp
		#return sorted(mini)
		return mini
 

	def build(self, C):
		#flag记录已经合并的节点
		flag = []		
		#初始化各关键字key节点
		n = len(C)
		for key in C:
			self.huffman_tree.append(Node(key))
		#构建hufuman,n个值,需要n-1次合并操作
		for i in range(n,2*n-1):
			mini = self.find2node(flag)
			print(str(mini[0])+'\t'+str(mini[1]))		
			#合并的节点记录,下次合并不需要考虑		
			flag.append(mini[0])
			flag.append(mini[1])
			#构建新的Node节点,key为两个子节点key相加
			key = self.huffman_tree[mini[0]].key + self.huffman_tree[mini[1]].key
			node = Node(key,lchild=self.huffman_tree[mini[0]],rchild=self.huffman_tree[mini[1]])
			#新节点加入
			self.huffman_tree.append(node)
			self.huffman_tree[mini[0]].parent = node
			self.huffman_tree[mini[1]].parent = node
			self.huffman_tree[mini[1]].code = 1	

		#记录root节点
		print(node.key)
		self.root = node  
		
	#中序遍历
	def inorder_tree_walk(self, tree):
		if tree is not None:
			self.inorder_tree_walk(tree.lchild)
			print(tree.key, end=" ")
			self.inorder_tree_walk(tree.rchild)

	#获取节点的编码
	def huffman_code(self, cur):
		path = []
		while cur.parent!=None:
			#path结果保存从根节点搜索路径的编码结果
			path.insert(0,cur.code)
			cur = cur.parent
		return path	
				 
if __name__=='__main__':
	tree =  HuffmanTree()
	C = [5,9,12,13,16,45]
	tree.build(C)
	tree.inorder_tree_walk(tree.root)
	print()
	#打印每个叶子节点的赫夫曼编码
	for i in range(len(C)):
		path = tree.huffman_code(tree.huffman_tree[i])
		print('key='+str(tree.huffman_tree[i].key))
		print(path)	

特定のコードについては、各章のアルゴリズムpython実装のgithubアドレスアルゴリズムの概要を参照してください。

おすすめ

転載: blog.csdn.net/BGoodHabit/article/details/106068573