题目: 从下列6个题目中选择4个: (1)表达式求值问题:给定一个四则运算的中缀表达式、前缀表达式和后缀表达式,编程计算表达式的值。 (2)看病排队候诊问题:根据病人的病情规定不同的优先级别。医生在诊断治疗时,选择优先级别高的病人进行诊治,如果遇到两个优先级别相同的病人,则选择优先来排队的病人进行诊治。 (3)计算哈夫曼树的WPL值:根据给定的n个权值(非负值),计算所构造哈夫曼树的WPL值。 (4)图的应用:学校要建立一个娱乐中心,设计该娱乐中心的位置,使得各部门到中心的距离较近。 (5)哈希表的应用:根据给定的一组整数,建立哈希表。(6)汽车牌照的快速查询:对一组汽车牌照进行排序和查找。
|
设计要求: (1)分析问题,设计相应的数据结构(word); (2)算法设计,给出各算法描述(word); (3)给出源程序清单; (4)用测试数据去验证算法及程序的正确性; (5)算法时间复杂度分析。 Word报告要求: (1)分析问题,设计相应的数据结构(word); (2)算法设计,给出各算法描述(word); (3)给出源程序清单; (4)用测试数据去验证算法及程序的正确性; (5)算法时间复杂度分析。 |
教师评语及成绩:
教师签名: |
1. 表达式求值
表达式求值问题
给定一个四则运算的中缀表达式、前缀表达式和后缀表达式,编程计算表达式的值。
基本要求:
(1)在给定的表达式中要包含括号;
(2)栈的操作要求自己完成,不允许调用类库中的方法;
(3)对不同的操作编写相应的函数。
测试数据要求:
给定测表达式(字符串)中的数字字符要有包含两位以上的数字字符。
将中缀表达式转换为前缀表达式:
遵循以下步骤:
(1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2;
(2) 从右至左扫描中缀表达式;
(3) 遇到操作数时,将其压入S2;
(4) 遇到运算符时,比较其与S1栈顶运算符的优先级:
(4-1) 如果S1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;
(4-2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;
(4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;
(5) 遇到括号时:
(5-1) 如果是右括号“)”,则直接压入S1;
(5-2) 如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;
(6) 重复步骤(2)至(5),直到表达式的最左边;
(7) 将S1中剩余的运算符依次弹出并压入S2;
(8) 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。
将中缀表达式转换为后缀表达式:
与转换为前缀表达式相似,遵循以下步骤:
(1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2;
(2) 从左至右扫描中缀表达式;
(3) 遇到操作数时,将其压入S2;
(4) 遇到运算符时,比较其与S1栈顶运算符的优先级:
(4-1) 如果S1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(4-2) 否则,若优先级比栈顶运算符的高,也将运算符压入S1(注意转换为前缀表达式时是优先级较高或相同,而这里则不包括相同的情况);
(4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;
(5) 遇到括号时:
(5-1) 如果是左括号“(”,则直接压入S1;
(5-2) 如果是右括号“)”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号为止,此时将这一对括号丢弃;
(6) 重复步骤(2)至(5),直到表达式的最右边;
(7) 将S1中剩余的运算符依次弹出并压入S2;
(8) 依次弹出S2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式(转换为前缀表达式时不 用逆序)。
中缀表达式求值计算:
1)顺序扫描中缀表达式,
当遇到一个左括号时立即将其压栈;
当遇到对应的右括号时,将栈中的操作符弹出并输出,直到遇到左括号。最后再弹出左括号(但不输出);
当遇到一个分量时,立即输出;
当遇到一个操作符时,将它与栈顶操作符比较:
<1>如果它比栈顶的操作符优先级高,或者它是左括号后的第一个操作符,则将其压入栈;
<2>否则(低或相等),将栈顶操作符弹出并输出;
<3>继续比较,如果它比新的栈顶操作符的优先级高,跳到 2),否则,重复<2> <3>。
如果扫描到了中缀表达式的末尾,将栈中的剩余的操作符全部弹出并输出,否则重复 1)。
前缀表达式的计算机求值:
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 op 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。
例如前缀表达式“- × + 3 4 5 6”:
(1) 从右至左扫描,将6、5、4、3压入堆栈;
(2) 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素,注意与后缀表达式做比较),计算出3+4的值,得7,再将7入栈;
(3) 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈;
(4) 最后是-运算符,计算出35-6的值,即29,由此得出最终结果。
可以看出,用计算机计算前缀表达式的值是很容易的。
后缀表达式的计算机求值:
与前缀表达式类似,只是顺序是从左至右:
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
例如后缀表达式“3 4 + 5 × 6 -”:
(1) 从左至右扫描,将3和4压入堆栈;
(2) 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素,注意与前缀表达式做比较),计算出3+4的值,得7,再将7入栈;
(3) 将5入栈;
(4) 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
(5) 将6入栈;
(6) 最后是-运算符,计算出35-6的值,即29,由此得出最终结果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//栈的应用,表达式解析
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef int Status;
//联合体,既可以存储char,也可以存储Float,用于前缀表达式解析计算
typedef union {
char ch;
float num;
} UnData;
//包含多类存储结构的结构体
typedef struct {
int type; //0-字符,1-浮点数
UnData data;
} UnStruct;
//联合体,既可以存储char,也可以存储char*,用于中缀表达式与前缀后缀之间的转换
typedef union {
char ch;
char* strNum;
} UnElem;
//包含多类存储的结构体
typedef struct {
int type; //0-字符,1-字符串
UnElem elem;
} UeStruct;
template<class DT>
struct SqStack {
DT *base;
DT *top;
int stacksize;
};
typedef SqStack<char> CharStack; //字符栈
typedef SqStack<float> FloatStack; //浮点数栈
typedef SqStack<UnStruct> UnStack; //复合数据栈
typedef SqStack<UeStruct> UeStack; //复合数据栈
//初始化构造
template<class ST, class DT>
Status InitStack(ST &S) {
S.base = (DT *)malloc(STACK_INIT_SIZE * sizeof(DT));
if(!S.base) exit(OVERFLOW);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
//扩展存储空间
template<class ST, class DT>
Status IncrementStack(ST &S, int increment = 0) {
DT *newbase = (DT *)realloc(S.base,
(S.stacksize + (increment == 0 ? STACKINCREMENT : increment))
*sizeof(DT));
if(!newbase) exit(OVERFLOW);
S.base = newbase;
S.stacksize += (increment == 0 ? STACKINCREMENT : increment);
return OK;
}
//判断栈是否为空
template<class ST>
Status StackEmpty(ST &S) {
return S.base == S.top;
}
//获取栈顶元素
template<class ST, class DT>
Status GetTop(ST &S, DT &e) {
if(StackEmpty(S)) return ERROR;
e = *(S.top - 1);
return OK;
}
//入栈
template<class ST, class DT>
Status Push(ST &S, DT e) {
if(S.top == S.base + S.stacksize) IncrementStack<ST, DT>(S);
*S.top++ = e;
return OK;
}
//出栈
template<class ST, class DT>
Status Pop(ST &S, DT &e) {
if(StackEmpty(S)) return ERROR;
e = *--S.top;
return OK;
}
//辅助操作,比较算符t1,t2的优先级
char Precede( char t1, char t2, char *ops, char opt[7][7]) {
char *p1,*p2;
p1=strchr(ops,t1);
p2=strchr(ops,t2);
return (opt[p1-ops][p2-ops]);
}
//辅助操作,进行运算符的计算
float Calc(float x, float y, char op) {
switch(op) {
case '+':
return x + y;
break;
case '-':
return x - y;
break;
case '*':
return x * y;
break;
case '/':
return x / y;
break;
}
return 1.0;
}
//辅助操作,判断字符是否在字符串中
char* In(char c, char *chs) {
return strchr(chs, c);
}
//解析字符串获取操作数(字符串)或者操作符(字符)(返回1操作数,0操作符号)
//支持逆序解析(用于中缀表达式转化为前缀表达式)
int GetEntity(char *str, int& pos, char* num, char& op, char *ops, char *nums, char *seps, int rev = 0) {
int c = 0, isNum = 0;
while(1) {
//如果是分隔符
if(In(str[pos], seps)) {
//如果已经读入了数字,返回数字
if(isNum) {
num[c] = 0;
if(rev) strrev(num);
return 1;
}
//否则继续移动
if(rev) pos--;
else pos++;
continue;
}
//如果是操作符
if(In(str[pos],ops)) {
//如果已经读入了数字,返回数字
if(isNum) {
num[c] = 0;
if(rev) strrev(num);
return 1;
}
//否则返回操作符
op = str[pos];
if(rev) pos--;
else pos++;
return 0;
}
//如果是数字
if(In(str[pos], nums)) {
isNum = 1;
num[c++] = str[pos];
if(rev) pos--;
else pos++;
continue;
}
//如果是其他字符报告非法字符,注意,此处EvaluateExpression函数中未加入检测!!!
return -1;
}
}
//辅助操作,从字符串中解析数字
float ParseNum(char *str) {
int pos = 0;
float relInt = 0.0, relFrac = 0.0;
while(str[pos] != '.' && str[pos]) {
relInt = relInt * 10 + (str[pos++] - '0');
}
if(str[pos] == '.') {
pos++;
float div = 10;
for(; str[pos]; div /= 10) {
relFrac += (str[pos++] - '0') / div;
}
}
return relInt + relFrac;
}
//中缀表达式的解析
float EvaluateExpressionMid(char *str, char *ops, char *nums, char *seps, char opt[7][7]) {
CharStack opStack;
FloatStack numStack;
InitStack<CharStack, char>(opStack);
InitStack<FloatStack, float>(numStack);
Push<CharStack,char>(opStack, '#');
int pos = 0;
char op, tch;
char num[20];
float ta, tb;
int cat = GetEntity(str, pos, num, op, ops, nums, seps);
while(cat || op != '#' || !GetTop<CharStack, char>(opStack, tch) || tch != '#') {
//如果是操作数
if(cat) {
Push<FloatStack, float>(numStack, ParseNum(num));
cat = GetEntity(str, pos, num, op, ops, nums, seps);
}
//否则如果是运算符
else {
GetTop<CharStack, char>(opStack, tch);
//比较栈顶运算符和当前运算符
switch(Precede(tch, op, ops, opt)) {
//栈顶运算符优先权低
case '<' :
Push<CharStack,char>(opStack, op);
cat = GetEntity(str, pos, num, op, ops, nums, seps);
break;
//脱括号
case '=' :
Pop<CharStack, char>(opStack, tch);
cat = GetEntity(str, pos, num, op, ops, nums, seps);
break;
//栈顶优先权高
case '>' :
Pop<CharStack, char>(opStack, tch);
Pop<FloatStack, float>(numStack, tb);
Pop<FloatStack, float>(numStack, ta);
Push<FloatStack, float>(numStack, Calc(ta, tb, tch));
break;
}
}
}
GetTop<FloatStack, float>(numStack, ta);
return ta;
}
//后缀表达式的解析
//加入逆向参数,支持前缀表达式的解析
float EvaluateExpressionSuffix(char *str, char *ops, char *nums, char *seps, int rev = 0) {
FloatStack numStack;
InitStack<FloatStack, float>(numStack);
int pos = rev ? strlen(str) - 2 : 0; //是否反向扫描,用于前缀表达式解析
char op;
char num[20];
float ta, tb;
int cat = GetEntity(str, pos, num, op, ops, nums, seps, rev);
while(cat || op != '#') {
//如果是操作数,直接入栈
if(cat) {
Push<FloatStack, float>(numStack, ParseNum(num));
}
//如果是运算符,出栈两个操作数进行计算,将计算结果入栈
else {
if(rev) {
//用于支持前缀表达式解析
Pop<FloatStack, float>(numStack, ta);
Pop<FloatStack, float>(numStack, tb);
} else {
Pop<FloatStack, float>(numStack, tb);
Pop<FloatStack, float>(numStack, ta);
}
Push<FloatStack, float>(numStack, Calc(ta, tb, op));
}
//获取下一个操作数或者运算符
cat = GetEntity(str, pos, num, op, ops, nums, seps, rev);
}
//获取栈顶结果
GetTop<FloatStack, float>(numStack, ta);
return ta;
}
//前缀表达式的解析
//使用一个复合数据栈
//凑够两个操作数即出栈另一个操作数和运算符计算,完成计算后再检查是否栈内还有操作数,有则重复处理,直到遇到运算符
float EvaluateExpressionPrefix(char *str, char *ops, char *nums, char *seps) {
UnStack unStack;
InitStack<UnStack, UnStruct>(unStack);
int pos = 0;
char op,tch;
char num[20];
float ta, tb;
UnStruct us;
int cat = GetEntity(str, pos, num, op, ops, nums, seps);
while(cat || op != '#') {
//如果是操作符,直接入栈
if(!cat) {
us.type = 0;
us.data.ch = op;
Push<UnStack, UnStruct>(unStack, us);
}
//如果是操作数
else {
tb = ParseNum(num);
GetTop<UnStack, UnStruct>(unStack, us);
//如果栈顶是操作符,直接入栈
if(!us.type) {
us.type = 1;
us.data.num = tb;
Push<UnStack, UnStruct>(unStack, us);
}
//否则操作数和操作符出栈,运算
else {
//如果是栈顶是操作数一直循环
while(us.type) {
Pop<UnStack, UnStruct>(unStack, us);
ta = us.data.num;
Pop<UnStack, UnStruct>(unStack, us);
tch = us.data.ch;
tb = Calc(ta, tb, tch);
GetTop<UnStack, UnStruct>(unStack, us);
}
//栈顶不是操作数时,最后计算结果入栈
us.type = 1;
us.data.num = tb;
Push<UnStack, UnStruct>(unStack, us);
}
}
//读入下一个操作符或者操作数
cat = GetEntity(str, pos, num, op, ops, nums, seps);
}
//获取栈顶结果
GetTop<UnStack, UnStruct>(unStack, us);
return us.data.num;
}
//前缀表达式的解析,方法2,从右向左扫描,使用后缀解析算法
float EvaluateExpressionPrefix2(char *str, char *ops, char *nums, char *seps) {
//字符串首部加#
char *newStr = (char *)malloc(sizeof(char) * (strlen(str) + 2));
newStr[0] = '#';
for(int i = 0; i <= strlen(str); i++) {
newStr[i+1] = str[i];
}
return EvaluateExpressionSuffix(newStr, ops, nums, seps, 1);
}
//中缀表达式转后缀表达式,思路与解析中缀表达式类似,只不过不计算而是直接入栈,注意按栈输出是反序的
//设定rev = 1,可用于中缀表达式到前缀表达式的转换,可按栈的正常顺序输出
void ExpressionMid2Suffix(char *str, char *ops, char *nums, char *seps, char opt[7][7], int rev = 0) {
CharStack opStack;
UeStack relStack;
InitStack<CharStack, char>(opStack);
InitStack<UeStack, UeStruct>(relStack);
Push<CharStack,char>(opStack, '#');
int pos = rev ? strlen(str) - 2 : 0; //是否反向扫描,用于中缀转前缀
char op, tch;
char num[20];
UeStruct us;
int cat = GetEntity(str, pos, num, op, ops, nums, seps, rev);
while(cat || op != '#' || !GetTop<CharStack, char>(opStack, tch) || tch != '#') {
//如果是操作数,直接入栈
if(cat) {
us.type = 1;
us.elem.strNum = (char *)malloc(sizeof(char) * (strlen(num) + 1));
strcpy(us.elem.strNum, num);
Push<UeStack, UeStruct>(relStack, us);
cat = GetEntity(str, pos, num, op, ops, nums, seps, rev);
}
//否则如果是运算符
else {
GetTop<CharStack, char>(opStack, tch);
//比较栈顶运算符和当前运算符
switch(Precede(tch, op, ops, opt)) {
//栈顶运算符优先权低,运算符入栈
case '<' :
Push<CharStack,char>(opStack, op);
cat = GetEntity(str, pos, num, op, ops, nums, seps, rev);
break;
//脱括号
case '=' :
Pop<CharStack, char>(opStack, tch);
cat = GetEntity(str, pos, num, op, ops, nums, seps, rev);
break;
//栈顶优先权高,运算符出栈,压入结果栈中
case '>' :
Pop<CharStack, char>(opStack, tch);
us.type = 0;
us.elem.ch = tch;
Push<UeStack, UeStruct>(relStack, us);
break;
}
}
}
printf("\n");
//按栈的顺序输出,用于中缀转前缀
if(rev) {
while(!StackEmpty<UeStack>(relStack)) {
Pop<UeStack, UeStruct>(relStack, us);
//如果是操作数
if(us.type) {
printf("%s ", us.elem.strNum);
//return us.elem.strNum;
}
//如果是运算符
else {
printf("%c ", us.elem.ch);
//return us.elem.ch;
}
}
}
//从底到顶输出,用于中缀转后缀
else {
for(UeStruct *pus = relStack.base; pus < relStack.top; pus++) {
//如果是操作数
if(pus->type) {
printf("%s ", pus->elem.strNum);
//return pus->elem.strNum;
}
//如果是运算符
else {
printf("%c ", pus->elem.ch);
//return pus->elem.ch;
}
}
}
printf("\n");
}
//中缀表达式转前缀表达式,从右向左扫描,操作数直接入结果栈,运算符优先级大的先入栈
void ExpressionMid2Prefix(char *str, char *ops, char *nums, char *seps) {
//字符串首部加#
char *newStr = (char *)malloc(sizeof(char) * (strlen(str) + 2));
newStr[0] = '#';
for(int i = 0; i <= strlen(str); i++) {
newStr[i+1] = str[i];
}
//使用新的优先级矩阵(由于要从右向左扫描,所以左右括号功能互换,同时原来的等于归于大于,改为等于归为小于)
char opt[7][7]= {{'<','<','<','<','>','<','>'},
{'<','<','<','<','>','<','>'},
{'>','>','<','<','>','<','>'},
{'>','>','<','<','>','<','>'},
{'>','>','>','>','>','@','>'},
{'<','<','<','<','=','<','@'},
{'<','<','<','<','@','<','='}
};
ExpressionMid2Suffix(newStr, ops, nums, seps, opt, 1);
}
int main() {
char *ops = "+-*/()#"; //运算符
char *nums = "01234567890."; //数字
char *seps = " \t"; //分隔符
//优先级
char opt[7][7]= {{'>','>','<','<','<','>','>'},
{'>','>','<','<','<','>','>'},
{'>','>','>','>','<','>','>'},
{'>','>','>','>','<','>','>'},
{'<','<','<','<','<','=','@'},
{'>','>','>','>','@','>','>'},
{'<','<','<','<','<','@','='}
};
printf("\n请输入中缀表达式:Please input the expression:\n");
char str[80];
//读入一行,以换行符为结束标记
scanf("%[^\n]", str);
//末尾加入一个#
int len = strlen(str);
str[len] = '#';
str[len+1] = 0;
float rel;
//数据测试
/*rel = EvaluateExpressionSuffix(str, ops, nums, seps);
printf("\n后缀表达式的结果是:The result of Suffix expression is: %f.\n",rel);*/
printf("中缀转换成后缀的表达式为:\n");
ExpressionMid2Suffix(str, ops, nums, seps, opt);
rel = EvaluateExpressionMid(str, ops, nums, seps, opt);
//printf("\n中缀表达式的结果是:The result of nifix expression is: %f.\n",rel);
printf("\n请根据转换的后缀表达式输入;Please input the expression:\n");
//用于吸收换行符
getchar();
//重新定义一个数组来存储
char str1[80];
//读入一行,以换行符为结束标记
scanf("%[^\n]", str1);
//末尾加入一个#
int len1 = strlen(str1);
str1[len1] = '#';
str1[len1+1] = 0;
float rel1;
rel1 = EvaluateExpressionSuffix(str1, ops, nums, seps);
//printf("\n后缀表达式的结果是:The result of Suffix expression is: %f.\n",rel1);
//rel = EvaluateExpressionPrefix(str, ops, nums, seps);
printf("中缀转换成前缀的表达式为:\n");
ExpressionMid2Prefix(str, ops, nums, seps);
printf("\n请根据转换的前缀表达式输入;Please input the expression:\n");
//用于吸收换行符
getchar();
//重新定义一个数组来存储
char str2[80];
//读入一行,以换行符为结束标记
scanf("%[^\n]", str2);
//末尾加入一个#
int len2 = strlen(str2);
str2[len2] = '#';
str2[len2+1] = 0;
float rel2;
rel2 = EvaluateExpressionPrefix2(str2, ops, nums, seps);
printf("\n前缀表达式的结果是:The result of prefix expression is: %f.\n", rel2);
printf("\n中缀表达式的结果是:The result of nifix expression is: %f.\n",rel);
printf("\n后缀表达式的结果是:The result of Suffix expression is: %f.\n",rel1);
//char Rel[80];
//ExpressionMid2Prefix(str, ops, nums, seps);
getchar();
getchar();
return OK;
}
分析:
ExpressionMid2Suffix ()函数为中缀表达式转后缀表达式,思路与解析中缀表达式类似,只不过不计算而是直接入栈,注意按栈输出是反序的,这里我们假设我们计算的数学式子操作数和操作符的总数为n,那么这个函数的时间复杂度为O(n)
ExpressionMid2Prefix()函数为中缀表达式转前缀表达式,从右向左扫描,操作数直接入结果栈,运算符优先级大的先入栈,这里我们假设我们计算的数学式子操作数和操作符的总数为n,那么这个函数的时间复杂度为O(n)。
EvaluateExpressionSuffix()为缀表达式的解析,根据计算过程计算n个操作数与操作符,它的时间复杂度为O(n)
2.计算哈夫曼树的WPL值
计算哈夫曼树的WPL值
根据给定的n个权值(非负值),计算所构造哈夫曼树的WPL值。
基本要求:
- 根据给定的数据,建立哈夫曼树;
(2)输出每个叶子结点的带权路径长度;
(3)输出哈夫曼树的WPL值。
测试数据要求:输入的n个权值之和应为100,且不允许有负值。
1.首先要构造一棵哈夫曼树,哈夫曼树的结点结构包括权值,双亲,左右孩子;假如由n个字符来构造一棵哈夫曼树,则共有结点2n-1个;在构造前,先初始化,初始化操作是把双亲,左右孩子的下标值都赋为0;然后依次输入每个结点的权值
2.第二步是通过n-1次循环,每次先找输入的权值中最小的两个结点,把这两个结点的权值相加赋给一个新结点,,并且这个新结点的左孩子是权值最小的结点,右孩子是权值第二小的结点;鉴于上述找到的结点都是双亲为0的结点,为了下次能正确寻找到剩下结点中权值最小的两个结点,每次循环要把找的权值最小的两个结点的双亲赋值不为0(i).就这样通过n-1循环下、操作,创建了一棵哈夫曼树,其中,前n个结点是叶子(输入的字符结点)后n-1个是度为2的结点
3.编码的思想是逆序编码,从叶子结点出发,向上回溯,如果该结点是回溯到上一个结点的左孩子,则在记录编码的数组里存“0”,否则存“1”,注意是倒着存;直到遇到根结点(结点双亲为0),每一次循环编码到根结点,把编码存在编码表中,然后开始编码下一个字符(叶子)
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
struct BTreeNode
{
ElemType data;
struct BTreeNode* left;
struct BTreeNode* right;
};
//1、根据数组 a 中 n 个权值建立一棵哈夫曼树,返回树根指针
struct BTreeNode* CreateHuffman(ElemType a[], int n)
{
int i, j;
struct BTreeNode **b, *q;
b = (BTreeNode**)malloc(n*sizeof(struct BTreeNode));
for (i = 0; i < n; i++) //初始化b指针数组,使每个指针元素指向a数组中对应的元素结点
{
b[i] = (BTreeNode*)malloc(sizeof(struct BTreeNode));
b[i]->data = a[i];
b[i]->left = b[i]->right = NULL;
}
for (i = 1; i < n; i++)//进行 n-1 次循环建立哈夫曼树
{
//k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标
int k1 = -1, k2;
for (j = 0; j < n; j++)//让k1初始指向森林中第一棵树,k2指向第二棵
{
if (b[j] != NULL && k1 == -1)
{
k1 = j;
continue;
}
if (b[j] != NULL)
{
k2 = j;
break;
}
}
for (j = k2; j < n; j++)//从当前森林中求出最小权值树和次最小
{
if (b[j] != NULL)
{
if (b[j]->data < b[k1]->data)
{
k2 = k1;
k1 = j;
}
else if (b[j]->data < b[k2]->data)
k2 = j;
}
}
//由最小权值树和次最小权值树建立一棵新树,q指向树根结点
q = (BTreeNode*)malloc(sizeof(struct BTreeNode));
q->data = b[k1]->data + b[k2]->data;
q->left = b[k1];
q->right = b[k2];
b[k1] = q;//将指向新树的指针赋给b指针数组中k1位置
b[k2] = NULL;//k2位置为空
}
free(b); //删除动态建立的数组b
return q; //返回整个哈夫曼树的树根指针
}
//2、求哈夫曼树的带权路径长度
ElemType WeightPathLength(struct BTreeNode* FBT, int len)//len初始为0
{
if (FBT == NULL) //空树返回0
return 0;
else
{
if (FBT->left == NULL && FBT->right == NULL)//访问到叶子结点
return FBT->data * len;
else //访问到非叶子结点,进行递归调用,返回左右子树的带权路径长度之和,len递增
return WeightPathLength(FBT->left,len+1)+WeightPathLength(FBT->right,len+1);
}
}
//3、根据哈夫曼编码,计算每个叶子结点的带权路径长度(可以根据哈夫曼树带权路径长度的算法基础上进行修改)
void HuffManCoding(struct BTreeNode* FBT, int len)//len初始值为0
{
int temp = 0;
static int a[10];//定义静态数组a,保存每个叶子的编码,数组长度至少是树深度减一
if (FBT != NULL)//访问到叶子结点时输出其保存在数组a中的0和1序列编码
{
if (FBT->left == NULL && FBT->right == NULL)
{
int i;
printf("权值为%d的叶子结点的带权路径长度为:", FBT->data);
for (i = 0; i < len; i++) {
temp+=1;
//printf("%d",a[i]); 输出每个叶子结点对应的哈夫曼编码
}
printf("%d",temp);
printf("\n");
}
else//访问到非叶子结点时分别向左右子树递归调用,并把分支上的0、1编码保存到数组a
{ //的对应元素中,向下深入一层时len值增1
a[len] = 0;
HuffManCoding(FBT->left, len + 1);
a[len] = 1;
HuffManCoding(FBT->right, len + 1);
}
}
}
//主函数
int main()
{
int n, i;
ElemType* a;
struct BTreeNode* fbt;
printf("请输入想要构造的哈夫曼树中带权叶子结点数n:");
while(true)
{
scanf("%d", &n);
if (n > 1)
break;
else
printf("重输n值:");
}
printf("\n");
a = (ElemType*)malloc(n*sizeof(ElemType));
printf("请输入%d个整数作为权值:", n);
for (i = 0; i < n; i++)
scanf(" %d", &a[i]);
fbt = CreateHuffman(a, n);
printf("\n");
//printf("树中每个叶子结点的哈夫曼编码:\n");
//HuffManCoding(fbt, 0);
printf("树中每个叶子结点的带权路径长度:\n");
printf("\n");
HuffManCoding(fbt, 0);
printf("\n");
printf("哈夫曼树的WPL为:");
printf("%d\n", WeightPathLength(fbt, 0));
}
分析:
WPL()函数中计算了每个叶子节点的权值乘以它的路径长度之和,因为代码中规定了n个计算叶子节点,我们用到了n个叶子节点的信息所以这个函数的时间复杂度为O(n)
PrintCodeTable()为输出每个叶子节点的编码和路径长度,假设叶子节点个数为n个,那么这个函数的时间复杂度为O(n)
ConstructHuffmanTree()函数中,又n个叶子节点那么就会存在2n-1个节点,那么这个函数的时间复杂度为O(2n)
Select()函数是在叶子节点中选择两个节点较小的,最坏的情况是全部遍历我们的叶子节点,这个时候的时间复杂度为O(n)
3.哈希表的应用
哈希表的应用
基本要求:
根据给定的一组整数,建立哈希表。
1)设计哈希函数;
(2)分别采用线性探测再散列法和链地址法解决冲突;
(3)对哈希表进行查找,输出查找成功和不成功的信息。
测试数据要求:建立哈希表时输入的数据可以有相同的值。
1 开放地址法
这个方法的基本思想是:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。这个过程可用下式描述:
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))
其中: H ( key ) 为关键字 key 的直接哈希地址, m 为哈希表的长度, di 为每次再探测时的地址增量。
采用这种方法时,首先计算出元素的直接哈希地址 H ( key ) ,如果该存储单元已被其他元素占用,则继续查看地址为 H ( key ) + d 2 的存储单元,如此重复直至找到某个存储单元为空时,将关键字为 key 的数据元素存放到该单元。
增量 d 可以有不同的取法,并根据其取法有不同的称呼:
( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列;
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再 散列;
( 3 ) d i = 伪随机序列 伪随机再散列;
例1设有哈希函数 H ( key ) = key mod 7 ,哈希表的地址空间为 0 ~ 6 ,对关键字序列( 32 , 13 , 49 , 55 , 22 , 38 , 21 )按线性探测再散列和二次探测再散列的方法分别构造哈希表。
解:
( 1 )线性探测再散列:
32 % 7 = 4 ; 13 % 7 = 6 ; 49 % 7 = 0 ;
55 % 7 = 6 发生冲突,下一个存储地址( 6 + 1 )% 7 = 0 ,仍然发生 冲突,再下一个存储地址:( 6 + 2 )% 7 = 1 未发生冲突,可以存入。
22 % 7 = 1 发生冲突,下一个存储地址是:( 1 + 1 )% 7 = 2 未发生 冲突;
38 % 7 = 3 ;
21 % 7 = 0 发生冲突,按照上面方法继续探测直至空间 5 ,不发生冲突, 所得到的哈希表对应存储位置:
下标: 0 1 2 3 4 5 6
49 55 22 38 32 21 13
( 2 )二次探测再散列:
下标: 0 1 2 3 4 5 6
49 22 21 38 32 55 13
注意:对于利用开放地址法处理冲突所产生的哈希表中删除一个元素时需要谨慎, 不能直接地删除,因为这样将会截断其他具有相同哈希地址的元素的查找地址,所 以,通常采用设定一个特殊的标志以示该元素已被删除。
2 链地址法
链地址法解决冲突的做法是:如果哈希表空间为 0 ~ m - 1 ,设置一个由 m 个指针分量组成的一维数组 ST[ m ], 凡哈希地址为 i 的数据元素都插入到头指针为 ST[ i ] 的链表中。这种方法有点近似于邻接表的基本思想,且这种方法适合于冲突比较严重的情况。
#include <stdio.h>
#include<stdio.h>
#include<stdlib.h>
#define Max 20//初始大小
#define listincrement 10//增量
#define MOD 8
typedef struct {
int *data;//数据存储
int length;//表的长度
int listsize;//申请空间大小
} sqlist; //顺序表
typedef struct {
int *data;//哈希表数据存储
int length;//表的长度
int listsize;//申请空间大小
} Hsqlist; //线性哈希表
typedef struct Lnode {
int data;
struct Lnode *next;
} Lnode,*HLinklist;
typedef struct HNode {
Lnode *firstarc;//指向第一个依附于该点的指针
} HNode,Hlist[Max];
typedef struct {
Hlist vertices;//哈希表链地址
int listnum;//哈希地址个数
} HashTable;
//建立顺序表
void Initlist(sqlist &l) {
l.data=(int*)malloc(Max*sizeof(int));
l.listsize=Max;
printf("请输入线性表的大小(不得超过8):");
scanf("%d",&l.length);
while(l.length>8){
printf("输入的数据有误,请重新输入:");
scanf("%d",&l.length);
}
printf("请输入数据元素:");
for(int i=0; i<l.length; i++) {
scanf("%d",&l.data[i]);
}
}
//转化哈希表 线性探测再散列法
void InitHsqlist(sqlist L1,Hsqlist &L2) {
int i,j,key;
L2.length=MOD;
L2.listsize=Max;
L2.data=(int*)malloc(Max*sizeof(int));
for(i=0; i<Max; i++) //初始化哈希表
L2.data[i]=0;
for(i=0; i<L1.length; i++) {
key = L1.data[i]%MOD;
if(L2.data[key]) {
for(j=1;j<L1.length; j++) {
key=(L1.data[i]%MOD+j)%8;
if(L2.data[key] == 0)
break;
}
}
L2.data[key]=L1.data[i];
printf("%d-%d\n",key,L1.data[i]);
}
}
//链地址法解决冲突
void InitHashTable(sqlist L1,HashTable &L2) {
Lnode *p,*q;
int i,j,key;
L2.listnum = L1.length;
for(i = 0 ; i < Max ; i ++)
L2.vertices[i].firstarc=NULL; //链地址的初始化
for(i = 0; i < L1.length; i++) {
p=(Lnode*)malloc(sizeof(Lnode));
p->data=L1.data[i];
p->next=NULL;
key=L1.data[i]%8;
q=L2.vertices[key].firstarc;
if(q) {
while(q->next)
q=q->next ;
q->next=p;
} else
L2.vertices[key].firstarc=p;
}
return;
}
//查找 线性探测再散列法哈希表
void LookupHsqlist(Hsqlist H) {
int n;
printf("根据线性探测再散列法查找元素:\n");
printf("请输入想要查找的元素:");
scanf("%d",&n);
int temp;
temp = n%8;
for (int i = temp;i<8;i++){
if(H.data[i] == n){
printf("已成功找到!并且他的哈希地址为%d!\n",n%8);
return ;
}
}
printf("未找到想要查找的元素!\n");
return;
}
//查找 链地址法解决冲突哈希表
void LookupHsqlist(HashTable H) {
int n;
Lnode *temp;
printf("根据链地址法给出的哈希地址查找元素:");
scanf("%d",&n);
temp=H.vertices[n].firstarc;
printf("查找元素为:");
while(temp) {
printf("%5d",temp->data);
temp=temp->next;
}
}
int main() {
sqlist L1;
Hsqlist L2;
HashTable L3;
Initlist(L1);
InitHsqlist(L1,L2);//转化哈希表 线性探测再散列法
//线性探测再散列法
printf("线性探测再散列法输出哈希表:\n");
int i;
for(i=0; i<8; i++)
printf("%5d",L2.data[i]);
printf("\n");
InitHashTable(L1,L3);// 链地址法解决冲突
//链地址法输出哈希表
printf("链地址法输出哈希表:\n");
HLinklist p,q;
for(i=0; i<8; i++) {
p=L3.vertices[i].firstarc;
printf("%d----",i);
while(p) {
printf("%d->",p->data);
p=p->next;
}
putchar('\n');
}
LookupHsqlist(L2);//查找
LookupHsqlist(L3);
return 0;
}
分析:
Initlist()函数中我们输入了表长为n我们进行n次循环读取我们输入的n个数据,所以这里的时间复杂度为O(n)
ChazhaoHsqlist()函数为查找函数对n个数据进行查找最坏的情况下是把全部
存储的数据挨个进行比较,时间复杂度为O(n)
InitHashTable()链地址法解决冲突,最坏的情况是把整个空间全部查找一遍寻找空闲位置,所以时间复杂度为O(n)
4. 汽车牌照的快速查询
汽车牌照的快速查询
对一组汽车牌照进行排序和查找。
基本要求:
(1)利用排序算法对汽车牌照进行排序;
(2)采用折半查找思想完成查找。
测试数据要求:
测试的数据不得少于50个,不得有重复的牌照。车牌号中可以是数字和字符的组合,车牌号可以人工输入,也可以自动生成。
折半查找算法:首先,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
重复以上步骤,直到找到满足条件的结果为止,若找不到,则返回失败。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
typedef char Car[10];
typedef struct {
Car *elem;
char key[600][600];
int length;
} LinkList;
//随机产生你想要的数量的车牌号
void Input(LinkList &L) {
srand(time(0));
printf("请输入想要产生的车牌号数量(至少50个,至多100个):");
scanf("%d",&L.length);
printf("\n");
//判断输入的数据是否有误
while(L.length<50||L.length>100) {
printf("输入的数据有误,请重新输入:");
scanf("%d",&L.length);
printf("\n");
}
//根据输入的车牌号数量,重新分配空间
L.elem=(Car*)malloc(L.length*sizeof(Car));
//随机产生含有大小写字母以及数字的6车牌号
for(int i=0; i<=L.length-1; i++) {
for(int j=0; j<6; j++) {
int flag=rand()%2;
if(flag == 1)
L.key[i][j] = rand()%26+'A'; //随机产生大写字母
else
L.key[i][j] = rand()%10+'0'; //随机产生数字
}
strcpy(L.elem[i],L.key[i]);
}
}
//选择排序法,对车牌号进行排序
void BubbleSort(LinkList &L) {
char a[20];
int minIndex;
for(int i = 0; i<L.length-1; i++) {
minIndex = i;
for(int j = i+1; j<L.length; j++) {
if(strcmp(L.elem[j],L.elem[minIndex])<0)//strcmp是比较两个字符串的大小,两个字符串相同时返回0,第一个字符串大于第二个字符串时返回一个正值,否则返回负值.
minIndex = j;
}
strcpy(a,L.elem[i]);
strcpy(L.elem[i],L.elem[minIndex]);
strcpy(L.elem[minIndex],a);
}
}
//利用折半查找查找车牌号是否存在
void Binsrch(LinkList &L,char *a) {
int low,high,mid;
int flag=1;
low=0;
high=L.length-1;
while(low<=high&&flag) {
mid=(low+high)/2;
if(strcmp(a,L.elem[mid])==0) {
printf("查找的该车牌号是第%d个!\n",mid+1);
flag=0;
} else if(strcmp(a,L.elem[mid])>0) {
low=mid+1;
mid=(low+high)/2;
} else {
high=mid-1;
mid=(low+high)/2;
}
}
if(flag)
printf("查找的车牌号不存在!\n");
}
//输出所有的车牌号
void Output(LinkList &L) {
int i;
for(i=0; i<L.length; i++)
printf("%s\n",L.elem[i]);
}
int main() {
int i;
LinkList L;
char car[20];
while(1) {
printf("——————————请选择操作 ——————————\n");
printf("——————————1.输入信息 ——————————\n");
printf("——————————2.车牌号排序——————————\n");
printf("——————————3.车牌号查询——————————\n");
printf("——————————0.退出 ——————————\n");
scanf("%d",&i);
while(true) {
if(i == 1) {
Input(L);
break;
}
else if(i==2) {
printf("车牌号的排序结果为:\n");
BubbleSort(L);
Output(L);
break;
}
else if(i==3) {
printf("输入要查找的车牌号:\n");
getchar();
gets(car);
Binsrch(L,car);
break;
}
else
return 0;
}
}
return 0;
}
Input()函数中我们输入我们需要对车号排序的数量,需要我们输入n个函数对其进行读取,所以这里的时间复杂度为O(n)
BubbleSort()函数对我们输入的车牌号进行排序,里边用到了冒泡排序,那么这个函数的时间复杂度为O()
Binsrch()函数为使用二分查找对我们输入的车牌号进行查找,,那么函数时间复杂度为 O()
Output()输出车牌信息,时间复杂度为O(n)