天勤的内容
1.用栈实现表达式的转换
1.1中缀转后缀
规则:从左到右扫描表达式,循环进行以下步骤:如果遇到操作数就把它写出来。如果遇到运算符就把它入栈,但在入栈之前需要拿当前运算符与栈顶运算符进行一下比较,比较它们的运算优先级,如果当前运算符小于或等于栈顶运算符,就把栈顶运算符出栈,并将其写入到当前得到的结果表达式中。然后继续比较,如果优先级还是小于或等于,就继续把栈顶运算符出栈,直到当前的运算符大于栈顶运算符才将当前运算符入栈。栈空的情况下,直接入栈。当遇到栈顶元素是左括号的情况下也是直接入栈,直到扫描到当前运算符是右括号时才依次将栈中元素出栈,直到左括号,并把左括号出栈,不写会结果表达式中,直接把左括号扔掉。当扫描完整个表达式后,若栈还不为空,则把栈中全部的运算符出栈并写入结果表达式中。
代码如下:
//判断优先级的函数
int getPriority(char op){
if(op==‘+’||op==‘-’)
return 0;
else
return 1;
}
1
2
3
4
5
6
7
//判断优先级的函数
int getPriority(char op){
if(op==‘+’||op==‘-’)
return 0;
else
return 1;
}
void infixToPostFix(char infix[],char s2[],int &top2){ //infix[]为中缀表达式,s2[]用来存放后缀表达式,注意这里栈顶是引用型
char s1[maxSize]; int top1=-1; //新建一个栈并设置栈顶
int i=0;
while(infix[i]!=‘\0’){
if(‘0’<=infix[i]&&infix[i]<=‘9’){ //默认表达式中数字都为个位数
s2[++top2]=infix[i];
++i;
}else if(infix[i]‘(’){
s1[++top1]=‘(’;
++i;
}else if(infix[i]‘+’||infix[i]‘-’||infix[i]‘*’||infix[i]‘/’){
if(top1-1||s1[top1]‘(’||getPriority(infix[i])>getPriority(s1[top1])){
s1[++top1]=infix[i];
i++;
}else{
s2[++top2]=s1[top1–];
}
}else if(infix[i]‘)’){
while(s1[top1]!=‘(’){
s2[++top2]=s1[top1–];
}
–top1; //将左括号出栈,并扔掉
++i;
}
}
while(top1!=-1){ //若栈不为空,则依次将栈中的运算符出栈到结果表达式中
s2[++top2]=s1[top1–];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1.2中缀转前缀
规则:是一个跟中缀转后缀一个相反的过程。从右往左扫描表达式,这里是遇到右括号入栈,直到遇到左括号全部出栈。如果遇到运算符就把它入栈,但在入栈之前需要拿当前运算符与栈顶运算符进行一下比较,比较它们的运算优先级,如果当前运算符小于栈顶运算符,就把栈顶运算符出栈,并将其写入到当前得到的结果表达式中。然后继续比较,如果优先级还是小于,就继续把栈顶运算符出栈,直到当前的运算符大于或等于栈顶运算符才将当前运算符入栈。
代码如下:
//判断优先级的函数
int getPriority(char op){
if(op==‘+’||op==‘-’)
return 0;
else
return 1;
}
void infixToPostFix(char infix[],int len,char s2[],int &top2){ //infix[]为中缀表达式,len为中缀表达式的长度,s2[]用来存放前缀表达式,注意这里栈顶是引用型
char s1[maxSize]; int top1=-1; //新建一个栈并设置栈顶
int i=len-1; //与转后缀的不同处
while(i>=0){
if(‘0’<=infix[i]&&infix[i]<=‘9’){ //默认表达式中数字都为个位数
s2[++top2]=infix[i];
–i; //与转后缀的不同处
}else if(infix[i]‘)’){ //与转后缀的不同处
s1[++top1]=‘)’;
–i;
}else if(infix[i]‘+’||infix[i]‘-’||infix[i]‘*’||infix[i]‘/’){
if(top1-1||s1[top1]‘)’||getPriority(infix[i])>=getPriority(s1[top1])){ //与转后缀的不同处,这里是大于等于
s1[++top1]=infix[i];
–i;
}else{
s2[++top2]=s1[top1–];
}
}else if(infix[i]‘(’){ //与转后缀的不同处
while(s1[top1]!=‘)’){
s2[++top2]=s1[top1–];
}
–top1; //将左括号出栈,并扔掉
–i;
}
}
while(top1!=-1){ //若栈不为空,则依次将栈中的运算符出栈到结果表达式中
s2[++top2]=s1[top1–];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1.3后缀转前缀
规则:当两个子表达式遇到运算符时,出栈,并把第一个出栈的表达式放在右边,第二个出栈的表达式放在左边,运算符放在最前面,然后再次入栈。循环进行这个过程。
2.用栈实现表达式的计算
2.1用栈求中缀表达式的值(需要两个栈)
规则:需要两个栈(s1和s2),一个用来暂存操作数,一个用来暂存运算符。当遇到操作数的时候就入s1栈;遇到左括号的时候,就入s2栈;当遇到运算符的时候就得进行判断,如果此时s2栈空或s2栈顶为左括号就直接入s2栈,如果此时扫描到的运算符大于栈顶运算符的优先级则入s2栈,否则把s2栈顶的运算符出栈,同时从s1栈中出栈两个操作数并进行一次运算,而第一次出栈的操作数在右边,第二次出栈的操作数在左边,并将运算结果入s1栈,循环执行这个过程,直到当前扫描到的运算符的优先级小于或等于当前栈顶运算符的优先级,才把当前扫描到的运算符入栈;如果遇到的是右括号,则连续出栈运算符直到遇到左括号,每次都执行上述的运算并把运算结果入s1栈;最后,当扫描完整个表达式时,如果s2栈中还有运算符,则依次出栈,进行运算并把运算结果压入s1栈中,当s2栈空时,s1栈顶元素就是最后的运算结果。
举个例子:
代码如下:
//判断优先级的函数
int getPriority(char op){
if(op==‘+’||op==‘-’)
return 0;
else
return 1;
}
//计算两个操作是结果给引用型result,然后返回本次运算是否符合运算规则
int calSub(float opand1,char op,float opand2,float &result){
if(op==‘+’) result=opand1+opand2;
if(op==‘-’) result=opand1-opand2;
if(op==‘') result=opand1opand2;
if(op==’/‘){
if(fabs(opand2)<MIN){
return 0;
}else{
result=opand1/opand2;
}
}
return 1;
}
//从s2出栈一个运算符,从s1栈出栈两个操作数,并执行计算
int calStackTopTwo(float s1[],int &top1,char s2[],int &top2){
float opnd1,opnd2,result;
char op;
int flag;
opnd2=s1[top1–]; //注意第一个出栈的数是右边的操作数
opnd1=s1[top1–];
op=s2[top2–];
flag=calSub(opnd1,op,opnd2,result);
if(flag0){
std::cout<<“ERROR”<<std::endl; //这是c++的写法,c的写法是puts(“ERROR”)
}
s1[++top1]=result;
}
float calInfix(char exp[]){
float s1[maxSize]; int top1=-1; //创建s1栈
float s2[maxSize]; int top2=-1; //创建s2栈
int i=0;
while(exp[i]!=‘\0’){
if(‘0’<=exp[i]&&exp[i]<=‘9’){
s1[++top1]=exp[i]-‘0’;
++i;
}else if(exp[i]’(‘){
s2[++top2]=’(‘;
++i;
}else if(exp[i]‘+’||exp[i]’-‘||exp[i]‘*’||exp[i]’/'){
if(top2==-1||s2[top2]‘(’||getPriority(exp[i])>getPriority(exp[top2])){
s2[++top2]=exp[i];
++i;
}else{
int flag=calStackToTwo(s1,top1,s2,top2);
if(flag0){
return 0;
}
}
}else if(exp[i]‘)’){
while(s2[top2]!=‘(’){
int flag=calStackToTwo(s1,top1,s2,top2);
if(flag0){
return 0;
}
–top2; //将左括号出栈
++i; //直接跳过右括号
}
}
}
while(top2!=-1){ //扫描完整个表达式后,s2栈非空
int flag=calStackToTwo(s1,top1,s2,top2);
if(flag==0){
return 0;
}
}
return s1[top1];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
2.2用栈求后缀表达式的值(只需一个栈)
规则:从左到右扫描整个表达式,当遇到操作数的时候就入栈,当遇到运算符的时候,就从栈中弹出两个操作数并计算结果后压入栈中,当要注意的是,第一个弹出的操作数在右边,第二个弹出的操作数在左边。当整个扫描过程结束后,栈顶就是运算结果。
举个例子:
代码如下:
int calSub(float opand1,char op,float opand2,float &result){
if(op==‘+’) result=opand1+opand2;
if(op==‘-’) result=opand1-opand2;
if(op==‘') result=opand1opand2;
if(op==’/'){
if(fabs(opand2)<MIN){
return 0;
}else{
result=opand1/opand2;
}
}
return 1;
}
float calPostFix(char exp[]){
float s[maxSize]; int top =-1;
for(int i=0;exp[i]!=‘\0’;++i){
if(‘0’<=exp[i]&&exp[i]<=‘9’){
s[++top]=exp[i]-‘0’;
}else{
float opnd1,opnd2,result;
char op;
int flag;
opnd2=s[top–];
opnd1=s[top–];
op=exp[i];
flag=calSub(opnd1,op,opnd2,result);
if(flag==0){
std::cout<<“ERROR”<<std::endl;
break;
}
s[++top]=result;
}
}
return s[top];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2.3用栈求前缀表达式的值(只需一个栈)
规则:从右往左扫描整个表达式,遇到操作数就入栈,遇到运算符就出栈两个操作数。不过与后缀表达式不同的是,第一次出栈的数在左边,第二次出栈的数在右边。
举个例子:
代码如下:
int calSub(float opand1,char op,float opand2,float &result){
if(op==‘+’) result=opand1+opand2;
if(op==‘-’) result=opand1-opand2;
if(op==‘') result=opand1opand2;
if(op==’/'){
if(fabs(opand2)<MIN){
return 0;
}else{
result=opand1/opand2;
}
}
return 1;
}
float calPreFix(char exp[],int len){
float s[maxSize]; int top=-1;
for(int i=len-1;i>=0;–i){
if(‘0’<=exp[i]&&exp[i]<=‘9’){
s[++top]=exp[i]-‘0’;
}else{
float opnd1,opnd2,result;
char op;
int flag;
opnd1=s[top–];
opnd2=s[top++];
op=exp[i];
flag=calSub(opnd1,op,opnd2,result);
if(flag==0){
std::cout<<“ERROR”<<std::endl;
return 0;
}
s[++top]=result;
}
}
return s[top];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
3.循环队列的配置问题
往年真题中默认循环队列的性质,在这里我们称为正常配置。
3.1正常配置
队空时,头指针和尾指针指向同一个位置。
入队时,尾指针前移一位。先移动指针再入队元素。
出队时,头指针也前移一位。
当继续出队时,就队空了,即头指针和尾指针指向同个位置。
我们应该留出一个位置来区分队空和队满。队满时,头指针和尾指针相差一个位置。
以上就是一般题目默认的正常配置,但当题目有给限定条件时,就不要直接按照这个正常配置来。
3.2非正常配置
曾经在考题中出现过的非正常配置,也就是没有按照上面的正常配置的循环队列。
3.2.1第一种情况
队空的情况跟正常配置一样。头指针和尾指针指向同一个地方。
这里就有所不同了,是先入队元素再移动指针。入队一个元素后,再把尾指针前移一位。
出队也是先出头指针指向的元素,再移动头指针。
队满的情况跟正常配置一样。如果尾指针再前移一位后取余等于头指针则队满。
小结:跟正常配置不同的就是在入队跟出队这里,而这样导致的结果就是,头指针指向队头元素,而尾指针指向队尾元素的后一位。正常配置是头指针指向队头元素的前一个,尾指针指向队尾元素。
计算队中元素的公式是跟正常配置一样的。有可能中间推导过程有点不一样。
3.2.2第二种情况
队空的情况是头指针和尾指针相差一个位置,判断条件是正常配置的判空条件。
入队时是先移动尾指针,再入队元素。
队满时,头指针和尾指针相隔一个位置,所以这里判断队满是得+2。如果是不空一个位置,全部占满,则头指针和尾指针只相差一个位置,则判断条件跟判空条件是一样的,所以不行,得空一个位置。
总结:只要题目设计符合队列的情况,就都可以配置进行变化,所以这里要多注意一下。
4.用栈模拟队列
5.用栈解决括号匹配问题
规则:从左到右扫描整个表达式,如果遇到左括号则入栈;如果遇到右括号就出栈,如果此时栈空则括号不匹配,如果可以出栈,则判断左右括号是否匹配,如果匹配则继续扫描,否则直接判断不匹配。当扫描完整个表达式后,栈中还有元素,则说明不匹配。
举个例子:
代码如下:
int isMatched(char left,char right){
if(left==‘(’&&right==‘)’){
return 1;
}else if(left==‘[’&&right==‘]’){
return 1;
}else if(left==‘{’&&right==‘}’){
return 1;
}else{
return 0;
}
}
int isParenthesesBalanced(char exp[]){
char s[maxSize]; int top=-1;
for(int i=0;exp[i]!=‘\0’;i++){
if(exp[i]‘(’||exp[i]‘[’||exp[i]‘{’){
s[++top]=exp[i];
}
if(exp[i]‘)’||exp[i]‘]’||exp[i]‘}’){
if(top==-1){ //栈为空
return 0;
}
char left=s[top–];
if(isMatched(left,exp[i])==0){
return 0;
}
}
}
if(top>-1){ //栈非空
return 0;
}
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
6.数组
数组和矩阵部分可以直接看下面王道对应的内容,已经有插图和加以解释了。
7.广义表
7.1逻辑结构
广义表(Lists,又称列表)是一种非连续性的数据结构,是线性表的一种推广。即广义表中放松对表元素的原子限制,容许它们具有其自身结构。
广义表中的每个元素即可以是原子,也可以是广义表。突破了线性表带表元素的限制,线性表中的每个元素都是不可再分的原子。
GetTail()是取表尾元素,所以也是一个广义表,要加括号。
7.2存储结构
7.2.1头尾链表存储结构
“1”代表是广义表结点,“0”代表原子结点。
7.2.2扩展线性表存储结构
王道的内容
1.栈
1.1顺序栈
1.2链栈
链栈相当于单链表,插入和删除都在头节点(开始结点)操作。
2.队列
栈和队列都属于线性表,只不过操作所限。
2.1顺序实现队列(循环队列)
2.1.1入队操作
把顺序实现的队列,经过取模变成逻辑是的“环”,也就是循环队列。上面这种情况,队满是牺牲一个存储单元的。
2.1.2出队操作
2.1.3方案一:循环队列的判空、判满条件
2.1.4方案二:循环队列的判空、判满条件
2.1.5方案三:循环队列的判空、判满条件
2.1.6其他出题方式
第一种是队尾指针指向队尾元素的后一个位置。还有一种出题方式就是队尾指针指向队尾元素。
2.1.7小结
2.2链式实现队列
2.2.1入队操作
2.2.2出队操作
3.栈的应用——表达式求值
3.1中缀式转后缀式(手算 适合作选择题)
3.2中缀式转前缀式(手算 适合作选择题)
3.3中缀式转后缀式(机算)
这里可以看作是天勤的补充,有解释这些运算为什么要有这样子的顺序。
当表达式中存在有括号的情况下:
当遇到运算符出现我们确定不了运算顺序的时候,我们都可以先压入到栈里。
因为括号内的运算,我们需要先运算,所以就括号内的运算可以直接生效。
3.4中缀表达式的计算
4.栈的应用——递归
在编程的过程中,可以自定义栈,将递归算法改造成非递归算法。
5.特殊矩阵压缩存储
5.1一维数组的存储结构
5.2二维数组的存储结构
5.2.1行优先存储
5.2.2列优先存储
5.3普通矩阵的存储
5.4对称矩阵的压缩存储
5.4.1行优先原则
总结起来就是这样:
5.4.2列优先原则
5.5三角矩阵的压缩存储
5.6三对角矩阵的压缩存储
5.7稀疏矩阵的压缩存储
5.7.1顺序存储
5.7.2十字链表法
5