Python implémente l'arbre de Huffman
L'arbre de Huffman est un arbre binaire spécial, un arbre binaire avec la longueur de chemin pondérée la plus courte, également connu sous le nom d'arbre binaire optimal.
Étant donné N poids comme poids des N nœuds feuilles de l'arbre binaire, un arbre binaire est construit. Si la longueur de chemin pondérée de l'arbre binaire atteint le minimum, l'arbre binaire est appelé un arbre de Huffman.
Les nœuds avec des poids plus importants dans l'arbre de Huffman sont plus proches de la racine.
Les arbres de Huffman sont principalement utilisés dans les domaines du codage de l'information et de la compression de données, et sont à la base des algorithmes de compression modernes.
1. Termes associés de l'arbre de Hoffman
Pour que l'arbre de Hoffman atteigne la longueur minimale du chemin pondéré, quel est le poids? Quel est le chemin? Quelle est la longueur pondérée du chemin?
1. Chemin
Dans une arborescence, le chemin d'un nœud vers un nœud enfant ou un nœud descendant est appelé un chemin.
2. Le poids du nœud
Dans un scénario d'application spécifique, chaque nœud de l'arborescence binaire correspond à une signification métier spécifique et chaque nœud a un poids différent. La valeur pondérée du nœud est appelée la valeur pondérée du nœud.
Comme le montre la figure ci-dessous, le poids du nœud C est de 5.
3. La longueur du chemin du nœud
Le nœud racine se trouve au premier niveau de l'arbre binaire et la longueur du chemin du nœud au niveau L dans l'arbre binaire est L-1.
Comme le montre la figure ci-dessus, le nœud F est au troisième niveau de l'arbre binaire et la longueur du chemin de F est 3-1 = 2.
La longueur du chemin d'un nœud peut également être comprise de cette manière. Si le chemin de chaque nœud vers son nœud enfant est enregistré en tant qu'unité de chemin, à partir du nœud racine, le nombre d'unités de chemin vers un nœud est appelé la longueur de chemin de le nœud.
4. La longueur de chemin pondérée du nœud
Le produit du poids du nœud et de la longueur de chemin du nœud est appelé longueur de chemin pondérée du nœud.
Dans la figure ci-dessus, le poids du nœud D est de 18 et la longueur de chemin est de 2, puis la longueur de chemin pondérée du nœud D est de 18 * 2 = 36.
5. Longueur de chemin pondérée de l'arbre
La somme des longueurs de chemin pondérées de tous les nœuds feuilles de l'arbre est appelée la longueur de chemin pondérée de l'arbre et est désignée par WPL (Weighted Path Length of Tree).
Comme le montre la figure ci-dessus, la longueur de chemin pondérée de l'arbre binaire est WPL = 18 * 2 + 7 * 2 + 6 * 2 + 17 * 2 = 96
Deuxièmement, le processus de construction de l'arbre Hoffman
Étant donné N poids comme poids des N nœuds feuilles de l'arbre binaire, un arbre binaire peut être construit pour construire une variété d'arbres binaires avec des structures différentes, et les longueurs de chemin pondérées des arbres binaires avec des structures différentes ne sont pas nécessairement les mêmes. Ce n'est que lorsque la longueur de chemin pondérée de l'arbre binaire est la plus petite que l'arbre binaire est un arbre de Huffman.
Par exemple, étant donné les quatre poids de 3, 5, 7, 13 comme poids des nœuds feuilles, un arbre binaire à quatre nœuds feuilles peut avoir de nombreuses structures différentes. Voici un exemple de deux d'entre eux, le côté gauche de l'arbre binaire La longueur pondérée du chemin est 3 * 3 + 5 * 2 + 7 * 2 + 13 * 2 = 59, et la longueur pondérée du chemin de l'arbre binaire à droite est 13 * 1 + 7 * 2 + 3 * 3 + 5 * 3 = 51. Selon les caractéristiques de l'arbre de Huffman, le nœud avec le poids le plus élevé est plus proche de la racine, c'est-à-dire que le nœud avec le poids le plus élevé a un chemin plus court. L'arbre binaire à droite dans la figure ci-dessous est un Huffman tree, et le poids de l'arbre est La longueur du chemin a atteint un minimum.
Alors, comment construire un arbre de Huffman basé sur les poids des nœuds feuilles donnés? Avant de construire l'arbre de Huffman, dérivez d'abord quelques propriétés générales de l'arbre de Huffman.
1. Pour s'assurer que l'arbre binaire construit est un arbre de Huffman, il est nécessaire de rendre le chemin du nœud avec un poids important aussi court que possible. Inversement, le nœud avec un faible poids ne peut être qu'au niveau supérieur du arbre binaire, et le chemin court est Donner des nœuds avec de gros poids. D'un point de vue local, tant qu'il est assuré que le chemin de chaque nœud n'est pas supérieur au nœud dont le poids est plus petit que lui, l'algorithme glouton peut être utilisé.
2. Il n'y aura pas de nœud avec un seul nœud enfant dans l'arborescence de Huffman. En supposant qu'il existe un nœud avec un seul nœud enfant dans l'arborescence de Huffman, si le nœud est supprimé, la longueur du chemin de tous les nœuds feuilles de son sous-arbre peut être réduite de 1, et un arbre binaire avec une longueur de chemin pondérée plus petite peut être construit, qui est la même que La définition de l'arbre de Hoffman est contradictoire, donc l'hypothèse ne tient pas.
3. Si l'arborescence de Huffman n'a que deux nœuds feuilles, les chemins des deux nœuds feuilles sont égaux et les deux valent 1.
Selon ces propriétés, commencez à construire l'arbre de Huffman, les étapes sont les suivantes:
1. Considérons un arbre avec N nœuds feuilles comme une forêt de N arbres (chaque arbre n'a qu'un seul nœud racine).
Prenons 3,5,7,13 comme exemples.
2. Sélectionnez les deux arbres avec le plus petit poids de nœud racine de la forêt, et utilisez-les comme sous-arbres gauche et droit du nouvel arbre (de sorte que le nouvel arbre soit construit pour satisfaire l'arbre de Huffman), et le poids du nœud racine de le nouvel arbre est ses sous-arbres gauche et droit. La somme des poids du nœud racine. Ensuite, les deux arbres fusionnés sont supprimés de la forêt et le nouvel arbre est ajouté à la forêt.
Choisissez parmi eux les plus petits 3 et 5, fusionnez-les dans un arbre Hoffman, puis ajoutez le nouvel arbre à la forêt.
3. Répétez l'étape 2 jusqu'à ce qu'il ne reste qu'un seul arbre dans la forêt et que le dernier arbre soit l'arbre Hoffman.
Afin de garantir que la structure de l'arbre de Huffman est unique, l'arbre avec un petit poids de nœud racine est utilisé comme sous-arbre gauche et l'arbre avec un grand poids de nœud racine est utilisé comme sous-arbre droit lors de chaque fusion dans cet article. Cela peut être déterminé par vous-même, car tant que la longueur de chemin pondérée de l'arbre atteint le minimum, quelle que soit la structure, il s'agit d'un arbre Hoffman et l'arbre Hoffman n'est pas unique.
Continuez à sélectionner les plus petits 7 et 8 et fusionnez-les.
La structure arborescente finale de Hoffman est la suivante.
Vérifiez maintenant que la longueur pondérée du chemin de l'arbre est WPL = 13 * 1 + 7 * 2 + 3 * 3 + 5 * 3 = 51. Plus le poids est élevé, plus le chemin du nœud est court, il s'agit donc d'un arbre de Huffman .
Troisièmement, Python implémente l'arbre de Huffman
1. Préparation du code
# 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
Créez d'abord une classe de nœuds Node, qui est utilisée pour créer les nœuds de l'arbre de Huffman. Ici, nous devons y prêter attention, car lors de la construction de l'arbre de Huffman, il est nécessaire de sélectionner en continu les deux arbres avec le plus petit nœud racine d'un forêt à fusionner, donc dans le nœud Ajouter un bit d'indicateur, is_in_tree, si c'est True, cela signifie que l'arbre a été fusionné dans l'arbre de Huffman et ne sera pas récupéré à plusieurs reprises.
Pour implémenter une classe d'arbre Huffman HuffmanTree à l'avance, préparez d'abord une méthode show_tree () pour imprimer l'arbre de Huffman dans une structure arborescente.
Selon le processus de construction de l'arbre de Hoffman, la méthode de construction de l'arbre de Hoffman est réalisée.
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 (sortants): Construisez un arbre Huffman. Lors de la construction de l'arbre de Huffman, N poids sera donné, et si N <= 0, retourne directement. Si N = 1, utilisez directement ce poids comme poids du nœud racine pour construire un arbre de Huffman. Si N> = 2, utilisez d'abord ces N poids comme poids du nœud racine pour construire une forêt contenant N arbres, puis sélectionnez les deux arbres avec le plus petit poids de nœud racine de la forêt à fusionner, et bouclez jusqu'à ce qu'il ne reste qu'un arbre.
Dans cette méthode, il y a les points suivants à noter, sinon il est facile de se tromper:
1. Pour faciliter le traitement dans le code, l'arborescence fusionnée n'est pas supprimée de la liste des bois (l'opération de suppression est très gênante, surtout lorsque les poids sont égaux), mais en modifiant le bit indicateur is_in_tree du nœud racine, si is_in_tree vaut True, cela signifie que l'arborescence a été fusionnée et ne sera pas récupérée à plusieurs reprises.
Alors, comment juger que l'arbre de Huffman a été construit? Lorsque le drapeau is_in_tree du nœud racine d'un seul arbre dans les bois est Faux, mais ce n'est pas facile à juger. Après chaque fusion, il est nécessaire de juger les bois. Racine bit de drapeau de nœud. Selon les propriétés analysées ci-dessus, en utilisant l'algorithme glouton, chaque fois que les arbres sont fusionnés, le nouvel arbre est un arbre de Huffman local, et il n'y aura pas de nœuds avec un seul nœud enfant dans l'arbre de Huffman. Dans le processus de construction de l'arbre de Huffman, chaque nœud est ajouté aux bois de la forêt en tant que nœud racine d'un arbre, de sorte que la longueur des bois est égale au nombre de nœuds de l'arbre de Hoffman, lorsque la longueur des bois atteint le Hoffman Lorsque le nombre total de nœuds dans l'arbre, l'arbre de Huffman est construit.
Dans l’arbre de Huffman, à l’exception du nœud feuille, les autres nœuds ont deux nœuds enfants. Selon les caractéristiques de l’arbre binaire, lorsque le nombre de nœuds feuilles est N, le nombre de nœuds avec deux nœuds enfants est N + 1, donc le Huo à N nœuds feuilles Le nombre de nœuds dans l'arbre de Fuman est de 2 * N + 1.
2. Afin d'obtenir les deux nœuds racine avec le poids le plus petit, deux variables node1 et node2 sont déclarées à l'avance. Ces deux variables peuvent être affectées à deux nœuds avec des poids importants à l'avance. Il est recommandé d'utiliser directement un flottant infini positif ( 'inf'), si les première et deuxième valeurs de woods sont assignées au début, lorsque la première ou la deuxième valeur est la valeur minimale, la même valeur peut être récupérée à chaque fois dans la boucle.
3. Lorsque vous recherchez les deux valeurs de nœud racine avec le poids le plus petit, si le poids du nœud actuel est inférieur au poids de node1, node.data <node1.data, alors node1 est affecté à node2 et node est affecté à node1, si Le poids du nœud actuel est supérieur ou égal au poids de node1 et inférieur au poids de node2, node1.data <= node.data <node2.data, alors le nœud est affecté à node2 . Il est facile d'oublier le deuxième cas ici.
if __name__ == '__main__':
tree = HuffmanTree()
leavers = [11, 5, 7, 13, 17, 11]
tree.huffman(leavers)
tree.show_tree()
résultat de l'opération:
--------------------
64
├─R─39
| ├─R─22
| | ├─R─11
| | └─L─11
| └─L─17
└─L─25
├─R─13
└─L─12
├─R─7
└─L─5
--------------------
Étant donné que les poids de N nœuds feuilles sont [11, 5, 7, 13, 17, 11], la structure arborescente de Huffman est comme illustré dans la figure ci-dessous.
La longueur de chemin pondérée de l'arbre de Huffman est WPL = 13 * 2 + 17 * 2 + 5 * 3 + 7 * 3 + 11 * 3 + 11 * 3 = 162.