C++ はポインターを使用してハフマン ツリーを書き込みます

目次

1 はじめに

2. ノード

3. キャラクター統計

4. 2 つの最小値を選択します

5. ハフマンツリーを構築する

6. トラバーサル機能

7. ソースコード


1 はじめに

目的: C++ 機能を使用してポインターの問題を解決する

注: エンコードやデコードなどの関数関数は、ハフマン初期化、構築、および走査に基づいて実装されるため、ここでは示されません。ハフマン構築および走査関数の主要な部分のみが示されており、主要な部分は解釈されます。

免責事項: 著者は先人たちからインスピレーションを受けており、このブログの一部は先人たちのコードの解釈であり、一部は先人たちのコードに基づいて著者がわずかに変更を加えて入手したものです。

原作者ポータル:ハフマンツリーの設立_Reticent_Manのブログ-CSDNブログ_ハフマンツリーの作成

2. ノード

template<class T>
struct AlphaNode {
	T data;
	int freq=0;
	string code;//编码
	AlphaNode<T>* parent;
	AlphaNode<T>* lchild;
	AlphaNode<T>* rchild;
};

1. テンプレート クラスを使用します。カスタム データのその後の保存に使用します。

2. freq: 周波数の略語。コードの入力端は任意の文字列を入力して統計を作成することなので、統計結果を格納するために使用されるデータ フィールドがここで最初に宣言されます。

3.code:エンコード結果(エンコード内容は当ブログでは表示されません)

4. ノードは、ルート ノードと左右のサブツリーという 3 つのツリー構造に従います。

3. キャラクター統計

//字符串统计函数
//需要注意 这里是从arr_str[1]开始放东西的  arr_str[0]是空的

AlphaNode<char> arr_str[400];//建立一个节点数组

static int length = 1;
void statisticAlpha(string str) {
	
	int i = 0;//从string[0]开始遍历
	while (str[i]) {
		if (0 == i) {
			arr_str[i+1].data = str[i]; 
			arr_str[i+1].freq++;
			length++;
		}
		else {
			int j = 1;//从新建数组的第二个存放数据的节点开始遍历

            //判定是否有重复的数据
			for (; arr_str[j].data!=NULL; j++) {
				if (str[i] == arr_str[j].data)break;
			}
			//锁定应该存放东西的位置 即j位置


			if (j >length-1) {
				arr_str[length].data = str[i];
				arr_str[length].freq++;
				length++;
			}
			else {
				arr_str[j].freq++;
			}
		}
		i++;
	}
	int j = 1;
	cout << "您输入的字符串  统计为:" << endl;
	while (arr_str[j].data!=NULL) {
		cout << arr_str[j].data << ':' << arr_str[j].freq << endl;
		j++;
	}
}


//注意 以下代码只是示例
main(){
    cout <<"请在下方输入一个任意字符串"<< endl;
    string temp;
    getline(cin, temp);
    statisticAlpha(temp);
}

1. main() 関数で temp を使用して一時的に保存します (最後の例)

   読み取りの場合は、スペースを読み取り、復帰で終わる必要があるため、getline(cin,str) 関数が使用されます。

2. 特殊判定: (0==i) は、最初の文字が一致してはいけないため、直接保存されます。

3.

            //判定是否有重复的数据
			for (; arr_str[j].data!=NULL; j++) {
				if (str[i] == arr_str[j].data)break;
			}
			//锁定应该存放东西的位置 即j位置

str[]は一時的に保存する配列なので一時的には変更されないので前に置きます。

結果を比較する場合、配列は1から格納し始めるので、通常の位置[j]は空なので、ループ結果jをそのまま下に使用することができます。

4. 以下では、j が最後まで到達したかどうかを判断し、関連する一連の操作を実行します。

4. 2 つの最小値を選択します

【キーポイント】

1. 最小値は配列の成長に応じて変化し、新しいノードの合計 (2 つの子の頻度) も計算範囲に含まれます。

例: 初回: 2 1 1

        2 回目: 2 1 1 2 (末尾の 2=1+1) (最小の重み (freq) を持つ 2 つの子の合計)

        3回目: 2 1 1 2 4 (最後の4=2+2) (1が2つ使用されているため、最小の重みは古い2と新しい2の順番になります)

2. ノードがすでに子であるかどうかを判断するにはどうすればよいですか?

        セントリー p[200] を導入します。

int p[200] = { 0 };//用来判断节点是否被选取过

        センチネルは最初にすべて 0 に初期化され、選択されていないことを示します。

3.

void Huffman::SelectMin(int &x, int &y,int end) {
	//x、y用于返回两个 最小值权值的 位置
	
	long int min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && p[i] == 0) {
			min1 = a[i].freq;
			x = i;
		}
	}
	min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && i != x && p[i] == 0) {
			min1= a[i].freq;
			y = i;
		}
	}
}

2 つの特別な判定には、 // それらが選択されないようにするために p[i]==0 があることに注意してください。

最小値が確実に生成されるように、999999 に初期化された最小値を定義します。

4. なぜ参照を int &x、int &y に渡すのか

        コードの後半の位置が違ったり、スコープが違ったりするので、いつでもデータを変更できて便利なので、トラブルを避けるために参照方式を採用しています。

5. 終わりとは何か

上の 1. で述べたように、比較の長さは今後も長くなります。今回は後で end が元の長さを渡しますが、次回は length+1 を渡します (新しいノードが生成されるため、新しいノードも比較に含める必要があります)。そのため、end は可変長の数値です。 。

5. ハフマンツリーを構築する

//示例:避免看不懂在写什么
class Huffman{
public:
    AlphaNode<char>* Creation(int);//创建哈夫曼树
private:
    AlphaNode<char>* a;
}
int x, y;


//构建的函数正文:
AlphaNode<char>* Huffman::Creation(int n) {
	int i;
	//初始化
	for (int i = 1; i <= 2 * n - 1; i++) {
		a[i].lchild = a[i].parent = a[i].rchild = NULL;
	}
	for (i = n; i < 2 * n - 2; i++) {
		SelectMin(x, y, i-1);
		p[x] = p[y] = 1;
		a[i].lchild = &a[x];
		a[i].rchild = &a[y];
		a[i].freq = a[x].freq + a[y].freq;
		
	}
	return &a[i - 1];
}


//示例:
main(){
    AlphaNode<char> *p = hf.Creation(length);
    //length就是存储之后的存储数组的长度 比如abca的长度为4(从1开始存  +1)
}

関数のパラメータ int n は配列の長さです (次の例で渡されます)。

コードの中間セクションの説明に重点を置きます。

1. SelectMin( x , y , i-1 );

        最小重みノードの位置を決定し、それを x、y として返します。ここで、x ノードの重み ≤ y ノードの重み

        (デフォルトの x は左側のノードです)

2.(i < 2*n - 2)

なぜこのような判決が下されるのでしょうか?

このサイクルの回数は厳密に制限する必要があります 

最後のサイクルでは、最後から最後と 2 番目に大きい 2 つの重みが常に合計され、この合計によって新しいストレージ ポイントが生成されます。

a[i].freq = a[x].freq + a[y].freq;

文はまだ続きます。

これより回数が1回多いとyの値が不定となり無限ループに陥ります。

(y の参照が渡され、SelectMin 関数は「y に一致する値がない」場合に何が起こるかを指定していないため、結果として、y は元のアドレス値を使用して for ループを継続します)

ループ数 = 配列の長さ - 1;

文字列 abca をカウントした後の配列の長さは 3 (類似した項目を結合) であるため、比較の数は 3-1=2 になります。

コードに戻ると、比較の数は (2*n - 2 - n)+1=n - 1 となります。

3. return &a[i - 1]; はなぜですか?

このアルゴリズムによると、配列のすぐ後ろにスペースを空けて物を格納するためです。

a 配列の先頭部分 (a[1] ~a[n]) はすべて格納されたデータであり、子ノードやルート ノードは含まれません。

ポインタ演算は a[n+1] からのみ利用可能で、子ノードとルート ノード間の接続が可能

そして、この配列の終わり、つまり最後の加重合計ノードがヘッド ノードです。

ヘッド ノードには、対応する子ノードが格納されます。

したがって、そのアドレスを直接返します。

6. トラバーサル機能

void Huffman::DispTree(AlphaNode<char>* a) {
	if (a != NULL) {
		cout << a->data;
		if (a->lchild != NULL || a->rchild != NULL) {
			cout << '(';
			DispTree(a->lchild);
			if (a->rchild != NULL)cout << ',';
			DispTree(a->rchild);
			cout << ')';
		}
	}
}

原作者の言葉を引用すると:

その後、純粋な数値の出力形式では、サブツリーと親の間の対応関係を確認できないことが判明しました。データ構造の本を読んでいると、一般化された表があったので、括弧を付ける方法、つまり、まずノードに左部分木があるかどうかを判断する方法を考えてみました(ハフマン木では、左部分木があれば、右のサブツリーが存在する必要があります)。存在する場合は、括弧を出力し、左と右のサブツリーをカンマで区切ります。これにより、視覚的な可読性が向上します。

7. ソースコード

#include<iostream>
#include<string>
using namespace std;

template<class T>
struct AlphaNode {
	T data;
	int freq=0;
	string code;//编码
	AlphaNode<T>* parent;
	AlphaNode<T>* lchild;
	AlphaNode<T>* rchild;
};
AlphaNode<char> arr_str[400];
int p[200] = { 0 };//用来判断节点是否被选取过
void statisticAlpha(string str);
int x, y;

struct HCode {
	char data;
	string code;
};

class Huffman {
private:
	AlphaNode<char>* a;
	int N;//叶子节点数量
	void code(int i, string newcode);//递归函数 对应第i个节点编码
public:
	Huffman(AlphaNode<char>*);
	AlphaNode<char>* Creation(int);//创建哈夫曼树
	void DispTree(AlphaNode<char>*);//遍历
	void CreateCodeTable();//创建编码表
	void Encode(char *s, char *d);//编码
	void Decode(char *s, char *d);//译码
	void SelectMin( int &x, int &y, int end);
};

Huffman::Huffman(AlphaNode<char>* temp) {
	a = temp;
}


//字符串统计函数
//需要注意 这里是从arr_str[1]开始放东西的  arr_str[0]是空的
static int length = 1;
void statisticAlpha(string str) {
	
	int i = 0;//从string[0]开始遍历
	while (str[i]) {
		if (0 == i) {
			arr_str[i+1].data = str[i]; 
			arr_str[i+1].freq++;
			length++;
		}
		else {
			int j = 1;//从新建数组的第二个存放数据的节点开始遍历
			for (; arr_str[j].data!=NULL; j++) {
				if (str[i] == arr_str[j].data)break;
			}
			//锁定应该存放东西的位置
			if (j >length-1) {
				arr_str[length].data = str[i];
				arr_str[length].freq++;
				length++;
			}
			else {
				arr_str[j].freq++;
			}
		}
		i++;
	}
	int j = 1;
	cout << "您输入的字符串  统计为:" << endl;
	while (arr_str[j].data!=NULL) {
		cout << arr_str[j].data << ':' << arr_str[j].freq << endl;
		j++;
	}
}

//返回统计后的数组的长度


//挑选两个最小值的函数
void Huffman::SelectMin(int &x, int &y,int end) {
	//x、y用于返回两个 最小值权值的 位置
	
	long int min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && p[i] == 0) {
			min1 = a[i].freq;
			x = i;
		}
	}
	min1 = 999999;
	for (int i = 1; i <= end; i++) {
		if (a[i].freq < min1 && i != x && p[i] == 0) {
			min1= a[i].freq;
			y = i;
		}
	}
}
//abbcccddddeeeeeffffffggggggg
AlphaNode<char>* Huffman::Creation(int n) {
	int i;
	//初始化
	for (int i = 1; i <= 2 * n - 1; i++) {
		a[i].lchild = a[i].parent = a[i].rchild = NULL;
	}
	for (i = n; i < 2 * n - 2; i++) {
		SelectMin(x, y, i-1);
		p[x] = p[y] = 1;
		a[i].lchild = &a[x];
		a[i].rchild = &a[y];
		a[i].freq = a[x].freq + a[y].freq;
		
	}
	return &a[i - 1];
}

void Huffman::DispTree(AlphaNode<char>* a) {
	if (a != NULL) {
		cout << a->data;
		if (a->lchild != NULL || a->rchild != NULL) {
			cout << '(';
			DispTree(a->lchild);
			if (a->rchild != NULL)cout << ',';
			DispTree(a->rchild);
			cout << ')';
		}
	}
}

int main() {
	cout <<"请在下方输入一个任意字符串"<< endl;
	string temp;
	getline(cin, temp);
	statisticAlpha(temp);

	cout << length << endl;
	Huffman hf(arr_str);
	AlphaNode<char> *p = hf.Creation(length);
	hf.DispTree(p);
	return 0;
}

おすすめ

転載: blog.csdn.net/Samurai_Bushido/article/details/124889615