信息论实验-信源编码2(Lz编码和算数编码的C++实现)

2023-05-16

上一篇文章给出了Huffman编码和Shannon Fano编码的编码原理以及C++的程序,程序可以用来实现给任意类型的文件进行无损压缩,缺点是比较耗时,不能作为正常的通用压缩软件来使用,但是作为算法理解,算法思路是没有问题的,后续可能需要进行优化,下面的LZ编码和算数编码和Huffman、Fano编码是走的截然不同的道路,他们的思想差别很大,但却殊途同归,在算法理解上我借助了一些网友前辈的博客中的例子,文章最后我也会指出引用了那些文章,谢谢前辈们给出的经典的例子能让晚辈站在巨人的肩膀上,所以在此声明,希望我的引用不会侵犯到前辈们的权益
* 先上源代码*
信源编码源代码

第三章:算数编码的实现

一. 算数编码的原理

算数编码不同于Huffman编码,它是非分组(非块)码。它从全序列出发,考虑符号之间的依赖关系来进行编码的。
算数编码主要的编码方法正是计算输入信源符号序列所对应的区间。术编码的基本原理是将编码的消息表示成实数0和1之间的一个间隔(Interval),消息越长,编码表示它的间隔就越小,表示这一间隔所需的二进制位就越多。
算术编码用到两个基本的参数:符号的概率和它的编码间隔。信源符号的概率决定压缩编码的效率,也决定编码过程中信源符号的间隔,而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输出。
给定事件序列的算术编码步骤如下:
(1)编码器在开始时将“当前间隔” [ L, H) 设置为[0,1)。
(2)对每一事件,编码器按步骤(a)和(b)进行处理
(a)编码器将“当前间隔”分为子间隔,每一个事件一个。
(b)一个子间隔的大小与下一个将出现的事件的概率成比例,编码器选择子间隔对应于下一个确切发生的事件相对应,并使它成为新的“当前间隔”。
(3)最后输出的“当前间隔”的下边界就是该给定事件序列的算术编码。
设Low和High分别表示“当前间隔”的下边界和上边界,CodeRange为编码间隔的长度,LowRange(symbol)和HighRange(symbol)分别代表为了事件symbol分配的初始间隔下边界和上边界。算术编码也是一种对错误很敏感的编码方法,如果有一位发生错误就会导致整个消息译错。
算术编码可以是静态的或者自适应的。在静态算术编码中,信源符号的概率是固定的。在自适应算术编码中,信源符号的概率根据编码时符号出现的频繁程度动态地 进行修改,在编码期间估算信源符号概率的过程叫做建模。需要开发动态算术编码的原因是因为事先知道精确的信源概率是很难的,而且是不切实际的。当压缩消息 时,我们不能期待一个算术编码器获得最大的效率,所能做的最有效的方法是在编码过程中估算概率。因此动态建模就成为确定编码器压缩效率的关键。

二. 算数编码过程

假设有信源符号{A, B. C, D}, 对应的概率和初始编码间隔如下表所示

符号ABCD
概率01.0.40.20.3
初始编码间隔[0, 0.1)[0.1, 0.5)[0.5, 0.7)[0.7, 1)

如果二进制消息序列的输入为:CADACDB。编码时首先输入的符号是C, 找到他的编码范围是[0.5, 0.7)。由于消息由于消息中第二个符号A的编码范围是[0, 0.1),因此它的间隔就取[0.5, 0.7)的第一个十分之一作为新间隔[0.5, 0.52)。依此类推,编码第3个符号D时取新间隔为[0.514, 0.52),编码第4个符号A时,取新间隔为[0.514, 0.5146),…。消息的编码输出可以是最后一个间隔中的任意数
编码过程如下表所示
表3.2 算数编码的编码过程

步骤输入符号编码间隔编码判决
1C[0.5, 0.7]符号的间隔范围[0.5, 0.7]
2A[0.5, 0.52][0.5, 0.7]间隔的第一个1/10
3D[0.514, 0.52][0.5, 0.52]间隔的最后一个1/10
4A[0.514, 0.5146][0.514, 0.52]间隔的第一个1/10
5C[0.5143, 0.51442][0.514, 0.5146]间隔的第五个1/10开始,二个1/10
6D[0.514384, 0.51442][0.5143, 0.51442]间隔的最后3个1/10
7B[0.5143836,0.514402][0.514384,0.51442]间隔的4个1/10,从第1个1/10开始
8从[0.5143876, 0.514402]中选择一个数作为输出:0.5143876

译码过程如下表
表3.3 算数编码的译码过程

步骤间隔译码符号译码判决
1[0.5, 0.7]C0.51439在间隔 [0.5, 0.7)
2[0.5, 0.52]A0.51439在间隔 [0.5, 0.7)的第1个1/10
3[0.514, 0.52]D0.51439在间隔[0.5, 0.52)的第7个1/10
4[0.514, 0.5146]A0.51439在间隔[0.514, 0.52]的第1个1/10
5[0.5143, 0.51442]C0.51439在间隔[0.514, 0.5146]的第5个1/10
6[0.514384, 0.51442]D0.51439在间隔[0.5143, 0.51442]的第7个1/10
7[0.51439,0.5143948]B0.51439在间隔[0.51439, 0.5143948]的第1个1/10
8译码的消息:C A D A C D B

三. 算数编码的实现

这几种算法唯独算数编码我没有用C++实现,当时记得为了应付课堂作业,借用了网上一位博友的代码,大家如果想借鉴这个算法实现的代码,我这里可以给出我更改后的版本,但并不是我原创的,但是很抱歉具体是在哪里借鉴的我忘记了,当时比较草率,没有想太多,这里我还是把过程及测试结果给大家介绍清楚,代码的话如果能找到原主人最好不过了,这里我给出我改过的版本。
算数编码我只是使用字符串进行了测试,不能做到像Huffman编码一样对任何类型的文件都进行编码和译码。
主函数

int main()
{
    string str; //输入要编码的String类型字符串
    int number = 0, size = 0; //number--字符串中不重复的字符个数;size--字符串长度 
    char c[N]; //用于存储不重复的字符
    long double p[N], output; //p[N]--不重复字符的概率,output--编码结果 
    disp();
    cout << "输入要编码的字符串:";
    getline(cin, str); //输入要编码的字符串
    size = str.length(); //字符串长度
    number = proba(str, c, p, size);//调用求概率函数,返回不重复字符的个数
    cout.setf(ios::fixed); //“魔法配方”规定了小数部分的个数
    cout.setf(ios::showpoint); //在此规定编码结果的小数部分有十个
    cout.precision(10);//调用编码函数,返回编码结果
    output = bma(c, p, str, number, size);//调用译码函数,输出要编码的字符串,
    yma(str, c, p, number, size, output); //以验证编码是否正确   
    getchar();
    return 0;
}

特殊结构和功能函数的定义

#define N 100 //输入的字符应该不超过50个
struct L //结构用于求各字符及其概率
{
    char ch; //存储出现的字符(不重复)
    int num; //存储字符出现的次数
    double f;//存储字符的概率
};
//显示信息
void disp();
//求概率函数,输入:字符串;输出:字符数组、字符的概率数组;返回:数组长度; int proba(string str,char c[],long double p[],int count);
//求概率的辅助函数
int search(vector<L> arch, char, int n);
long double bma(char c[], long double p[], string str, int number, int size);
int proba(string str, char c[], long double p[], int count);
//编码函数,输入:字符串,字符数组,概率数组,以及数组长度;输出:编码结果 long double bma(char c[],long double p[],string str,int number,int size);
//译码函数,输入:编码结果,字符串,字符数组,概率数组,以及它们的长度;输出:字符串
//该函数可以用于检测编码是否正确
void yma(string str, char c[], long double p[], int number, int size, long double input);

void disp()

{

    cout << endl;
    cout << "此程序只需要输入要编码的字符串,不需要输入字符概率\n";
    cout << endl;

}

//求概率函数

int proba(string str, char c[], long double p[], int count)

{

    cout.setf(ios::fixed); //“魔法配方”规定了小数部分位数为三位
    cout.setf(ios::showpoint);
    cout.precision(3);
    vector<L>pt; //定义了结构类型的向量,用于同时存储不重复的字符和其概率
    L temp; //结构类型的变量
    temp.ch = str[0]; //暂存字符串的第一个字符,它的个数暂设为1
    temp.num = 1;
    temp.f = 0.0;
    pt.push_back(temp); //将该字符及其个数压入向量
    for (int i = 1; i<count; i++)//对整个字符串进行扫描

    {
        temp.ch = str[i]; //暂存第二个字符
        temp.num = 1;
        temp.f = 0.0;
        for (int j = 0; j<pt.size(); j++) //在结构向量中寻找是否有重复字符出现
        { //若重复,该字符个数加1,并跳出循环 
            int k; //若不重复,则压入该字符,并跳出循环 
            k = search(pt, str[i], pt.size());
            if (k >= 0)

            {
                pt[k].num++;
                break;
            }
            else
            {
                pt.push_back(temp);
                break;
            }

        }

    }
    for (int i = 0; i<pt.size(); i++) //计算不重复字符出现的概率
    {
        pt[i].f = double(pt[i].num) / count;
    }
    int number = pt.size(); //计算不重复字符出现的次数
    cout << "各字符概率如下:\n";
    for (int i = 0; i<number; i++) //显示所得的概率,验证是否正确
    {
        if (count == 0)
        {
            cout << "NO sample!\n";
        }
        else
        {
            c[i] = pt[i].ch;
            p[i] = pt[i].f;
            cout << c[i] << "的概率为:" << p[i] << endl;
        }
    }
    return number; //返回不重复字符的个数
}
//求概率的辅助函数
//若搜索发现有重复字符返回正数
//否则,返回-1
int search(vector<L> arch, char ch1, int n)
{
    for (int i = 0; i<n; i++)
    {
        if (ch1 == arch[i].ch) return i;
    }
return -1;
}
//编码函数
long double bma(char c[], long double p[], string str, int number, int size)
{
    long double High = 0.0, Low = 0.0, high, low, range;
    //High--下一个编码区间的上限,Low--下一个编码区间的下限;
    //high--中间变量,用来计算下一个编码区间的上限;
    //low--中间变量,用来计算下一个编码区间的下限;
    //range--上一个被编码区间长度
    int i, j = 0;
    for (i = 0; i<number; i++)
    {
        if (str[0] == c[i]) break; //编码第一个字符
    }
    while (j<i)
    {
        Low += p[j++]; //寻找该字符的概率区间下限
    }
    range = p[j]; //得到该字符的概率长度
    High = Low + range; //得到该字符概率区间上限
    for (i = 1; i<size; i++) //开始编码第二个字符
    {
        for (j = 0; j<number; j++) //寻找该字符在c数组中的位置
        {
            if (str[i] == c[j])
            {
                if (j == 0) //若该字符在c数组中的第一个字符
                {
                    low = Low; //此时该字符的概率区间下限刚好为零 
                    high = Low + p[j] * range;
                    High = high;
                    range *= p[j]; //求出该字符的编码区间长度
                }
                else //若该编码字符不是c数组中的第一个 
                {
                    float proba_next = 0.0;
                    for (int k = 0; k <= j - 1; k++)
                        proba_next += p[k]; //再次寻找字符的概率区间下限
                    low = Low + range*proba_next; //编码区间下限 
                    high = Low + range*(proba_next + p[j]);//编码区间上限
                    Low = low; //编码区间下限 
                    High = high; //编码区间上限 
                    range *= p[j]; //编码区间长度
                }
            }
            else continue; //i++,编码下一个字符 
        }
    }
    cout << endl;
    cout << "输入字符串的编码为:" << Low << endl;
    return Low;
}
//译码函数
void yma(string str, char c[], long double p[], int number, int size, long double input)
{
    vector<char> v; //定义char类型向量v
    long double temp; //中间变量
    long double sum[N]; //存储不重复字符概率区间的下限
    sum[0] = 0.0; //数组第一个元素为0
    for (int i = 1; i<number + 1; i++) //计算数组各元素的值
    {
        sum[i] = sum[i - 1] + p[i - 1];
    }
    for (int j = 0; j<size; j++)
    {
        for (int k = 0; k<number; k++)
        {
            //确定被编码字符的下限属于【0,1】之间的哪一段 
            if ((input>sum[k]) && (input<sum[k + 1])) //发现在哪就将属于该段的字符压入向量v
            {
                v.push_back(str[j]);
                temp = (input - sum[k]) / (sum[k + 1] - sum[k]);//计算下一个被编码字符的下限 
                input = temp;
                break;
            }
            else
                continue;
        }
    }
    cout << endl;
    cout << "译码输出为:"; //将译码结果输出
    for (int m = 0; m<v.size(); m++)
    {
        cout << v[m];
    }
    cout << endl;
}

第四章:LZ编码的实现之LZ-78编码

一. LZ-78编码原理

经过查找资料,LZ编码并不是一种编码,而是一组编码,由LZ-77和LZ-78演变而来有很多种变形。所以这里我只选取了一种比较简单的算法LZ-78算法。
LZ-78编码算法是一种分段编码算法。算法的压缩过程非常简单。在压缩时维护一个动态词典Dictionary,其包括了历史字符串的index与内容。设信源符号集 A=a0,a1,a2...aq1 共q个字符,设输入信源序列为

S1S2...Sn

编码就是将此序列分成不同的X段。分段的原则是:
1. 先取第一个符号作为第一段,然后再继续分段
2. 若出现有与前面相同符号时,就在添加进跟随后面的一个符号一起组成一个段,以使与前面的段不同。
3. 尽可能取最少个连通着的信源符号,并保证隔断都不相同。直至信源符号徐立结束。
这样,不同的段内的信源符号可看成一短语,可得不同段所对应的短语字典表。若编成二元码,段号用二进制表示,段号所需码长为 l=logX(n) ,X(n)是段号数。

二. LZ-78编码过程

LZ-78编码在压缩时维护一个动态词典Dictionary,其包括了历史字符串的index与内容;压缩情况分为三种:
1. 若当前字符c未出现在词典中,则编码为(0, c);
2. 若当前字符c出现在词典中,则与词典做最长匹配,然后编码为(prefixIndex,lastChar),其中,prefixIndex为最长匹配的前缀字符串,lastChar为最长匹配后的第一个字符;
3. 为对最后一个字符的特殊处理,编码为(prefixIndex,)
举例
以字符串“ABBCBCABABCAABCAAB”压缩编码构造字典的过程如下
LZ-78编码
1. A 不在字典中; 插入A
2. B不在字典中; 插入B
3. B 在字典中.
BC 不在字典中; 插入BC
4. B在字典中.
BC在字典中.
BCA不在字典中.;插入BCA
5. B在字典中.
BA不在字典中; 插入BA.
6. B在字典中.
BC在字典中.
BCA在字典中.
BCAA不在字典中;插入BCAA
7. B在字典中.
BC在字典中.
BCA在字典中.
BCAA在字典中.
BCAAB 不在字典中; 插入BCAAB.

三. LZ-78编码编程实现和性能分析

LZ-78算法我构建了一个LZ78类。类的定义如下

class LZ78
{
public:
    struct Dictionary
    {
        unsigned int Index;
        int preIndex;
        unsigned char lastChar;
        vector<unsigned char> stringInDic;
    };
public:
    struct OutData
    {
        unsigned int preIndex;
        unsigned char lastChar;
    };
public:
    string fileAddress;
    LZ78();   //构造函数
    void open(string);
    void Press();
    void Decode(string sourcefile, string dstfile);
private:
    bool IfStringInDic(vector<Dictionary> CurrentString, vector<unsigned char> DataDic, unsigned int &Index);
private:
    vector<unsigned char> FindPreString(vector<Dictionary> DataDic, unsigned int);
};

核心属性
1. struct Dictionary: 存储字典信息
2. struct OutData: 存储输出信息即上面所提到的二元组(preIndex, lastChar);
3. string fileAddress: 需要压缩的文件的路径名称
核心函数:
1. void open(string address) : 打开待压缩文件
2. void Press(): 压缩文件操作
3. void Decode(string sourcefile, string dstfile): 解码操作
正式操作见下面主函数:
主函数:

int main()
{
    LZ78 haha;
    clock_t start, end;
    start = clock();
    haha.open("./KongFu.jpg");  //打开文件
    haha.Press();   //压缩文件
    end = clock();
    cout << "压缩文件用时:" << endl << endl;
    cout << double((end - start) / CLOCKS_PER_SEC) << "/s" << endl << endl;
    start = clock();
    LZ78 nothaha;
    nothaha.Decode("./KongFu.jpg.lz", "KongFuout.jpg");
    cout << "解压用时:" << endl << endl;
    cout << double((start - end) / CLOCKS_PER_SEC) << "/s" << endl << endl;
    getchar();
}

文件压缩步骤
第一步:建立haha对象为LZ78类型
第二步:打开待压缩的文件
第三步:压缩文件
第四部:压缩结束
文件解压步骤
第一步:建立nothaha对象为LZ78类型
第二步:解压文件
第三步:解压结束
LZ-78编码的性能测试见下表
表4.1 LZ-78编码性能测试

原始文件890Bytes(文本)46.7kb(图像)7.82M(视频)
压缩文件3133Bytes47.9kb7.81M
压缩率1.291.02599.8%
压缩用时6/s20/s/
解码用时0/s0/s/

看到自己程序跑的结果自己都想笑,这也太慢了点了,有时候想安慰一下自己,可能是C++读取文件的API很耗时,要想速度快,有朝一日自己写读取文件的API,励志。
最后附上LZ-78程序的C++源代码

LZ78.h

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
class LZ78
{
public:
    struct Dictionary
    {
        unsigned int Index;
        int preIndex;
        unsigned char lastChar;
        vector<unsigned char> stringInDic;
    };
public:
    struct OutData
    {
        unsigned int preIndex;
        unsigned char lastChar;
    };
public:
    string fileAddress;
    LZ78();   //构造函数
    void open(string);
    void Press();
    void Decode(string sourcefile, string dstfile);
private:
    bool IfStringInDic(vector<Dictionary> CurrentString, vector<unsigned char> DataDic, unsigned int &Index);
private:
    vector<unsigned char> FindPreString(vector<Dictionary> DataDic, unsigned int);
};

LZ78::LZ78()
{

}
void LZ78::open(string input)
{
    fileAddress = input;
}

void LZ78::Press()
{
    ifstream read;
    read.open(fileAddress, ios::in|ios::binary);
    if (!read )
    {
        cout << "文件读取错误" << endl << endl;
        return;
    }
    ofstream write;
    write.open(fileAddress + ".lz", ios::out|ios::binary);
    if (!write)
    {
        cout << "输出文件不能建立(*.lz)" << endl << endl;
    }

    unsigned char *firstchar = new unsigned char;
    read.read((char*)firstchar, sizeof(unsigned char));
    vector<Dictionary> DataDic;    //建立字典
    while (!read.eof())
    {

        if (DataDic.size() == 0)
        {
            Dictionary firstDic;
            OutData *firstout = new OutData;
            firstDic.Index = 1;
            firstDic.preIndex = 0;
            firstDic.lastChar = *firstchar;
            firstDic.stringInDic.push_back(*firstchar);
            DataDic.push_back(firstDic);
            firstout->lastChar = *firstchar;
            firstout->preIndex = 0;
            write.write((char*)firstout, sizeof(OutData));
        }
        else
        {
            unsigned char *now = new unsigned char; //用于读取的字符
            unsigned char *reallast = new unsigned char;
            vector<unsigned char> CurrentString;
            unsigned int index = 0;  //字符串存在在字典中的位置, 初始设为0
            Dictionary currentInfo;   //存储当前的单词到字典中
            OutData *currentOutdata = new OutData;  //存储当前编码信息,一会压缩进如压缩文件。

            int EOFflag = 0;
            do
            {
                read.read((char*)now, sizeof(unsigned char));
                if (read.eof())
                {
                    EOFflag = 1;  //标记是否到文件的结尾
                    break;
                }
                else
                {
                    CurrentString.push_back(*now);
                }
            } 
            while (IfStringInDic(DataDic, CurrentString, index));


            if (EOFflag == 1)
            {
                if (CurrentString.size() == 0)
                {
                    break;  //如果当前字符串中没有字符,直接跳出循环
                }
                else
                {     //如果当前字符串中有字符,对这段字符进行压缩。
                    *reallast = CurrentString[CurrentString.size() - 1];
                    CurrentString.erase(CurrentString.end() - 1);
                    IfStringInDic(DataDic, CurrentString, index);

                }

            }
            else
            {
                *reallast = *now;
            }
            currentInfo.Index = DataDic.size() + 1;
            currentInfo.lastChar = *reallast;
            currentInfo.preIndex = index;
            currentInfo.stringInDic = CurrentString;
            DataDic.push_back(currentInfo);

            currentOutdata->lastChar = *reallast;
            currentOutdata->preIndex = index;
            write.write((char*)currentOutdata, sizeof(OutData));

        }

    }

    read.close();
    write.close();
}

bool LZ78::IfStringInDic(vector<Dictionary> DataDic, vector<unsigned char> CurrentString, unsigned int &Index)
{
    int flag = 0;
    for (int i = 0; i < DataDic.size(); i++)
    {
        if (CurrentString == DataDic[i].stringInDic)
        {
            Index = DataDic[i].Index;
            flag = 1;
            return true;
        }

    }
    if (flag == 0)
    {
        return false;
    }
}


void LZ78::Decode(string sourcefile, string dstfile)
{
    ifstream readfile;
    ofstream putfile;
    readfile.open(sourcefile, ios::in | ios::binary);
    putfile.open(dstfile, ios::out | ios::binary);
    OutData *getdata = new OutData;
    readfile.read((char*)getdata, sizeof(OutData));
    vector<Dictionary> DataDic;    //建立字典
    Dictionary *spacefirst = new Dictionary;
    spacefirst->Index = 0;
    spacefirst->lastChar = '0';
    spacefirst->preIndex = 0;
    //spacefirst->stringInDic
    DataDic.push_back(*spacefirst);
    while (!readfile.eof())
    {
        Dictionary *now = new Dictionary;
        now->lastChar = getdata->lastChar;
        now->Index = DataDic.size();
        now->preIndex = getdata->preIndex;
        vector<unsigned char> preString;  //存储前一个的字符串
        if (now->preIndex != 0)
        { //如果preIndex等于0那么此字符是新出现的字符,否则在前面字典中找
            preString = FindPreString(DataDic, now->preIndex);
        }
        preString.push_back(now->lastChar);  
        now->stringInDic = preString;  //获取此单词的字符串。
        DataDic.push_back(*now);
        for (int i = 0; i < preString.size(); i++)
        {
            putfile.write((char*)&preString[i], sizeof(unsigned char));
        }
        readfile.read((char*)getdata, sizeof(OutData));

    }
    readfile.close();
    putfile.close();
}

vector<unsigned char>  LZ78::FindPreString(vector<Dictionary> DataDic, unsigned int preindex)
{
    if (DataDic.size() < 1)
    {
        cout << "不能找到前一个字符串" << endl;
    }
    else
    {
        for (int i = 0; i < DataDic.size(); i++)
        {
            if (preindex == DataDic[i].Index)
            {
                return DataDic[i].stringInDic;
            }
        }
    }
}

LZ-78Main.cpp

#include "LZ78.h"
#include <string>
#include <time.h>
int main()
{
    LZ78 haha;
    clock_t start, end;
    start = clock();
    haha.open("./KongFu.jpg");  //打开文件
    haha.Press();   //压缩文件
    end = clock();
    cout << "压缩文件用时:" << endl << endl;
    cout << double((end - start) / CLOCKS_PER_SEC) << "/s" << endl << endl;
    start = clock();
    LZ78 nothaha;
    nothaha.Decode("./KongFu.jpg.lz", "KongFuout.jpg");
    cout << "解压用时:" << endl << endl;
    cout << double((start - end) / CLOCKS_PER_SEC) << "/s" << endl << endl;
    getchar();
}

总结

经过此次试验真的是收获了很多,首先是对各种编码的熟悉,从Huffman编码、Shannon Fano码、算数编码到LZ编码家族,了解了Zip,winrar等压缩软件的压缩原理,以及压缩算法从兴起到应用,里面还有一些版权纠纷的故事(LZW版权问题)。到现在的操作系统使用的压缩软件的情况,Windows主要的软件是winrar和zip, linux系统下主要是.gz和gzip软件。其原理都是基于DELLATE算法的。DELLATE算法是以Huffman编码和LZW编码为核心的。另一方面,从信息论角度细致的学习了各种编码的编码原理和译码方式,这些收获相信在今后的学习中都会有用的。这次实验不仅锻炼了编程能力,而且学习到很多编码领域的新知识。

参考文献
《信息论与编码》傅祖芸、赵建中 电子工业出版社
C++实现Huffman文件编码和解码:http://www.cnblogs.com/matrix-r/p/3354887.html
zip压缩原理 http://blog.jobbole.com/76676/
Huffman对文件编码和解码http://blog.csdn.net/computer_liuyun/article/details/41446773
huffman编码原理与实现:http://blog.csdn.net/abcjennifer/article/details/8020695
二进制和ASCII编码: http://blog.csdn.net/sxhelijian/article/details/29594687
压缩编码的历史:http://blog.csdn.net/kimylrong/article/details/39405981
Lz78算法:http://www.cnblogs.com/aeexiaoqiang/p/6529375.html
算数编码: http://blog.csdn.net/adam_tu/article/details/7696455

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

信息论实验-信源编码2(Lz编码和算数编码的C++实现) 的相关文章

  • FreeRTOS基础二:堆内存管理之heap_4方案

    背景知识 从FreeRTOS V9 0 0内核版本开始 xff0c 内核对象占用的内存可以在编译期间静态分配 xff0c 也可以在运行期间动态分配 内核对象如 xff1a tasks xff08 任务 xff09 xff0c queues
  • MQ与Webservice的区别

    Webservice 和MQ MessageQueue 都是解决跨平台通信的常用手段 xff0c 两者有哪些区别呢 xff1f 个人认为最本质的区别在于 Webservice近乎实时通信 xff0c 而MQ却通常是延时通信 什么意思呢 xf
  • uORB通信机制和添加自己的topics学习笔记

    本文参考 Ubuntu16 04下PX4环境快速搭建及uORB通信机制进行操作 结合网上一篇uROB的介绍PX4 Pixhawk uORB深入理解和应用进行深入的了解 1 简介 uORB Micro Object Request Broke
  • 看到一篇很好的介绍磁力计原理的博客

    详细内容戳这里http blog sina com cn s blog 402c071e0102v8ig html
  • QT工程转换为VS2013项目文件

    xff08 win7系统 xff09 1 点击开始 xff0c 输入cmd xff0c 打开cmd 2 输入cd c apm点击回车键 注 xff1a apm是我的qt的工程文件夹 xff0c 最好放在c盘 xff0c 其他盘转换都不成功
  • C语言:函数返回字符串的四种方法

    转载连接 xff1a 1 https blog csdn net turkeyzhou article details 6104135 comments 2 https www cnblogs com qingergege p 649668
  • C语言:字符串中查找指定字符——strchr()和strrchr()

    参考文章连接 xff1a 1 http c biancheng net cpp html 161 html 2 http c biancheng net cpp html 172 html 1 头文件 xff1a include lt st
  • C语言:整型、浮点型和字符串间转换

    参考文章链接 xff1a 1 http c biancheng net cpp html 1573 html 2 http c biancheng net cpp html 1574 html 1 整型 浮点型 gt 字符串 整数转换为字符
  • 学习贵在坚持!

    最近学习颇为不顺 xff0c 周围都是一些不利的消息 xff0c 有些心灰意冷 可是这不是与我写本文的初衷相悖了么 xff1f 看到了比自己优秀的人干出来辉煌的事情 xff0c 写出来文字优美的文章 xff0c 自己就有相形见绌的自卑感 可
  • Qt中 QString 和int, char等的“相互”转换

    原文链接 xff1a https blog csdn net ei nino article details 7297791 Qt中 int float double转换为QString 有两种方法 1 使用QString number 如
  • 计算器第二版:C语言,VC++6.0

    使用栈实现 xff0c 前缀表达式变后缀表达式的原理 xff0c 但是没有转换 xff0c 是边转换边实现 xff1a include lt stdio h gt include lt stdlib h gt include lt coni
  • 计算器第三版:C语言,递归,VC++6.0

    参考文章 xff1a https blog csdn net u011692041 article details 49796343 https blog csdn net u011692041 article details 497991
  • 计算器第四版:C++,QT

    核心算法和第二版一样 xff1a 头文件 xff1a calculate h ifndef CALCULATE H define CALCULATE H include lt QMainWindow gt include lt QPushB
  • USB协议概念学习

    1 USB总线结构 usb的总线拓扑结构如下所示 xff1a 从USB总线结构可以看出 xff0c 主要由3部分组成 xff1a USB主机 Host USB线缆 USB设备 hub Func等 USB主机 xff1a 一般成为USB Ho
  • 创新工场两道笔试题0919

    题目1 字符串去重 xff0c 老题目 xff0c 只是要求不能开辟新空间用来复制原字符串 思路 xff1a 使用布尔型的简单hash表可以节省空间 xff0c 用来存储字符是否出现的信息 xff0c 刚开始hash表里面都是false x
  • ROS仿真机器人学习笔记二:创建4轮小车模型及相关xraco文件修改

    系列文章目录 提示 xff1a 这里可以添加系列文章的所有文章的目录 xff0c 目录需要自己手动添加 例如 xff1a 第一章 Python 机器学习入门之pandas的使用 提示 xff1a 写完文章后 xff0c 目录可以自动生成 x
  • 旧电脑升级Windows11时检查CPU和TPM2.0不满足的解决方案(慎重)

    上个月微软发布了Windows11 22H2正式版 xff0c 不少新电脑也接收到了推送 xff0c 楼主的台式 xff08 i3 8100 军规星H310M xff09 也接收到了推送 xff0c 但是碍于Win11蛋疼的右键和状态栏消息
  • windows下安装docker

    windows下安装docker 0 前置条件 环境说明 xff1a windows11 家庭中文版 开启Hyper V xff08 可以百度如何开启 xff09 如何添加Hyper V 创建hyper txt xff0c 复制如下内容 x
  • STM32CubeMX配置生成FreeRTOS项目

    文章目录 1 安装STM32CubeMX软件1 1 下载安装1 2 安装要用到的芯片软件包 2 配置FreeRTOS项目2 1 创建工程2 2 配置SYS2 3 配置RCC2 4 配置系统运行时钟2 5 配置UART1串口作为调试代码2 6
  • ScrumMaster的教练职责

    ScrumMaster是Scrum团队的敏捷教练 Ken Rubin说 xff0c 类似于运动团队的教练 xff0c ScrumMaster观察团队使用Scrum的过程 xff0c 帮助团队提高工作绩效 教练不是顾问 xff0c 不提供解决

随机推荐

  • Autoware.Auto avp仿真详解

    1 定位 定位节点启动的是 ndt localizer 61 Node package 61 39 ndt nodes 39 executable 61 39 p2d ndt localizer exe 39 namespace 61 39
  • VMware + ubuntu16.04 + ROS kinetic 下配置realsense D435i 遇到的问题

    在配置Realsense D435i 的过程中 xff0c 遇到一个问题 执行 scripts patcg realsebse ubuntu lts sh 下载速度奇慢 10K s左右 而且会在接受到36 的时候不动了 xff0c 等了一晚
  • 白话tensorflow分布式部署和开发

    关于tensorflow的分布式训练和部署 xff0c 官方有个英文的文档介绍 xff0c 但是写的比较简单 xff0c 给的例子也比较简单 xff0c 刚接触分布式深度学习的可能不太容易理解 在网上看到一些资料 xff0c 总感觉说的不够
  • 全息投影技术

    1 概念 全息投影技术 xff08 front projectedholographic display xff09 也称 虚拟成像 技术是利用干涉和衍射原理记录并再现物体真实的 三维 图像的技术 全息投影技术不仅可以产生立体的空中幻像 x
  • Ardupilot飞控添加使用诺瓦泰GPS

    Ardupilot飞控添加使用诺瓦泰双天线GPS航向角的设置 一 添加诺瓦泰GPS heading角数据包解析代码 1 打开libraries AP GPS AP GPS NOVA h xff0c 添加如下代码 xff1a struct P
  • SD标准以及规范

    SD标准及规范 SD应用 SD标准让制造商能生产高性能之产品来提升数百万计消费者的体验 xff0c 包含听音乐 录制视频 摄影 数据储存以及使用移动电话 身为一个产业的标准 xff0c SD标准被用于行动存储产业的多个市场领域中 xff0c
  • 《视觉SLAM十四讲》学习笔记-状态估计问题

    最大后验与似然 经典slam模型可表示为 xff1a x k 61 f x k 1 u k 43 w k z k j 61 h y j x k 43 v
  • 机器人学领域的顶级期刊和会议

    印象中 xff0c 机器人学涉及机械 控制 计算机和电子等领域 xff0c 十足的交叉学科 xff0c 所以涉及到的概念和技术也非常多 因工作关系 xff0c 看了三周的SLAM入门 xff0c 用的是高翔的 视觉SLAM十四讲 这本教材
  • SQL解析过程

    转载自 xff1a http blog aliyun com 733 简介 SQL任务是ODPS中使用最频繁的一类作业 大部分用户开始使用ODPS时要做的第一件事情就是学习怎么写ODPS的SQL ODPS SQL是一种非常灵活的语言 兼容大
  • C++中类所占的内存大小以及成员函数的存储位置

    类所占内存的大小是由成员变量 xff08 静态变量除外 xff09 决定的 xff0c 虚函数指针和虚基类指针也属于数据部分 xff0c 成员函数是不计算在内的 因为在编译器处理后 xff0c 成员变量和成员函数是分离的 成员函数还是以一般
  • 用于异常检测的深度神经网络模型融合

    用于异常检测的深度神经网络模型融合 在当今的数字时代 xff0c 网络安全至关重要 xff0c 因为全球数十亿台计算机通过网络连接 近年来 xff0c 网络攻击的数量大幅增加 因此 xff0c 网络威胁检测旨在通过观察一段时间内的流量数据来
  • STM32CubeIDE构建通用freertos项目(一)

    感慨 本人大约三四年没有碰单片机了 xff0c 遥想当年我还是用的keil工具 有幸以援助的身份介入公司的嵌入式项目 xff0c 结合自身经验讲讲 工作是一个长期的过程 xff0c 开头不注意则会产生蝴蝶效应 xff0c 导致接下来的工作一
  • CMake中CMakeLists文件的编写以及变量打印

    最近在学习PCL xff0c 在Win10下使用VS编写PCL程序 xff0c 配置环境时经常出错 xff0c 踩坑记录详见 xff1a Win10 43 VS2017 43 PCL 1 8 1软件安装 踩坑记录 看到 点云库PCL从入门到
  • 如何离线安装python包

    在我们的日常使用python的过程中 xff0c 通常是通过联网安装相关的依赖包 xff0c 但是有时候会有一些情况是没有网络的 xff0c 但我们又需要安装python的各种包 而包的依赖导致我们很难一个一个地从pypi网站下载whl文件
  • mavros 环境配置注意事项[Resource not found: px4 ROS path ]

    背景 我最近开始使用mavros xff0c 按照官网的教程进行安装 采用二进制的方式安装 xff0c 一切进行的很顺利 xff0c 接下来我就按照PX4官网的去执行一个让无人机自动起飞的例子 xff0c 完全按照官网的代码和配置到最后一步
  • rosdep init ERROR: cannot download default sources list... 解决方法

    问题描述 如标题所示 xff0c 当我们安装好ROS后 xff0c 想要用rosdep初始化时 xff0c 会遇到ERROR cannot download default sources list from https raw githu
  • Linux 运行命令时修改.bashrc并结束命令时恢复原样

    问题来源 我有一个bash程序 xff0c 想要在执行该程序的时候修 bashrc xff0c 然后更新一些环境变量 xff0c 并在结束 ctrl 43 c 的时候再把程序恢复原样 操作如下 xff1a echo 命令把想要增加的内容写入
  • 数字信号处理实验(一)

    实验目的 本次实验目的为 xff1a 在matlab环境下产生几种基本的数字信号 xff0c 并对这些基本的信号进行运算和变换 xff0c 同时利用程序结果对采样定理进行验证 xff0c 深刻理解采样定理 通过自己录制音频信号并对不同的音频
  • 数字图像处理实验(二)

    实验目的 实验一 xff1a 图像增强 了解图像增强的目的及意义 xff0c 加深对图像增强的感性认知 1 掌握直接灰度变换的图像增强方法 2 掌握灰度直方图的概念及其计算方法 3 掌握直方图均衡化合直方图规定化得计算过程 实验二 xff1
  • 信息论实验-信源编码2(Lz编码和算数编码的C++实现)

    上一篇文章给出了Huffman编码和Shannon Fano编码的编码原理以及C 43 43 的程序 xff0c 程序可以用来实现给任意类型的文件进行无损压缩 xff0c 缺点是比较耗时 xff0c 不能作为正常的通用压缩软件来使用 xff