《数据结构课程设计》讲义.docx
- 文档编号:6752688
- 上传时间:2023-01-10
- 格式:DOCX
- 页数:14
- 大小:27.61KB
《数据结构课程设计》讲义.docx
《《数据结构课程设计》讲义.docx》由会员分享,可在线阅读,更多相关《《数据结构课程设计》讲义.docx(14页珍藏版)》请在冰豆网上搜索。
《数据结构课程设计》讲义
《数据结构课程设计》讲义
一、表达式求值问题
1.1问题定义及设计要求
表达式求值是程序设计语言编译中的一个最基本问题。
人们在书写表达式时通常采用将运算符放在两个操作数中间的“中缀”表示形式,称为中缀表达式。
但是这种表达式形式对计算机处理来说是不太适合的。
在计算机领域,经常将算术表达式表示成“后缀”表示形式,称为后缀表达式。
如:
中缀表达式3+2*(7-5)对应的后缀表达式为3275-*+。
要求设计算法,实现
(1)算数四则运算中缀表达式到后缀表达式的转换;
(2)后缀表达式的求值;(3)中缀表达式的求值。
要求配备菜单,至少含如下选项:
------------------------------------------------------
1.中缀表达式到后缀表达式的转换
2.后缀表达式的计算
3.中缀表达式的计算
4.退出
-------------------------------------------------------
要求以字符序列的形式从终端输入语法正确的、不含变量的整数表达式。
要求演示在求值过程中运算符栈、操作数栈、输入字符和主要操作过程及运算结果。
要求在算法用到栈和队列。
要求设计的数据结构与算法使算法的时间复杂度与空间复杂度尽可能低。
1.2中缀表达式到后缀表达式的转换
(1)问题分析
假设在算术表达式中只含四种基本运算符,操作数是10以内的整数。
假设一个中缀表达式中没有括号(如4+2*3,它的后缀表达式为423*+)。
在扫描到中缀表达式中的2后,能立即输出+,因为*具有较高优先级,必须先运算,因此需先保存+。
也就是说,新扫描运算符优先级必须与前一个运算符的优先级做比较,如果新的运算符优先级高,就要像前一个运算符那样保存它,直到扫描到第二个操作数,将它输出后才能将该运算符输出。
因此,在转化中必须保存两个运算符,后保存的运算符先输出。
用计算机来实现这个转化过程,就需要用到能后进先出的数据结构----栈。
如果在中缀表达式中含小括号,那么由于括号隔离了优先级规则,它在整个表达式的内部产生了完全独立的子表达式。
因此,前面的算法就需要有所改变。
当扫描到一个左括号时,需要将其压入栈中,使其在栈中产生一个“伪栈底”。
这样算法就可以像前面一样进行。
但当扫描到一个右括号时,需要将栈中从栈顶到“伪栈底”之间的所有运算符弹出,然后再将这个“伪栈底”删除。
(2)数据结构与算法设计
算法基本思想:
利用栈(运算符栈)将中缀表达式转换为后缀表达式。
顺序扫描中缀表达式,当读到数字时,直接将其送至输出队列中;当读到运算符时,将栈中所有优先级高于或等于该运算符的运算符弹出,送至输出队列中,再将当前运算符入栈;当读入左括号时,将其入运算符栈;当读到右括号时,将栈中从栈顶到靠近栈顶的第一个左括号(“伪栈底”)之间的所有运算符全部依次弹出,送至输出队列中,再删除栈中的左括号。
算法设计要求:
将表达式中的操作数规定为1位数字字符。
也可根据个人的能力对这部分功能进行扩充,使得操作数可以是多位数,甚至可以是小数或负数。
运算符可只包含+、-、*、/四种基本运算。
也可根据需要对算法的功能进行扩充,允许有其它运算符。
为了简化算法,可认为扫描到的任何运算符,其优先级都比栈顶的左括号优先级高。
为了方便边界条件(栈空)判断,提高算法运行效率,在扫描中缀表达式之前,在空栈中预先压入一个‘#’字符作为栈底元素,另外,在中缀表达式的最后增加一个‘#’字符作为中缀表达式的结束标志,当扫描到结束符‘#’时,将栈中从栈顶到‘#’之间的所有运算符全部依次弹出,送至输出队列中,再删除栈中的‘#’,并结束算法。
算法应该能够过滤掉输入符号之间的空格。
也可对本算法功能进行扩充,使其具有对对输入表达式进行语法检查的功能。
要求输出中间结果及最终结果,最好能在算法运行过程中演示运算符栈和存放后缀表达式的队列的变化情况。
如,若输入的中缀表达式字符串:
1+2*(3-1+2)-3#,就会得到后缀表达式:
1231-2+*+3-,在算法运行过程中,运算符栈和存放后缀表达式的队列变化过程如表1-1所示。
表1-1中缀表达式到后缀表达式的转换过程示例
转换步骤
中缀表达式的读入
运算符栈
后缀表达式
初始
1+2*(3-1+2)-3#
#
空
1
+2*(3-1+2)-3#
#
1
2
2*(3-1+2)-3#
#+
1
3
*(3-1+2)-3#
#+
12
4
(3-1+2)-3#
#+*
12
5
3-1+2)-3#
#+*(
12
6
-1+2)-3#
#+*(
123
7
1+2)-3#
#+*(-
123
8
+2)-3#
#+*(-
1231
9
2)-3#
#+*(+
1231-
10
)-3#
#+*(+
1231-2
11
-3#
#+*
1231-2+
12
3#
#-
1231-2+*+
13
#
#-
1231-2+*+3
14
空
1231-2+*+3-
1.3后缀表达式的计算
(1)问题分析
在后缀表达式中,不仅不需要括号,而且还完全免除了运算符优先规则。
后缀表达式只需要从左到右顺序计算。
后缀表达式中可能有多个运算符,如1231-2+*+3-,因此必须像输入字符一样保存中间结果。
在计算后缀表达式时,最后保存的值最先取出参与运算,所以需要用栈来存储操作数及中间结果。
因为在生成的后缀表达式队列中,存放的是字符序列,因此在算法中要有一个数字字符到数值的转换。
(2)算法设计
算法基本思想:
利用栈(操作数和运算结果栈)计算后缀表达式。
顺序扫描后缀表达式,当读到数字时,将其送至栈中;当读到运算符θ时,将栈顶字符弹出,将其转换成对应的数值并赋给变量y,再将次栈顶字符弹出,将其转换成对应的数值,并赋给变量x,之后计算xθy,将运算结果转换成对应的数字字符送入栈中。
注意,本算法主要涉及两种数据结构:
队列和栈。
算法设计要求:
本算法可以对任意输入的后缀表达式进行计算,也可以以前一个中缀表达式到后缀表达式转换算法的输出为输入,计算该后缀表达式。
要求显示算法的运行结果。
最好能在算法运行过程中演示运算结果栈的变化情况。
以输入的后缀表达式为1231-2+*+3-的情况为例,在算法运行过程中,操作数及运算结果栈变化过程如表1-2所示。
表1-2后缀表达式计算过程示例
计算步骤
后缀表达式的读入
运算符栈
初始
1231-2+*+3-
空
1
231-2+*+3-
1
2
31-2+*+3-
12
3
1-2+*+3-
123
4
-2+*+3-
1231
5
2+*+3-
122
6
+*+3-
1222
7
*+3-
124
8
+3-
18
9
3-
9
10
-
93
11
空
6
1.4中缀表达式的计算
(1)问题分析
要把一个表达式翻译成正确求值的机器指令序列,首先要能够正确解释表达式。
要对算术表达式求值,首先要了解算术四则运算规则。
即:
先乘除,后加减;同级从左到右计算;先括号内,后括号外。
例如,1+2*(3-1+2)-3=1+2*(2+2)-3=1+2*4-3=1+8-3=9-3=6
任何一个表达式都是由操作数、运算符、界限符组成的。
操作数既可以是常数,也可以是说明为变量或常量的标示符;运算符可以分为算术运算符、关系运算符和逻辑运算符3类;基本界限符有左右括号和表达式结束符等。
为了简化问题,运算符可只包含+、-、*、/四种基本运算,括号只有圆括号。
可根据需要及个人能力对算法的功能进行扩充,允许有其它运算符。
为了简化算法,可认为扫描到的任何运算符,其优先级都比栈顶的左括号优先级高。
算符之间的优先关系如表1-3所示。
表1-3算符之间的优先关系表
+
-
*
/
(
)
#
+
>
>
<
<
<
>
>
-
>
>
<
<
<
>
>
*
>
>
>
>
<
>
>
/
>
>
>
>
<
>
>
(
<
<
<
<
<
=
x
)
>
>
>
>
x
>
>
#
<
<
<
<
<
x
=
(2)算法设计
算法基本思想:
建立并初始化操作数栈和算符栈,将表达式起始符‘#’压入算符栈。
依次读入表达式中的每个字符,当读入的字符ch是操作数,将其入操作数栈;当读入的字符ch是运算符,则根据运算符栈的栈顶元素和ch的优先权比较结果,做不同的处理:
若是小于,则ch压入运算符栈,读入下一个字符。
②若是大于,则弹出运算符栈顶的运算符,从操作数栈弹出两个数,进行相应运算,将结果压入操作数栈。
③若是等于,则运算符栈顶元素是‘(’且ch是‘)’,这时弹出运算符栈顶的‘(’,相当于去括号,然后读入下一个字符。
当读入的字符ch是‘#’,且运算符栈顶元素也是‘#’时,算法结束。
算法设计要求:
将表达式中的操作数规定为1位数字字符。
也可根据个人的能力对这部分功能进行扩充,使得操作数可以是多位数。
算法应能对由键盘输入任意中缀表达式进行计算。
算法应该能够过滤掉输入符号之间的空格。
也可对本算法功能进行扩充,使其具有对对输入表达式进行语法检查的功能。
要求在算法运行过程中演示运算符栈和操作数栈的变化情况,并显示最终运算结果。
1.5评价标准
若本实验项目占100分,则其中,
(1)基本功能占60分,具体分值分部如下:
●中缀表达式到后缀表达式的转换(20分)
●后缀表达式的计算(20分)
●中缀表达式的计算(20分)
(2)附加功能占20分,具体分值分部如下:
●具有语法检查功能(含滤空格、除零异常处理、括号匹配否判断等功能)。
(4分)
●除了用键盘输入数据外,还可以将前一个运算的结果(以指定特定文件形式)作为下一个运算的输入。
(2分)
●能演示算法运行过程中运算符栈和操作数栈的变化情况。
(3分)
●能处理多位数、负数及小数的运算。
(3分)
●能实现除了基本的加减乘除运算以外的其他运算,每增加一个运算加一分,直到加满4分为止。
(4分)
●界面友好,使用方便,最好能有图形界面。
(4分)
(3)设计报告占20分。
二、哈夫曼编码/译码问题
2.1问题定义及设计要求
哈夫曼算法应用非常广泛,本节以哈夫曼编码为例说明哈夫曼算法的应用。
在电报通信中,电文是以二进制的0、1序列传送的。
发送方需将电文中的字符转换成二进制的0、1序列(编码);接收方则需将收到的电文转化为对应的字符序列(译码)。
最简单的编码方式是等长编码,假设电文中字符串仅有26个大写的英文字母组成,采用等长的二进制编码时,每个字符用5位二进制位串表示即可(25>26)。
接收方只要按5位分割进行译码就可得到对应的字符。
一般来说字符集中的字符在电文中出现的频率是不均匀的,例如,英文中使用A和E较Z要频繁的多。
因此,若让使用频率高的字符编码尽可能短,则可使传送的电文总长缩短。
然而使用不等长编码可能使译码产生多义性的电文,例如,若用00表示A,01表示E,0001表示Z,则当接受方接收到串0001时,无法确定原文是AE还是W。
产生该问题的原因是A的编码是Z的编码的前缀。
因此,若对某个字符集进行不等长编码,则要求字符集中任何一个字符的编码都不是其它字符编码的前缀,这种编码叫前缀(编)码。
假设组成电文的字符集为C={c1,c2,…cn},每个字符ci在电文中出现的次数为ti,ci的编码长度为li,则电文总长为
。
然而不可能每次传送一个电文时都统计该电文中每个字符的具体出现次数,但可以通过大量的电文进行统计分析,得出每个字符ci出现的概率f(ci),则
是平均编码长度。
显然,平均编码长度越小,电文总长越短。
因此,若想电文总长最短,可以这样构造最优前缀码:
用每个待编码字符ci作为叶结点,用其概率f(ci)作其权值,利用哈夫曼算法构造一棵带权路径长度最小的哈夫曼树。
然后将哈夫曼树中每个分支结点的左分支标上0,右分支标上1,将从根到叶子的路径上的标号连接起来,作为该叶结点对应字符的哈夫曼编码。
显然,每个字符ci的编码长度是从根到叶子ci的路径长度li,因此,
既是平均编码长度,又是二叉树的带权路径长度。
由于哈夫曼树是带全路径长度最小的二叉树,因此哈夫曼编码平均编码长度最小。
此外,没有一个叶结点是另一个叶结点的祖先,因此,任何字符的编码都不可能是另一个字符编码的前缀。
因此,哈夫曼编码是最优的二进制前缀码。
要求设计与实现一个哈夫曼编码/译码系统,至少具有如下功能:
------------------------------------------------------
1.建立哈夫曼树
2.哈夫曼编码(生成字符的哈夫曼编码,并产生发送方0、1序列电文)
3.译码
4.退出
------------------------------------------------------
要求设计的数据结构与算法使算法的时间复杂度与空间复杂度尽可能低。
2.2哈夫曼树的建立
(1)问题分析
哈夫曼树是叶结点的带权路径长度总和最小的二叉树,也称为最优二叉树。
因此,在构造哈夫曼树时,权越大的叶结点应该离根越近,权越小的叶结点离根越远。
(2)算法设计
算法基本思想:
(1)根据给定的n个权值w1,w2,…,wn构成一个有n棵二叉树的森林F,其中每棵二叉树都只有一个权值为wi的根结点,其左右子树为空。
(2)贪心地从森林F中选出根结点的权值最小的和第二小的二叉树,将两棵树合并成一棵新树,为了保证新树仍然是一棵二叉树,需增加一个新的根结点,并将所选的两棵树的根分别作为新树根结点的左右孩子,将左右孩子的权值之和作为新根的权值。
(3)在新产生的森林F中,重复
(2),直到深林F中只剩一棵树为止。
算法设计要求:
在建立哈夫曼树时,叶结点对应的字符及其权值可以用键盘输入。
也可以将算法设计的更复杂一些,对指定的文件(电文),统计每个字符在电文中出现的次数。
假设在电文字符串中只包含大写英文字母,则可定义一个含26个元素的一维整型数组,用于存储每个字母出现的次数。
算法可使用扫描到的字母的ASCII码值减去64作为统计数组的下标计数,从而提高效率。
另外,算法还要求出电文中出现了哪些字符(在电文中出现的字符其出现次数不为零),保存这些字符及其在电文中出现的次数,以供建立哈夫曼树及编码时使用。
最好能画出算法所建立的哈夫曼树。
2.3哈夫曼编码
(1)问题分析
要编码的字符都是哈夫曼树中的叶结点,在哈夫曼树的存储结构中,因为增加了结点与其双亲的链接,所以在进行哈夫曼编码时可以从哈夫曼树的叶结点出发向上回溯到根结点,从而得到该字符的哈夫曼编码。
(2)算法设计
算法基本思想:
从哈夫曼树的叶子tree[i]出发,利用双亲指针parent找到tree[i]的双亲tree[parent],再利用tree[parent]的lchild和rchild指针域确定tree[i]是tree[parent]的左孩子还是右孩子,若是左孩子,则生成代码0,否则生成代码1。
然后以tree[parent]为出发点,重复上述过程,直到找到根结点为止。
因为这样生成的0、1代码序列与要求的编码次序相反,因此,可以将生成的代码从后往前依次存放在一个位串bits中。
虽然各字符的编码长度不同,但不会超过要编码的字符个数n,所以bits的大小可为n,并且需要一个指start指示编码在位串bits[n]中的起始位置。
算法设计要求:
要求生成哈夫曼编码对照表,并显示每个字符的哈夫曼编码。
要求建立编码文件,对于要编码的字符串中的每个字符,查哈夫曼编码对照表,找到后,将该字符的编码写到代码文件(或在屏幕输出),直到所有字符处理完为止。
查哈夫曼编码对照表若能用散列表,算法运行效率会比较高。
2.4译码
(1)问题分析
接收方可根据哈夫曼树译码,译码过程与编码过程相反。
(2)算法设计
算法基本思想:
从哈夫曼树的根结点出发,逐个读入电文中的二进制码,若读入0,走向左孩子,否则走向右孩子,一旦到叶结点,便可译出相应字符。
然后,重新从根结点出发继续译码,直到二进制电文结束。
2.5算法实现实例
(1)建立哈夫曼树
哈夫曼编树存储结构描述如下:
intm=2*n;
typestruct
{charch;
intweight;
intlchild,rchild,parent;
}HTnode,*HuffmanTree;//HTnode是哈夫曼树结点类型
哈夫曼算法如下:
voidCreatHuffmanTree(HufmanTree&HT,intn)//哈夫曼树算法,n是叶结点数
{intS1,S2;
if(n<=1)return;
m=2*n-1;//m是哈夫曼树结点总数
HT=newHTNode[m+1];//0单元不用
for(i=1;i<=m;i++)
HT[i].weight=HT[i].lchild=HT[i].rchild=HT[i].parent=0;
for(i=1;i<=n;i++)
cin>>HT[i].ch>>HT[i].weight;
for(i=n+1;i<=m;i++)
{Select(HT,i,S1,S2);
HT[s1].parent=i;
HT[s2].parent=i;
HT[i].lchild=s1;
HT[i].rchild=s2;
HT[i].weight=
HT[S1].weight+HT[S2].weight;
}
}//哈夫曼树算法
voidSelect(HufmanTreeHT,inti,int&s1,int&s2)
//在HT中选出权最小的和第二小的根结点,其下标分别记在s1和s2中
{S1=0;S2=0;
sm1=sm2=MAX;
for(j=1;j
if(HT[j].parent==0)
if(HT[j].weight {sm2=sm1; sm1=HT[j].weight; S2=S1; S1=j; } elseif(tree[j].weight {sm2=HT[j].weight; s2=j; } } (2)哈夫曼编码 数据结构与算法设计: 因为这样生成的0、1代码序列与要求的编码次序相反,因此,可以将生成的代码从后往前依次存放在一个位串bits中。 虽然各字符的编码长度不同,但不会超过要编码的字符个数n,所以bits的大小可为n,并且需要一个指start指示编码在位串bits[n]中的起始位置。 哈夫曼编码对照表的存储结构如下: typestruct {charch; char*first; }HuffmanCode; HuffmanCodeHC[m+1];//Huffman编码对照表 生成字符-哈夫曼编码对照表的算法如下: HUFFMANCODE(HufmanTreeHT,HuffmanCode&HC,intm)//构造哈夫曼编码{HC=newchar*[m+1];//m是要编码的字符数 cd=newchar[m];cd[m-1]=‘\0’; for(i=1;i<=m;i++) {strat=m-1; c=i;f=HT[i].parent; while(f! =0) {start--; if(HT[f].lchild==c) cd[start]=’0’; elsecd[start]=’1’; c=f;f=HT[f].parent; } HC[i].first=newchar[m-start]; Strcopy(HC[i].first,&cd[start]); } deletecd; } 建立电文的编码文件的算法如下: voidcoding(HuffmanCodeHC,char*str) //对str所代表的字符串进行编码并写入磁盘文件 {inti,j; FILE*fp; fp=fopen(“codefild.txt”,“w”); while(*str) {for(i=1;i<=m;i++) if(HT[i].ch==*str) {fputs(HC[i].first,fp); break; } str++; } } (3)译码 DECODE(codetypecode[],hufmtreetree[]) {endflag=-1; i=m-1; scanf(“%d”,&b); while(b! =endflag) {if(b==0) i=tree[i].lchild-1; else i=tree[i].rchild-1; if(tree[i].lchild==0) {putchar(code[i].ch); i=m-1;} scanf(“%d”,&b); } if(tree[i].lchild! =0) printf(“\nERROR\n”); } 2.6评价标准 若本实验项目占100分,则其中, (1)基本功能占65分,具体分值分部如下: ●建立哈夫曼树。 (20分) ●产生字符-哈夫曼编码对照表,并显示每个字符对应的哈夫曼编码。 (20分) ●对文件或从键盘输入的字符串进行编码,产生发送方的编码电文并显示或存入指定的文件中。 (5分) ●接收方译码,显示译码结果或将译码结果存入指定文件。 (20分) (2)附加功能占15分,具体分值分部如下: ●发送方按位压缩存储电文,接收方根据压缩电文解压译码,并显示压缩比。 (10分) ●画出哈夫曼树。 (2分) ●界面友好,使用方便,最好能有可视化界面。 (3分) (3)设计报告占20分。 附录一设计报告大纲 题目: 20字以内(封面页,格式见附录二) 如: 哈夫曼编码/译码系统的设计与实现 摘要: 300字以内(2分) 关键字: 3-5个(1分) 正文: 1.概述(3分) 问题定义(包括文字描述、输入、输出定义等) 研究的目的与意义 2.数据结构与算法设计(8分) (用适当的文字,将各部分衔接起来。 ) 数据的逻辑结构设计 数据的存储结构设计 算法基本思想 算法流程图 其它部分的设计 3.实现及测试(3分) 实现环境、实现技术方法及实现效果(包括文字描述及截图) 测试用例(设计测试用例时,应该包括基本功能测试、边界情况测试,异常情况处理测试等相应的测试用例。 ) 4.结论与展望(2分) 研究的关键结论(包括算法的时间复杂性)或发现; 研究工作的优势、不足及展望。 参考文献(1分) 要求至少有两篇参考文献。 附录: 程序源代码(要求对每个函数及函数内关键部分加注释。 ) 附录二设计报告格式要求 封面:
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构课程设计 数据结构 课程设计 讲义