信息论实验课上的作业,其中包括【香农编码、哈弗曼编码、算术编码和费诺编码】虽然有很大一部分都是网上借鉴别人的,但是自己琢磨了几次也算是看懂了原理,动手写了一些自己理解的东西,索性就记录一下。

1.香农编码

编码步骤:

将信源发出的q个消息符号按其概率大小的次序排列;

计算出各个符号的累加概率。

按下式计算第i个消息对应的码字长度。

将累加概率(十进制小数)变换成二进制小数,根据码长Li取小数点后Li个二进制符号作为第i个消息的码字。

代码:

【shannon.m】

%香农编码
%input:信源分布p
%output:二进制编码code
function shannon(p)
n = length(p);%信源符号数
p = sort(p,'descend');%降序排列
F = zeros(1,n);%累加概率序列
for i=2:n
    F(i)=F(i-1)+p(i-1);
end
l = zeros(1,n);%码长序列
for i=1:n
    l(i)=ceil(-log2(p(i)));%向上取整
end
disp('最终编码结果:');
for i=1:n
    f = max(l(i));
    w=trans(F(i),f); %调用二进制转换编码函数
    for j=1:l(i) %这个循环是指取前多少个作为码字
        code(j)=w(j);
    end
    disp([num2str(p(i)),'->',num2str(code)]); %这句是作为输出语句
end

【trans.m】

%十进制转换为二进制,应用于香农编码
%原理:乘二取整
%输入:需要编码的概率F,f为码长
%输出:编码后的码字
function w = trans( F,f )
for i=1:f
    temp=F.*2;
    if(temp<1)
        w(i)=0;
        F=temp;
    else
        F=temp-1;
        w(i)=1;
    end
end

运行结果:

>> p=[0.20 0.19 0.18 0.17 0.15 0.10 0.01]

p =

    0.2000    0.1900    0.1800    0.1700    0.1500    0.1000    0.0100

>> shannon(p)
最终编码结果:
0.2->0  0  0
0.19->0  0  1
0.18->0  1  1
0.17->1  0  0
0.15->1  0  1
0.1->1  1  1  0
0.01->1  1  1  1  1  1  0

2.哈弗曼编码(二元)

代码:

【myHuffman.m】

clc;
clear;
%输入格式:[p1 p2 p3 … pn]
%A = [0.1 0.18 0.4 0.05 0.06 0.1 0.07 0.04];    

A = input('输入信源概率分布:\n')
n = length(A);
%----先判断输入格式是否正确
for i = 1:n
    if A(i)<0
    fprintf('概率小于0,请重新输入。\n');
    A = input('输入信源概率分布:\n')
    end
end

A = sort(A,'descend');  %降序排列        
T = A;%设置一个当前编码序列
[m,n] = size(A);%行列数
B=zeros(n,n-1);%生成一个空的编码表
%-----编码表的第一列
for i=1:n
    B(i,1)=T(i);
end
sum=B(i,1)+B(i-1,1);%最后两个概率相加
T(n-1)=sum;
T(n)=0;%把刚所求最后两个数之和放进当前编码序列,最后一个设置为0
T = sort(T,'descend');%然后重新进行排序
t = n-1;%当前序列数目

%-----编码表的其他列
for j=2:n-1 %从第二列开始
    for i=1:t
        B(i,j)=T(i);
    end
        K=find(T==sum);  %找到当前序列中sum的位置,记在K中
        B(n,j)=K(end);%从第二列开始,编码表中的最后一位元素是sum在当前序列中的位置
        sum=(B(t-1,j)+B(t,j));%继续把最后两个元素相加记为sum
        T(t-1)=sum;
        T(t)=0;%把刚所求最后两个数之和放进当前编码序列,最后一个设置为0
        T = sort(T,'descend'); %然后重新进行排序
        t=t-1;%当前序列数目
end
%编码表
disp('编码表为:')
B
%-----开始编码
%先设置最后一列的两个元素编码为0和1
%这里写下划线是因为matlab会显示不出来0,所以只能加上下划线让它显示出来
%因为有下划线,所以后面计算码长的时候会除2
END1=sym('[_0,_1]'); 
END=END1;
t=3;     
d=1;          
for j=n-2:-1:1      %从倒数第二列开始依次对各列元素编码
    for i=1:t-2
        if i>1 & B(i,j)==B(i-1,j)
            d=d+1;
        else
            d=1;
        end
         %把相加后的那个数设置为-1方便找到
        B(B(n,j+1),j+1)=-1;          
        temp=B(:,j+1);                
        x=find(temp==B(i,j)); 
        %给没有变过的值编码
        END(i)=END1(x(d));            
    end
    y=B(n,j+1);
    %给变过的值编码,上面的补0,下面的补1
    END(t-1)=[char(END1(y)),'_0'];   
    END(t)=[char(END1(y)),'_1'];
    t=t+1;
    END1=END;
end
%编码结果
disp('编码结果为:')       
END
%-----计算编码效率p
for i=1:n
    [a,b]=size(char(END(i)));
    M(i)=b/2;    %这里除以2是因为有下划线占了一半的码字长度
end
%计算平均码长
L1 = 0;
for i=1:n
    L1 = L1 + M(i).*A(i);
end
%计算信息熵
H1=log2(A);
H=-A*(H1');
p=H/L1;
disp('编码效率为:')
p

备注:= = 这个有个很大的bug就是0在matlab里居然显示不出来!我也又气又无奈……只能用下划线来让它表示出来了。

运行结果:

输入信源概率分布:
[1/6 1/4 5/12 1/6]

A =

    0.1667    0.2500    0.4167    0.1667

编码表为:

B =

    0.4167    0.4167    0.5833
    0.2500    0.3333    0.4167
    0.1667    0.2500         0
    0.1667    2.0000    1.0000

编码结果为:
 
END =
 
[ _1, _0_1, _0_0_0, _0_0_1]
 
编码效率为:

p =

0.9850

3.算术编码

备注:哈哈哈哈这个是我自己写的!!(= = 虽然很简单但本菜鸟还是有自豪感的……)

代码:

【suanshu.m】

%算术编码程序
%输入:信源u、信源分布p、需要进行编码的序列s
%输入备注:输入格式:a=[0 1]  p = [1/4 3/4] s = [1 0 1 1 1]
%输出:该序列的算术编码S
%输出备注:以序列所在区间的左端点值作为所求序列的码字

%输入备注:如果信源符号是字符型,输入方式为:a = 'ab';  s = 'bbbababb';
%其实符号是数字可以写成数组也可以写成字符类型,matlab都支持运算,如:a = '01' s = '1101'
%序列比较长的时候写成字符类型比较方便

function [S] = suanshu(a,p,s)
%设置初始值,序列为空,左端点为0,右端点为1,长度为1
Slow(1)=0;
Shigh(1)=1;
range=Shigh(1)-Slow(1);

%计算信源符号的数目和所需要编码的序列长度
n = length(a);
m = length(s);

%计算信源a中每个元素对应的左右端点low high
low(1) = 0;
for i = 1:n
    if i == 1
        high(i) = p(i);
    else
        low(i) = p(i-1) + low(i-1);
        high(i) = low(i) + p(i);
    end
end
%开始遍历所要编码序列中的每个元素
for i = 1:m
    %寻找与当前元素相等的信源符号,把索引放入num中
    num = find(a==s(i));
    %开始计算添加新符号后的序列左右端点newSlow和newShigh
    Slow(i+1) = Slow(i) + range*low(num);
    Shigh(i+1) = Slow(i) + range*high(num);
    range = Shigh(i+1) - Slow(i+1);
end
%最后以序列所在区间的左端点值作为所求序列的码字
%即输出最后计算得到的Slow(m+1)
S = Slow(m+1);

运行结果:

>> a = '01'

a =

01

>> p = [1/8 7/8]

p =

    0.1250    0.8750

>> s = '11111110111110'

s =

11111110111110

>> suanshu(a,p,s)

ans =

    0.6312

4.费诺编码

备注:这个是朋友给我的= =,只看懂了一点,至于各种递归调用我已经晕了……

代码:

【f1.m】

function x=f1(i,j,p,r)
global x;
x=char(x);
if(j<=i)
   return;
else
    q=0;
%求累加概率
for t=i:j 
    q=p(t)+q;y(t)=q; 
end
%累加概率值与该总概率值减该累加概率值之差取绝对值
for t=i:j
v(t)=abs(y(t)-(q-y(t))); 
end
%取最小的一个值作为分界点
for t=i:j
    if(v(t)==min(v)) 
        %赋值
   for k=i:t 
        x(k,r)='0';
    end
    for k=(t+1):j
        x(k,r)='1';
    end
    d=t;
    %递归
    f1(i,d,p,r+1);
    f2(d+1,j,p,r+1);
    f1(d+1,j,p,r+1);
    f2(i,d,p,r+1);
    else
    end
end 
end
return;

【f2.m】

function x=f2(i,j,p,r)
global x;
x=char(x);
if(j<=i)
   return;
else
   q=0;
   %求累加概率值
for t=i:j 
    q=p(t)+q;y(t-i+1)=q; 
end
%%累加概率值与该总概率值减该累加概率值之差取绝对值
for t=1:j-(i-1)
v(t)=abs(y(t)-(q-y(t))); 
end
%取最小的一个值作为分界点
for t=1:j-(i-1)
    if(v(t)==min(v)) 
        d=t+i-1;
        %赋值
    for k=i:d   
        x(k,r)='0';
    end
    for k=(d+1):j
        x(k,r)='1';
    end 
    %递归
   f2(d+1,j,p,r+1);
   f1(i,d,p,r+1);
   f2(i,d,p,r+1);
   f1(d+1,j,p,r+1);
    else
    end
end 
end
return;

【myfano.m】

fprintf('请输入信源概率');
p=input('p=');
N = length(p);
s=0;l=0;H=0;
%计算信息熵
for i=1:N
    H=H+(- p(i)*log2(p(i)));
end
tic;
for i=1:N-1 %按概率分布大小对信源排序
    for j=i+1:N
        if p(i)<p(j)
           m=p(j);p(j)=p(i);p(i)=m;
        end
   end
end
x=f1(1,N,p,1);
%计算平均码长
for i=1:N  
    L(i)=length(find(x(i,:)));
    l=l+p(i)*L(i);
end
%编码效率
n=H/l; 
fprintf('编码效率:');disp(n) 
fprintf('编码结果:\n');
disp(x) 
fprintf('平均码长:K=');disp(l)

运行结果:

>> myfano
请输入信源概率p=[0.2 0.19 0.18 0.17 0.15 0.1 0.01]
编码效率:    0.9521

编码结果:
00  
010 
011 
10  
110 
1110
1111
平均码长:K=    2.7400

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

2019-11、12月记 上一篇
java笔记-异常 下一篇