ハフマン ツリーとハフマン コーディング (プライオリティ キューの実装に基づく)

ハフマン ツリーとハフマン コーディング (プライオリティ キューの実装に基づく)

コンセプト

ベース

  • パス: ツリー内のあるノードから別のノードへの分岐
  • パスの長さ: パス上の分岐の数は、パスの長さと呼ばれます
  • ツリーのパスの長さ: ツリーのルートから各ノードまでのパスの長さの合計
  • ノードの加重パス長: ノードからツリーのルートまでのパス長とノードの重みの積
  • ツリーの加重パス長 WPL: ツリー内のすべてのリーフ ノードの加重パス長の合計

ハフマン木の概念

ツリーの加重パス長 (WPL) によって定義される式は、次のように取得できます。ここで、wi w_iw葉ノードの重み、li l_ilは葉ノードのパスの長さ
WPL ( n ) = ∑ i = 1 nwili WPL(n) = \sum_{i=1}^{n} w_il_iW P L ( n )=私は= 1nwl

次の n 個の重みを設定
{ w 1 , w 2 , . . . , wn } \{w_1,w_2,...,w_n\}{ w1w2... ,wnn 個の葉ノードを持つ二分木を構築し
ます。各葉の重みはwi w_iw

その中で、WPLが最も小さい二分木はハフマン木と呼ばれ、最適二分木とも呼ばれます。

つまり、ハフマン木の WPL は次の条件を満たす
WPLH affman = WPL min ( n ) WPL_{Haffman} = WPL_{min}(n)W P Lハフマン_ _ _=W P L( n )この概念から、ハフマン木の形状が一意ではないことを
理解するのは難しくありません

ルート ノードから開始して、左の息子に接続されたエッジは 0 として記録され、右の息子に接続されたエッジは 1 として記録され、リーフ ノードまで、通過したエッジの数によって形成されるバイナリ シーケンスはハフマンです。葉ノードのコード

ハフマン エンコーディングの利点は、各リーフ ノードのエンコーディング (一意の文字またはその他のデータに対応する) が一意であり、任意のエンコーディングが別のエンコーディングのプレフィックスにならないことです。これにより、デコーディングの一意性が保証されます。

いくつかのプロパティもあります:

n_0 の場合n 0n0葉ノードのハフマン木、ノード数は次のとおり
node = 2 n 0 − 1 node = 2n_0-1ノード_ _ _=2n _01

ハフマン ツリーの構築とハフマン コードの取得

{ ( A , 2 ) , ( B , 5 ) , ( C , 4 ) , ( D ,
9 ) } \{(A,2) ,(B ,5),(C,4),(D,9)\}{ ( 2 ) ( B 5 ( C 4 ( D 9 )}
このデータを使用してハフマン木を構築します

まず、上記のデータは、ノードが 1 つしかない二分木と見なされます。

最小のルート ノードの重み{ 2 , 4 } \{2, 4\}を持つ 2 つの二分木を取り出す
{ 2 , 4 }
2 つ (左側のサブツリーのルート ノードの重みが右側のサブツリーのルート ノード以下) を同じ親ノードに接続すると、親ノードの重みは 6 になります。

ここに画像の説明を挿入

生まれたばかりの新しいツリーに加えて、使用されていないツリーのルート ノードに対応する重みは
{ 5 , 6 , 9 } \{5,6,9\}です。{ 5 6 ,9 )
また、2 つの最小のものを選択します (この時点で、新しく構築されたルート ノードが元のルート ノードの重みに等しい場合、元のルート ノードに対応するツリーが左のサブツリーとして使用され、新しく構築されたルート ノードがツリーに対応する右側のサブツリー)、上記の手順を繰り返して次の図を取得します。

ここに画像の説明を挿入

このとき未使用のツリーは 2 つだけで、ルート ノードに対応する重みは次のようになります
{ 9 , 11 } \{9,11\}{ 9 11 }
上記の手順をもう一度実行して、すべての木を使用します。つまり、完全なハフマン木を取得します。

ここに画像の説明を挿入

左の息子を結ぶエッジを 0、右の息子を結ぶエッジを 1 として記録します。

ここに画像の説明を挿入

各文字のハフマンコードを取得できます

(通常、コーディング シーケンスは右から左に記述します。たとえば、A は 011 です。左から右に記述する場合は、110 と記述する必要があります。この記事と以下のコード デモでは、すべて右から左を使用しています。メソッド) A :
011 , B : 01 , C : 111 , D : 0 A:011,B:01,C:111,D:0:011 B:01 ,C:111 D:0
文字列の文字列 BDA の場合、対応するハフマン エンコーディングは次のとおりです (文字列のシーケンスは左から右に読み取られ、コードは右から左に読み取られてデコードされます) 0 10 011 0\space10\
space0110 10 011  

コードのデモ

クラス定義

最初にハフマン木クラスを定義します

typedef vector<pair<int, char>> huff;

class Huffman {
    
    
private:
	//结点定义
	class node {
    
    
	public:
		char word;
		int weight;				//权值

		node *lc, *rc;	//左儿子结点,右儿子结点
		string code;
		
		node(int weight, char word) {
    
    
			lc = nullptr;
			rc = nullptr;
			code = "";

			this->word = word;
			this->weight = weight;
		}
	};

private:
	priority_queue<pair<int, node*>, vector<pair<int, node*>>, greater<pair<int, node*>>> que;	//存放结点指针的优先队列(小根堆)
	node* root;	//根结点
	unordered_map<char, string> un_map;

public:
	Huffman(vector<pair<int, char>> vec);
	string Find(char index);

private:
	void _CreateCode(node *n, stack<string> st, int flag);

};

方法

コンストラクタ

Huffman::Huffman(vector<pair<int,char>> vec)
{
    
    
	//将外部数据转成结点,存放到优先队列
	for (auto it = vec.begin(); it != vec.end(); it++)
	{
    
    
		node* p = new node(it->first, it->second);
		this->que.push(make_pair(it->first,p));
	}

	//构造哈夫曼树
	int cnt = 0;	//计数,每弹出两个结点连接到一个父节点,cnt置为0
	pair<int,node*> temp[2];
	while (!que.empty())
	{
    
    
		if (cnt >= 2)
		{
    
    
			node* p = new node(temp[0].first + temp[1].first, ' ');
			p->lc = temp[0].second;
			p->rc = temp[1].second;
			que.push(make_pair(temp[0].first + temp[1].first, p));
			cnt = 0;

			this->root = p;	//不断设置根节点
			continue;
		}

		temp[cnt] = que.top();
		que.pop();
		cnt++;
	}
	//最后会剩余两个结点于temp中,把它们连接起来
	node* p = new node(temp[0].first + temp[1].first, ' ');
	p->lc = temp[0].second;
	p->rc = temp[1].second;
	this->root = p;


	//建立叶子节点编码,左分支为0,右分支为1
	stack<string> st;
	_CreateCode(this->root, st, 0);

}

コード マッピング

文字に対応するハフマンコードをリーフノードに保存し、マップへのマッピング関係を作成します

void Huffman::_CreateCode(node *n,stack<string> st, int flag)
{
    
    
	if (!n) return;

	flag == -1 ? st.push("0") : (flag == 0 ? st.push("") : st.push("1"));
	if (n->lc == nullptr && n->rc == nullptr)
	{
    
    
		while (!st.empty())
		{
    
    
			n->code += st.top();
			st.pop();
		}
		un_map[n->word] = n->code;
		return;
	}

	this->_CreateCode(n->lc, st, -1);
	this->_CreateCode(n->rc, st, 1);
}

お問い合わせ

unordered_map<char, string> un_map;クエリを実行する場合は、関連するキーに対応する値を返すだけで済みます。

string Huffman::Find(char index)
{
    
    
	return this->un_map[index];
}

終了

ここでのコードの欠点は、まだ実装されていないデコードがあることです。ただ、各文字のハフマン符号を部分文字列として主文字列と一致させれば十分だと思いますが、これは文字列アルゴリズムの問​​題なのでここでは書きません。

おすすめ

転載: blog.csdn.net/qq_42759112/article/details/127505908
おすすめ