目录

  • 一、 问题描述与要求
  • 二、 需求分析
  • 三、 设计
    • 3.1 设计思想
      • 3.1.1 数据与操作的特性
      • 3.1.2 数据结构设计
      • 3.1.3 算法设计
    • 3.2 设计表示
      • 3.2.1 函数调用关系图
      • 3.2.2 类视图
    • 3.3 详细设计
      • 3.3.1 结点数据结构设计
      • 3.3.2 算法详细设计
  • 四、 调试分析
  • 五、 用户手册
  • 六、 测试数据及测试结果
    • 6.1 边界测试
    • 6.2 压力测试
  • 七、 此程序的亮点
  • 八、 源代码

一、 问题描述与要求

对于任意待编码的字符集(字符集中字符最少不能少于20个),并给定他们的出现概率,概率大小由编程输入。
要求:按照待编码的字符的概率生成Huffman树,并根据生成的Huffman树将待编码的字符进行Huffman编码,并输出。

二、 需求分析

根据题目要求,程序需要对字符串中每个字符出现的频率进行统计,得到统计表,用统计结果构造Huffman树,再对Huffman树进行遍历得到Huffman编码表,最后据此对待编码的明文编码成Huffman编码的密文。
以上需求涉及到字符串的预处理和统计,Huffman树的构造,二叉树的遍历,由遍历Huffman树的路径生成Huffman编码。
除此之外,还额外完成了解码功能的实现。

三、 设计

3.1 设计思想

3.1.1 数据与操作的特性

程序要从输入的字符串中统计出各字符出现的次数,其中多次涉及在已有统计表中的查找操作。
构建Huffman树时需要从上一步统计得来的表中选出最大和次大的两个元素,将两个子树合并,将合并后的根结点放回森林中,之后再在此森林中找到根节点最大和次大的两个子树再合并之后再放回。这个过程涉及树的合并,再放回去,找出最大和次大的算法。
遍历Huffman树得出Huffman编码的过程需要在遍历的同时记录在每个双亲结点处选择的方向,当遍历到叶子结点时输出路径作为Huffman编码。将明码字符和该字符的Huffman编码放入结构体数组中得到编码表
有了编码表,在对原文进行加密的过程中涉及到不断根据原码字符在编码表中查找对应的Huffman编码,查找次数和原文中字符次数相同,因此此处若采用顺序查找比较耗时,时间复杂度为O(n2)。相比之下,若采用折半查找法时间复杂度被优化为O(log2n)。所以此处采用折半查找法查找原文在编码表中对应的Huffman编码。

3.1.2 数据结构设计

根据以上分析,在统计字符串中每个字符出现的次数的过程中,每当扫描到字符串中的下一个字符,都不可避免地在已有的统计表中查找该字符,且查找非常频繁。因此可以考虑采用时间复杂度仅有O(log2n)的红黑树,可以大大提高统计中的查找效率。
从森林中找出最大和次大两个元素可以采用将两个子树合并后的新树插入原有有序序列,使其仍然是有序序列的方法。因为涉及到排序,而排序最小的时间复杂度为堆排序的O(nlog2n),如果用堆排序排好序再取结点合并后再插入的话还多一道插入的步骤,不如直接从最小堆里取结点,省掉了排序的过程,在数据量比较大的情况下,这种算法优势将非常明显。
在遍历Huffman树获得Huffman编码的过程中,考虑采用递归算法,用静态字符数组记录之前在每个双亲结点选择的方向,当抵达叶子结点时输出已经记录的路径作为该叶子结点的Huffman编码。
程序中因为涉及到多个表,所以有三个定义表结点的数据结构。字符数量统计表直接以Huffman树结点为其每个结点的结构,最小堆的数据结点也是Huffman树结点,最后译码表的结点内包含了一个HuffmanNode和一个存储编码的string。

3.1.3 算法设计

整个程序划分为输入输出的处理,字符统计,建立Huffman树,最小堆四大模块。
首先此程序不能处理中文,因为中文和英文字母不同,中文字符占两个字符,中英混合将给后续的统计和存储带来很大困难。因此该程序只处理英文字符串。所以,需要对输入的字符串进行遍历的同时检查是否含有非法字符。
由于建立Huffman树的过程涉及到多次在Huffman类和最小堆MinHeap类之间的数据交换,即把子树从最小堆中取出,合并后再放入的操作。最小堆采用结构体数组实现,堆内的每个结点无法装下整个合并后的子树,也不能只装合并后的新根节点,因为之后还会取出用于构造新的树。因此考虑使最小堆内的每个结点不存放子树本身,而是存放指向统计表中结点的指针。由此很方便地实现子树的取出,合并,再放回最小堆。
Huffman树被建立之后,用递归的方法对Huffman树进行遍历,得出Huffman编码。在函数中定义一个静态字符数组存储Huffman编码,使得下一层递归可以直接接着上一层递归继续编码,遇到叶子结点则在存储Huffman编码的字符串末尾加结束符号‘\n’,并赋给编码表中的相应单元。
结束递归之后,再用间接排序法(又称索引表法,表排序法)对存放Huffman编码表的结构体数组按照字母顺序降序进行排列,方便之后的折半查找。之所以用索引表法是因为结构体数组体积较大,而排序又涉及到结构体变量的不断赋值和移动,时间开销大。所以用间接排序法,排序过程中移动的是结构体变量在结构体数组中的下标,排序完后按照下标一次性将结构体变量移到新表中,构成有序表。
由于编码的过程涉及到不断根据原文字符到编码表中找对应的Huffman编码的过程,且次数非常频繁,因此此处不采用顺序查找法而是采用折半查找法,大大优化了时间复杂度。
解码过程直接基于Huffman树,而不是在编码表中根据密码查原文。解码时,密码是1则Huffman树中的指针指向当前节点的左子节点,密码是0则指向当前节点的右节点,直到抵达叶子结点,输出该叶子结点的字符,指针重置为根节点,如此循环,直到密码结束。

3.2 设计表示

3.2.1 函数调用关系图

3.2.2 类视图

3.3 详细设计

3.3.1 结点数据结构设计

由于程序中涉及到字符的统计,给每个字符编码,构建编码表。因此涉及到两结构体作为表的元组。程序中有Huffman结点和编码表结点两个结构体。
Huffman树结点的结构用伪代码可以表示成如下形式:

Huffman树结点
{单个原文字符;权值;左结点指针;右结点指针;构造函数和运算符重载;
}

Huffman结点是Huffman树的结点结构和字符统计结果表的元素。
tableNode是Huffman编码表中每个元组的结构,其类结构用伪代码表示如下

tableNode
{字符;权重;Huffman编码;等号重载;
}

3.3.2 算法详细设计

因为堆是完全二叉树,所以堆采用数组的形式存储会比较方便。如果一个结点的下标为i,那么其左子树下标为2i+1,右子树为2i+2,这样很方便能找到每个结点的子树。最小堆的建立的基本思想是将结点一个一个插入堆,插入的同时做调整,最终把所有结点都插入,形成最小堆。从堆中取元素也类似,最小堆的堆顶元素最小,取走之后删除该结点,然后再做调整。
我们采用从上到下逐步调整形成堆的方法,调用下滑调整算法,siftDown,将以它们为根的子树调整为最小堆,从局部到整体,将最小堆逐步扩大,直到整个树成为最小堆。
下面的代码截图包含了详细的注释,可以较为清楚的描述此算法。

由数组建立最小堆:

MinHeap::MinHeap(HuffmanNode* arr, int n)
{//由数组建立最小堆maxHeapSize = (DefaultSize < n) ? n : DefaultSize;heap = new HuffmanNode*[n];//分配空间if (heap == NULL){cout << "空间分配失败" << endl;exit(1);}for (int i = 0;i < n;++i)//初始化堆{heap[i] = &arr[i];//将统计结果表中每个元素的地址放入堆中}currentSize = n;int currentPos = (currentSize - 2) / 2;//设置开始调整的位置为最后分支节点while (currentPos >= 0){siftDown(currentPos, currentSize - 1);//检查并调整这棵子树currentPos--;}
}

向下调整算法siftDown

void MinHeap::siftDown(int start, int m)
{/*从节点start开始到m为止,自下向上比较,如果子女的值小于父结点的值,则关键码小的上浮,继续向下层比较,这样将一个集合局部调整为最小堆*/int i = start, j = 2 * i + 1;HuffmanNode* temp = heap[i];while (j <= m){if (j<m && heap[j]->weight>heap[j + 1]->weight)j++;//让j指向两子女中的最小者if (temp->weight <= heap[j]->weight)break;//小则不做调整else{//否则小者上浮,i,j下降heap[i] = heap[j];i = j;j = 2 * j + 1;}}heap[i] = temp;
}

向上调整算法:

void MinHeap::siftUp(int start)
{/*从结点start开始到结点0为止,自下向上进行比较,如果子女结点的值小于父结点的值则相互交换,这样将集合重新调整为最小堆*/int j = start, i = (j - 1) / 2;//i指向start的双亲结点HuffmanNode* temp = heap[j];while (j > 0)//沿着start的双亲结点向上直达根{if (heap[i]->weight <= temp->weight)break;//父结点值比插入的结点小,满足最小堆,不调整else{heap[j] = heap[i];//父子结点的值交换//上移一层j = i;i = (i - 1) / 2;}}heap[j] = temp;
}

插入算法:

void MinHeap::Insert(HuffmanNode*& x)
{if (currentSize == maxHeapSize){cout << "堆满" << endl;}heap[currentSize] = x;//插入siftUp(currentSize);//调整++currentSize;//堆计数+1
}

取堆顶

void MinHeap::GetMin(HuffmanNode*& x)
{if (!currentSize){cout << "堆空" << endl;}x = heap[0];heap[0] = heap[currentSize - 1];//用堆的最后一个最大的结点填补取走的currentSize--;siftDown(0, currentSize - 1);//调整
}

通常,从最小堆删除具有最小关键码记录的操作是将最小堆的堆顶元素,即完全二叉树的顺序表示的第0号元素删去。在把这个元素取走后,一般以堆的最后一个结点填补取走的堆顶元素,并将堆的实际元素个数减1。但是用最后一个元素取代堆顶元素将破坏堆,需要调用siftDown算法从堆顶向下进行调整。
Huffman树的构造思想是:用于构造树的n个结点组成森林,从中取前两个最小的结点组成新树,新树的根节点是两个叶结点权值之和,再将新树放回森林,再从森林中取根节点最小的两棵子树用同样的方法组成新树,再放回,循环往复直到森林中只剩一棵树为止。

HuffmanTree::HuffmanTree(string& s)//通过数组构造哈夫曼树
{int n = s.size();countResult(s, n);MinHeap heap(StatisticalResult,n);//建立装结点的最小堆HuffmanNode* parent = NULL;HuffmanNode* first; //存放根权值最小的子树树根HuffmanNode* second;//存放根权值次小的子树树根HuffmanCodeTable = new tableNode[n];tableSize = n;if (n == 1)//只有一个字符进行编码的情况{root = StatisticalResult;return;}for (int i = 0;i < n - 1;++i)//有n个结点,所以做n-1次合并{heap.GetMin(first);//选择根的权值最小的子树heap.GetMin(second);//选择根的权值次小的子树mergeTree(first, second, parent);//合并树heap.Insert(parent);//再插入堆}root = parent;
}

通过对树遍历得出Huffman编码,采用递归方法实现。

void HuffmanTree::GetHuffmanCode()
{_GetHuffmanCode(this->root, 0);//进入递归sortCodeTable();//对编码表排序,方便后面编码的时候折半查找
}void  HuffmanTree::_GetHuffmanCode(HuffmanNode* root, int depth)
{static char code[512];static int k = 0;tableNode temp;//临时//如果有左子树if (root->leftChild != NULL){code[depth] = '0';code[depth + 1] = '\0';_GetHuffmanCode(root->leftChild, depth + 1);}//如果有右子树if (root->rightChild != NULL){code[depth] = '1';code[depth + 1] = '\0';_GetHuffmanCode(root->rightChild, depth + 1);}else//都没有就是叶结点{temp.c = root->c;temp.weight = root->weight;temp.code = code;HuffmanCodeTable[k++] = temp;//存入表}
}

我在题目要求之外还额外实现了一个解码功能,解码算法的主要思想是:指向树结点的指针根据Huffman编码来选路,从根节点开始,如果当前编码是0就移向左子树,如果是1就移向右子树,当移到叶子结点时,输出叶子结点上的字符,同时该指针回到整棵树的根节点。如此循环往复,直到Huffman编码结束。
代码如下:

void HuffmanTree::decode(string& codedStr)
{string::const_iterator iter= codedStr.begin();//迭代器HuffmanNode* curNode = root;string decodedStr;while(iter != codedStr.end())//遍历整个密码字符串{if (*iter == '0')//如果当前编码为0,就移向左子树curNode = curNode->leftChild;if (*iter == '1')//如果当前编码为1,就移向右子树curNode = curNode->rightChild;if (curNode->rightChild == NULL || curNode->leftChild == NULL){//如果是叶子结点,就把明文字符加到明文字符串里decodedStr += curNode->c;curNode = root;//指针置回根结点}iter++;}cout << decodedStr<<endl;//循环完了就直接输出
}

四、 调试分析

这个程序的编写可谓费尽周折,远远超出了我想象的难度,我本来以为一天就能把程序写出来,结果写了四天。中间经历过两次代码重构,遇到了很多问题,经过很长时间的调试才解决。
印象中遇到的第一个比较大的问题是发现无法构建树,经过单步调试,监视根节点变量发现,构建出来的树只有两层。原因是当时我的最小堆直接存放的是树结点,这样树在合并之后,放入堆的就只有根节点,而下面的结点全部丢失了。我把堆中存放的对象从树结点结构体改成指向树节点的指针,解决了此问题。
另一个问题出在析构函数上。我添加了析构函数程序报错“访问权限冲突”。我监视变量值得同时单步调试发现,当析构函数delete到Huffman树的叶子结点的时候,删除叶结点会导致其父结点中指向其兄弟结点的指针改变,从而使兄弟结点丢失。到目前为止,这个问题仍然未解决,但不影响程序进行Huffman编码。我认为这可能和存放叶子结点的变量存放在用new生成的数组中有关,而用于连接它们的非叶结点却是单独用new申请的有关。
在撰写报告时偶然发现解码模块有不能解码出明文的最后一个字符的BUG,因为之前最后一个字符一直是英文句号而没有被发现。通过自行输入一个单词进行编码再进行译码发现最后一个字符没有译出来。经过分析得出问题出在if语句的位置上,也即是循环中if的顺序问题。如果密码字符串中的指针后移和判断是否为叶子结点跨越了循环,那么会出现密码字符串指针满足了退出循环的条件(到达了密码末尾),此时Huffman树中的指针也指向根节点了,然而记录明文的语句在下一个循环中,还没来得及记录明文字符,循环就结束了,导致最后一个明文字符记录不上。调换三个if的顺序,将判断是否为叶子结点的if语句放在每次循环的最后,让树结点指针移动之后立即判断有没有到达叶子结点,这些做完后密码字符串中的指针再后移。由此解决了此问题。

五、 用户手册

如上图,按照程序提示输入一段不带中文字符的字符串,即任何字符都必须只占一个char字符类型,不能含有全角字符。否则程序将会让你重新输入。该字符串输入之后必须以#结尾,表示结束输入。
比如我输入Data Structure# 则出现如下结果:

其中,上面Huffman编码表中的第一个空白实际上是空格。
然后再将生成的密文用Ctrl+C复制之后用Ctrl+V粘贴输入,程序经过解码将获得明文。
也可以复制密文的一部分,这里复制密文的前12位,也就是对应原文的“Data”这一部分,让程序解码,看看是不是解码结果是不是“Data”。

结果正好是101110000100对应的“Data”,说明解码正确。

六、 测试数据及测试结果

6.1 边界测试

测试数据:a#
测试目的:让程序对单个字符进行编码,看是否会出现问题。
正确输出:编码表中a对应的值为空白,也就是a没有编码。

这很正常,因为根节点就是叶子结点,找到它根本不用分支。但是对单独一个字符进行Huffman编码也是无意义的。

测试数据:ab#
测试目的:只有两个字符的字符串能否被正常编码?
正确输出:

单个字符也能顺利解码。

6.2 压力测试

测试数据:
China University of Geosciences (CUG), founded in 1952, is a national key university affiliated with the Ministry of Education. It is also listed in the National “211 Project”, the 985 Innovation Platform for Advantageous Disciplines and the “Double First-class Plan”. CUG, featuring geosciences, is a comprehensive university that also offers a variety of degree programs in science, engineering, literature, management, economics, law, education and arts. Its Geology and Geological Resources & Engineering have both been ranked as national number one disciplines. Its Earth Science, Engineering, Environmental Studies & Ecology, Materials Science, Chemistry, and Computer Science have entered the top 1% of global ESI (Essential Science Indicators), with Earth Science in the top 1 of the list.
CUG has two campuses in Wuhan. The main campus is the Nanwang Mountain Campus, located in the heart of the Wuhan East Lake National Innovation Demonstration Zone, which is popularly known as China Optics Valley. The Future City Campus is located in the east of Wuhan and is 27 km from the main campus. These two picturesque campuses cover a combined area of 1,435,545 m2. They are ideal places to study, work, and enjoy life. CUG also boasts four field training centers: Zhoukoudian in Beijing, Beidaihe in Hebei Province, Zigui in Hubei Province, and Badong in Hubei Province.
CUG has established a complete education system. As of November 2019, 28,635 full-time students, including 18,092 undergraduate students, 7,774 master’s students, 1,764 doctoral students, and 1,005 international students have enrolled in its subsidiary 23 schools and 86 research institutes. CUG currently has a faculty of 1,876 full-time teachers, among which there are 520 professors (11 of which are members of the Chinese Academy of Sciences) and 927 associate professors.
CUG is focused on fostering high-quality talent. Among its over 300,000 graduates, many have gone on to become scientific and technological elites, statesmen, business leaders and athletes. And they have made great contributions to the nation and society, represented by former Premier WEN Jiabao and 39 members of the Chinese Academy of Sciences and Chinese Academy of Engineering.
CUG has strengthened exchanges and cooperation with international universities. It has signed friendly cooperation agreements with more than 100 universities from the United States, France, Australia, Russia and other countries. CUG has actively carried out academic, scientific and cultural exchanges with universities around the world. There are about 1,000 international students from more than 90 countries studying at CUG. It also sponsors more than 900 teachers and students to study abroad or conduct international exchanges, and invites more than 400 international experts to visit, lecture, and teach at CUG every year. In 2012, CUG initiated and co-established the International University Consortium in Earth Science (IUCES) with 11 other world-renowned universities. IUCES is committed to promoting the common development of geosciences education and scientific research through resource sharing, exchange and cooperation among its member institutions. In addition, CUG has partnered with Bryant University from USA, Alfred University from USA, and Veliko Turnovo University from Bulgaria in establishing three Confucius institutes on their campuses.#
测试目的:测试在明文字符数非常多的情况下,程序能否顺利编码并且解码,是否出现内存泄漏的问题。
测试结果:

可见,在密码中截取一段进行解码,程序能够正确解码出对应的结果。
(存放在剪切板之中的是密码的全文,由于控制台一次性接受的字符数量有限,所以只有一小部分被粘贴了上去。)

七、 此程序的亮点

  1. 采用最小堆,折半查找,间接排序节省时间开销。其中创建Huffman树的时间复杂度被优化为O(nlog2n),编码中根据字符查询对应的编码的时间复杂度被优化为O(log2n)
  2. 额外实现解码功能。
  3. 使用C++编写,模块之间低耦合,便于维护。

八、 源代码

数据结构课程设计_哈夫曼编码.zip

数据结构课程设计报告——Huffman编码相关推荐

  1. 数据结构课程设计报告-职工信息管理系统

    "数据结构"课程设计报告 系 (院):           信息工程学院 设计题目:           员工管理系统 专业班级:          计算机科学与技术1401B 小 ...

  2. c语言数据结构课程设计停车场管理系统,数据结构课程设计报告停车场管理系统...

    <数据结构课程设计报告停车场管理系统>由会员分享,可在线阅读,更多相关<数据结构课程设计报告停车场管理系统(8页珍藏版)>请在人人文库网上搜索. 1.数据结构课程设计报告系 别 ...

  3. c语言实现一元多项式程序报告设计,数据结构课程设计报告一元多项式的计算..doc...

    数据结构课程设计报告一元多项式的计算. 题目:一元多项式的计算 --链表 摘要(题目) 一元多项式计算 任务:能够按照指数降序排列建立并输出多项式: 能够完成两个多项式的相加.相减,并将结果输入: 目 ...

  4. 数据结构课程设计报告(附代码)

    数据结构课程设计报告 一.实训目的 通过课程设计,学会运用数据结构知识,针对具体应用,自己设计合理数据结构,确定存储结构,并能设计具体操作算法,选择使用具体语言进行实现.掌握C++较复杂程序的组织和设 ...

  5. c语言数据结构课程设计电梯,数据结构课程设计报告(模拟电梯).doc

    数据结构课程设计报告(模拟电梯) 山东理工大学计算机学院 课 程 设 计 (数据结构) 班 级姓 名学 号 指导教师 二〇一二年一月十日 课程设计任务书及成绩评定 课题名称电 梯 模 拟 Ⅰ.题目的目 ...

  6. 2018数据结构课程设计报告

    目录 一.引言 a) 编写目的 b) 项目背景 c) 术语说明 d) 参考资料 二.任务概述 a) 目标 b) 运行环境 c) 需求概述 d) 条件与限制 三. 总体设计 a) 处理流程 b) 总体结 ...

  7. 【“BattenSnakexjp4.1”数据结构课程设计报告】

    烟台大学计算机与控制工程学院 "BattenSnakexjp4.1"数据结构课程设计报告 计146-2 徐吉平 版权所有

  8. 【数据结构课程设计报告】电话号码查询系统(Java实现)

    数据结构课程设计报告 电话号码查询系统 数据结构课程设计报告 一.需求分析 二.系统功能划分及设计 1.存储结构设计 2.系统的功能架构设计 3.模块设计 3.代码实现 一.需求分析 问题描述:路径规 ...

  9. 计算机课程设计收费管理系统,数据结构课程设计报告---收费停车场管理系统

    数据结构课程设计报告---收费停车场管理系统 (20页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 14.9 积分 XX大学计算机与电子 信息学院< ...

最新文章

  1. 随机森林RandomForest挖掘生物标记预测分类
  2. MySQL的主从复制延迟问题
  3. opencv-python 9.4 拆分及合并图像通道
  4. Chapter 2 Open Book——34
  5. 最新综述:基于Transformer的NLP预训练模型已经发展到何种程度?
  6. ARM汇编伪指令 .word
  7. 平板电脑桌面添加计算机快捷键,驰为Vi10教你Windows 10中的这些实用快捷键
  8. ebay注册流程_跨境电商平台eBay企业入驻流程
  9. 用fiddler解决跨域访问
  10. c 宏变量/宏函数/log
  11. 《南溪的目标检测学习笔记》——数值编码(encode)的学习笔记
  12. 算法眼中的世界是什么样子?他们用一些彩色方块画了出来
  13. Android Folding View(折叠视图、控件)
  14. 大学计算机实验vfp,Visual FoxPro程序设计上机实验(第2版)
  15. 【工具类】数据脱敏工具类
  16. 番茄工作法 计划表格式
  17. html表格的冻结列
  18. The projects in the reactor contain a cyclic reference
  19. mysql多字段in用法
  20. vivado17.4支持w25q128的方法

热门文章

  1. 人到中年,到底应该坚持打工还是去创业?
  2. “离婚”华为后,荣耀第一胎满身伤痕
  3. 思杰桌面虚拟化终端设备的选择
  4. 《最优状态估计-卡尔曼,H∞及非线性滤波》:第7章 卡尔曼滤波的扩展
  5. 多协议直播发布工具和接收工具
  6. springboot实现读取excel插入数据库
  7. java代码拉马车游戏,8届省赛java 拉马车
  8. 专业订制|软件开发|系统开发|网页设计|做网站|企业建站|网站建设
  9. Linux: 微软、苹果、EMC和甲骨文获得822项Novell专利
  10. 天融信上网行为管理系统设置wifi短信验证流程