前言
由于这一专栏算是我本科通信专业知识的总结,构建一个完备的通信系统是我的目标,且信源编码又是其中非常重要的一节,所以必须总结一下。但又由于不同的信源系统对应的信源编码是大相径庭的。所以这篇文章就作类似科普一般的文章,并且会附上基于MATLAB实现的Huffman Coding,希望通信小白可从这篇文章构建信源编码的知识网并实现Huffman Coding。
目录
4.2 完整的Huffman Coding编码译码MATLAB实现
一、信源编码
正如在本专栏量化一篇中讲到,信源编码是高度关系到信源的,不同的信源可能对应完全不同的信源编码方案。信源编码的首要目的如 移动通信原理与系统 一书中所表述的一样: “通常,对于一个数字通信系统而言,信源编码位于从信源到信宿的整个传输链路中的第一个环节,其首要目的就是通过压缩信源产生的冗余信息,降低传递这些不必要信息的开销,从而提高整个传输链路的有效性。” 打个比方,你想要的从你的电脑上发送 Game of Thrones 给你的另一台设备,假设一个字母一个字节(8bits),想想你得传多少个bits。但是我们知道,小说里字母出现的概率是很不一样的,通常来说e出现的次数最多,z出现次数最少,而出现次数最多的e理论上最好用更少的比特数去表示,这样就有可能减少传输的比特数。这就是一个简单的信源编码思想。
而目前非常广为流传的编码技术主要有以下三种:
1. Huffman Coding
2. 算数编码
3. LZ 编码(注意,LZ编码是一系列编码,常见的如LZ-78)
还是如上所说,不同信源可能对应完全不同的方案,如3G系统中视频信源编码主要是H.264,2G/3G 系统中的话音信源编码有CELP,AMR等。它们根据信源的特点设计了更行之有效的编码技术。但是这不代表上三种没有学习必要,他们能用于非常广泛的领域,比如我们今天MATLAB实现的Huffman Coding会用于zip压缩技术中。
二、Huffman Coding - 按步骤直接实现
2.1 Huffman Coding步骤
首先我们来按直觉实现一次Huffman Coding,然后再介绍如何用二叉树思想实现,你会发现二叉树思想实现地会更方便简单。
Huffman Coding是一种Prefix-free Coding,若要从数学上深度学习Source Coding可以看下面这个MIT的网课:
麻省理工 数字通信原理 I (MIT 6.450, Principles of Digital Communications I)【英】_哔哩哔哩_bilibilihttps://www.youtube.com/playlist?list=PL2AD004D035C24F21讲师:Prof. Lizhong Zheng, Prof. Robert Gallager课程地址:https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-450-principles-of-dihttps://www.bilibili.com/video/BV1Qt411Y7BA在Chapter2 和 Chapter3章节,有足够的数学细节。而且是全英文的可以锻炼自己的听力(好像有中英字幕版)。在此推荐一下。而这篇文章就不涉及更多的数学细节了。
Huffman Coding的主要流程非常简单,就以下三步:
1. 计算出你要编码的所有符号的出现概率,并按从大到小(从小到大也可以)降序排列。
[MATLAB] 对应MATLAB可以使用以下函数:
sort(A,dim,mode); A 是矩阵,dim=1则按列,dim=2则按行,mode可为'ascend' ,‘descend’
sortrows(A,mode); 和上面不同的是,sort会每一列/行都排序,而sortrows只排序第一列/行,其他列/行会像随从一样跟着动。
2.把最小的两个概率相加,形成新的一个合并概率,并将这个合并概率与原来的除最小两个概率的其他概率进行相同的排序方式。(注意,若合并概率和原来的概率有数值相同的,则将合并概率放在上面,原来的概率放下面)
[MATLAB] 算法
一般正常的方法是,等全部概率操作完,也即合并概率变为1时,再从最远处开始编码。但是我在MATLAB实现中,每两个概率相加就进行编码,这时候分四种情况和应对手段:
1.若这两个概率都不是合并概率,则给这两个概率所表示的字符编码'0'或'1'(比如概率大的为1小的为0,反之亦可),并且将这两个概率所表示的字符存储起来。(为什么存储起来后面就知道了)【比如下面我将全部以概率大的编码'1',小的编码'0'来操作】
2.若这两个概率都是合并概率,意味着它们一定来源于先前编码的字符的加和,所以分别给这两个合并概率的来源字符编码'0'或'1'。所以在第一步会有存储字符的操作,因为到时候我们要给来源编码,我们得知道来源。
3.一个是合并概率一个不是合并概率的情况有两种,也即合并概率数值较大或者较小,应对方法就是对合并概率的来源进行'0'或'1'的编码,并对另一个概率代表的字符编码'0‘或'1‘。
3. 计算到合并概率为1时,停止编码。(意味着到头了)
2.2 MATLAB 代码
以下是MATLAB的Huffman Coding实现,已经全部英文注释完毕,如果需要验证别的符号集,只需更改symbol和probabilities这两个向量里的信息即可。这里举例使用的是{'a' : 0.05,'b' : 0.06,'c' : 0.09,'d' : 0.1,'e' : 0.15,'f' : 0.15,'g' : 0.4}。
%%
% Construction of the Test table
probabilities=[0.05 0.06 0.09 0.1 0.15 0.15 0.4]'; % The probability of each symbol
symbol={'a','b','c','d','e','f','g'}; % Symbol list
symbols_index=linspace(1,length(symbol),length(symbol))'; % The index for each symbol, for example 1 represent 'a', '2' represent 'b'
table=[probabilities,symbols_index]; % This is an table containing information of upper two matrixs
max=length(probabilities); % Here we have 7 probabilities
%%
% Huffman Coding
sym_freq_sorted=sortrows(table,'descend'); % The sorted frequency table of all symbols
pro_com=0; % The combined probability [Used in step 2]
huffman_table=cell(max,1); % A table storing Huffman Code
% Initialization of the table above
for j=1:1:size(huffman_table,1)
huffman_table{j}=[];
end
list_psym=cell(max,1); % pre-allocate the 'list of previous symbols' [Used in step 2]
i=0; % A helpful parameter to locate the branch
while(pro_com<1) % As long as the combined probability equal to 1, The coding stops
% Draw the last two symbols out
upper=sym_freq_sorted(end,1);upper_index=sym_freq_sorted(end,2);
lower=sym_freq_sorted(end-1,1);lower_index=sym_freq_sorted(end-1,2);
% Coding Part
% If these two probabilities are not combined probability
if (upper_index<max+2)&&(lower_index<max+2)
huffman_table{upper_index}=[huffman_table{upper_index},'0'];
huffman_table{lower_index}=[huffman_table{lower_index},'1'];
i=i+1;
list_psym{i}=[upper_index,lower_index];
end
% If the upper one is combined probability
if (upper_index>max+2)&&(lower_index<max+2)
for index=list_psym{upper_index-500}
huffman_table{index}=[huffman_table{index},'0'];
end
huffman_table{lower_index}=[huffman_table{lower_index},'1'];
i=i+1;
list_psym{i}=[list_psym{upper_index-500},lower_index];
end
% If the lower one is combined probability
if (upper_index<max+2)&&(lower_index>max+2)
for index=list_psym{lower_index-500}
huffman_table{index}=[huffman_table{index},'1'];
end
huffman_table{upper_index}=[huffman_table{upper_index},'0'];
i=i+1;
list_psym{i}=[list_psym{lower_index-500},upper_index];
end
% If both of these probabilities are combined probability
if (upper_index>max+2)&&(lower_index>max+2)
for index=list_psym{upper_index-500}
huffman_table{index}=[huffman_table{index},'0'];
end
for index=list_psym{lower_index-500}
huffman_table{index}=[huffman_table{index},'1'];
end
i=i+1;
list_psym{i}=[list_psym{upper_index-500},list_psym{lower_index-500}];
end
% Combine two probabilities & extend it to symbol table & sort again
pro_com=upper+lower;
sym_freq_sorted=sym_freq_sorted(1:end-2,:);
sym_freq_sorted=[[pro_com,500+i];sym_freq_sorted];
sym_freq_sorted=sortrows(sym_freq_sorted,'descend');
end
%%
% Demonstrate the Huffman Coding
for i=1:1:length(symbol)
disp(['The Code of symbol: ',symbol{i},' is ',huffman_table{i}]);
end
手写验证:
三、Huffman Coding 二叉树思想实现
3.1 二叉树思想
3.1.1 Huffman Tree 结构
Huffman Coding 的二叉树思想实现关键是Huffman Tree的构建。先看看二叉树的构造:
1.父枝(parent)
2. 左孩子/左枝(Left child/ Left branch)和 右孩子/右枝 (Right child/ Right branch)
3. 权重(weight) 在Huffman Coding中就是每个枝的概率
4. 编码(Coding)在Huffman Coding中就是0或1,这里采用左枝为0,右枝为1
3.1.2 Huffman Tree 构造方法
回忆上面的Huffman Coding的顺序,就是不断地将概率数值最小的两个概率相加形成一个新的概率,现在我们将概率换成带有权重的树枝。那么就是不断将权重最小的树枝合并形成新的父枝,然后再将父枝加入其他的树枝之间排序,再相加,直到加到一个权重为1的树枝。拆解下来就是这样:(仍然沿用第二节的例题)
Step 1. 把全部要编码的符号和概率变成树枝
Step 2. 将最小的两个枝相加,并且记得重新排序
Step 3. 不断重复第二步
这样就可以从权重为1的枝开始,向下编码,权值大的为左孩子,编码为0,权值小的为右孩子,编码为1.上面的图我因为是手绘的没有注意左右之分,读者自己要注意。
3.1.3 Huffman Tree的特点
在此有几个注意点,也是算法的源泉,但是此文章不会进行数学的证明,想看证明可以看上面推荐的MIT网课,几个注意点是:
1. Huffman Tree的枝的个数一定是,n是要编码的符号的数量。数学原理是“它是无前缀码”
2.从最小权重的枝开始,相加形成新的枝,直到新枝权重为1
3. Huffman Coding得到的码是无前缀码(prefix free code)。啥是prefix free code呢?打比方说一组码是{'a': 0 'b': 011 'c': 101},你会发现0是011的前缀,那么这样的时候解码就会出现疑惑,比如接收端收到010,0出现的时候我要继续往下译码吗?但是无前缀码就没有这种疑惑,比如{‘’a': 0 'b': 101 'c': 110}。
3.2 MATLAB 实现
%%
% Test table of symbols
clear;clc;
table={'a',0.05;
'b',0.06;
'c',0.09;
'd',0.10;
'e',0.15;
'f',0.15;
'g',0.40};
nos=size(table,1); % The amount of symbols
weight_list=zeros(nos,1);
for i=1:1:nos
weight_list(i)=table{i,2};
end
%%
%The construction of Huffman tree
% A table of Huffman tree with four columns
Huffman_Tree=zeros(2*nos-1,4); % weights parent left_child right_child
for i=1:nos
Huffman_Tree(i,1)=weight_list(i);
end
buffer2=weight_list;
for i=1:nos-1
buffer1=buffer2(:,1); % A buffer for sorting
[probabilities,index]=sort(buffer1,'descend');
sum=probabilities(nos-i+1)+probabilities(nos-i); % sum of the two smallest probabilities(Pay attention to 0)
buffer2(nos+i,1)=sum;
buffer2(index(nos-i+1),1)=0; % delete the smallest value in order to go on
buffer2(index(nos-i),1)=0;
Huffman_Tree(nos+i,1)=sum; % weight of new branch
Huffman_Tree(index(nos-i+1),2)=nos+i; % update the index of parent of left child
Huffman_Tree(index(nos-i),2)=nos+i; % update the index of parent of right child
Huffman_Tree(nos+i,3)=index(nos-i+1); % store the index of left child
Huffman_Tree(nos+i,4)=index(nos-i); % store the index of right child
end
figure('name','Huffman Tree');
treeplot(Huffman_Tree(:,2)');title('Huffman Tree');
%%
% Huffman Encoding
Huffman_table=cell(nos,2);
Huffman_table(:,1)=table(:,1);
for i=1:1:nos
child=i;
parent=Huffman_Tree(i,2);
while(parent~=0)
if Huffman_Tree(parent,3)==child
Huffman_table{i,2}=[0,Huffman_table{i,2}]; % left is 0
else
Huffman_table{i,2}=[1,Huffman_table{i,2}]; % right is 1
end
child=parent;
parent=Huffman_Tree(parent,2);
end
end
四、Huffman Coding的解码(译码)
4.1 Huffman 译码器的原理
要译码必须对原理有足够的了解。Huffman 译码器的关键在于,他需要接收到来自发送端的 各个符号的出现次数和总符号数,然后根据接收到的信息计算出各个符号的出现频率,也就是说,接收端需要知道你在发送端进行Huffman Coding的东西。具体的步骤如下:
1.根据得到的信息计算各个符号出现频率
2.通过上面的符号频率重构Huffman Tree
3.通过Huffman Tree进行译码
而译码过程非常简单。由于是无前缀码,所以一翻译到没有左孩子和右孩子的结点就代表是一个符号。(自己画一下就发现了)
步骤是:
1. 从重构的Huffman Tree的权重为1的结点开始。根据接收到的比特流,一位一位地翻译。
2. 若此刻比特流是0,则往左侧走一个枝,并且读取这个结点的子节点
3. 若这个结点没有子节点,则表示到了一个符号
4. 不断翻译直到比特流结束。
4.2 完整的Huffman Coding编码译码MATLAB实现
%%
% Test table of symbols
clear;clc;
table={'a',0.05;
'b',0.06;
'c',0.09;
'd',0.10;
'e',0.15;
'f',0.15;
'g',0.40};
nos=size(table,1); % The amount of symbols
weight_list=zeros(nos,1);
for i=1:1:nos
weight_list(i)=table{i,2};
end
%%
%The construction of Huffman tree
% A table of Huffman tree with four columns
Huffman_Tree=zeros(2*nos-1,4); % weights parent left_child right_child
for i=1:nos
Huffman_Tree(i,1)=weight_list(i);
end
buffer2=weight_list;
for i=1:nos-1
buffer1=buffer2(:,1); % A buffer for sorting
[probabilities,index]=sort(buffer1,'descend');
sum=probabilities(nos-i+1)+probabilities(nos-i); % sum of the two smallest probabilities(Pay attention to 0)
buffer2(nos+i,1)=sum;
buffer2(index(nos-i+1),1)=0; % delete the smallest value in order to go on
buffer2(index(nos-i),1)=0;
Huffman_Tree(nos+i,1)=sum; % weight of new branch
Huffman_Tree(index(nos-i+1),2)=nos+i; % update the index of parent of left child
Huffman_Tree(index(nos-i),2)=nos+i; % update the index of parent of right child
Huffman_Tree(nos+i,3)=index(nos-i+1); % store the index of left child
Huffman_Tree(nos+i,4)=index(nos-i); % store the index of right child
end
figure('name','Huffman Tree');
treeplot(Huffman_Tree(:,2)');title('Huffman Tree');
%%
% Huffman Encoding
Huffman_table=cell(nos,2);
Huffman_table(:,1)=table(:,1);
for i=1:1:nos
child=i;
parent=Huffman_Tree(i,2);
while(parent~=0)
if Huffman_Tree(parent,3)==child
Huffman_table{i,2}=[0,Huffman_table{i,2}]; % left is 0
else
Huffman_table{i,2}=[1,Huffman_table{i,2}]; % right is 1
end
child=parent;
parent=Huffman_Tree(parent,2);
end
end
%%
% Huffman Decoding
bit_stream=[1 1 0 1 0 1];
len_of_bit_stream=size(bit_stream,2);
flag=2*nos-1;
i=1;
message_index=[];
while(i<=len_of_bit_stream) % Once read the end of bits stream, then stop
if(bit_stream(i)==0) % Start from the root, if 0 then go left, else go right
flag=Huffman_Tree(flag,3);
else
flag=Huffman_Tree(flag,4);
end
i=i+1; % Next bit
if Huffman_Tree(flag,3)==0
message_index(end+1)=flag;
flag=2*nos-1;
end
end
message='';
len=length(message_index); % Length of message
for i=1:1:len
message=[message,table{message_index(i),1}];
end
disp(['The text is: ',message]); % Display the message