第一章 绪论
第一节 什么是数据结构?
估猜以下软件的共性:学生信息管理、图书信息管理、人事档案管理。
数学模型:用符号、表达式组成的数学结构,其表达的内容与所研究对象的行为、特性基本一致。
信息模型:信息处理领域中的数学模型。
数据结构:在程序设计领域,研究操作对象及其之间的关系和操作。
忽略数据的具体含义,研究信息模型的结构特性、处理方法。
第二节 概念、术语
一、有关数据结构的概念
数据:对客观事物的符号表示。
例:生活中还有什么信息没有被数字化?
身份证,汽车牌号,电话号码,条形代码……
数据元素:数据的基本单位。相当于"记录"。
一个数据元素由若干个数据项组成,相当于"域"。
数据对象:性质相同的数据元素的集合。
数据结构:相互之间存在特定关系的数据集合。
四种结构形式:集合、线性、树形、图(网)状
形式定义:(D,S,P)
D:数据元素的集合(数据对象)
S:D上关系的有限集
P:D上的基本操作集
逻辑结构:关系S描述的是数据元素之间的逻辑关系。
存储结构:数据结构在计算机中的存储形式。
顺序映象、非顺序映象、索引存储、哈希存储
逻辑结构与存储结构的关系:
逻辑结构:描述、理解问题,面向问题。
存储结构:便于机器运算,面向机器。
程序设计中的基本问题:逻辑结构如何转换为存储结构?
二、有关数据类型的概念
数据类型:值的集合和定义在该值集上的一组操作的总称。
包括:原子类型、结构类型。
抽象数据类型(ADT):一个数学模型及该模型上的一组操作。
核心:是逻辑特性,而非具体表示、实现。
课程任务:
学习ADT、实践ADT。
如:线性表类型、栈类型、队列类型、数组类型、广义表类型、树类型、图类型、查找表类型……
实践指导:
为了代码的复用性,采用模块结构。
如:C中的头文件、C++中的类
第三节 ADT的表示与实现
本教材中,算法书写习惯的约定。
数据元素类型ElemType:int,float,char, char[] ……
引用参数 &
算法:
void add(int a,int b,int &c) { c=a+b; }
程序:
void add(int a,int b,int *p_c){ *p_c=a+b; }
第四节 算法的描述及分析
一、有关算法的概念
算法:特定问题求解步骤的一种描述。
1)有穷性 2)确定性 3)可行性
二、算法设计的要求
好算法:
1)正确性 2)可读性 3)健壮性 4)高效,低存储
三、时间复杂度
事前估算:问题的规模,语言的效率,机器的速度
时间复杂度:在指定的规模下,基本操作重复执行的次数。
n:问题的规模。
f(n):基本操作执行的次数
T(n)=O(f(n)) 渐进时间复杂度(时间复杂度)
例:求两个方阵的乘积 C=AB
void MatrixMul(float a[][n],float b[][n],float c[][n])
{ int i,j,k;
for(i=0; i<n; i++) // n+1
for(j=0; j<n; j++) // n(n+1)
{ c[i][j]=0; // nn
for(k=0; k<n; k++) // nn(n+1)
c[i][j]+ = a[i][k] * b[k][j]; // nnn
}
}
时间复杂度:
一般情况下,对循环语句只需考虑循环体中语句的执行次数,可以忽略循环体中步长加1、终值判断、控制转移等成分。
最好/最差/平均时间复杂度的示例:
例:在数组A[n]中查找值为k的元素。
for(i=0; i<n-1; i++)
if(A[i]==k) return i;
四、常见时间复杂度
按数量级递增排序:
< < < < <
< < <
将指数时间算法改进为多项式时间算法:伟大的成就。
五、空间复杂度
实现算法所需的辅助存储空间的大小。
S(n)=O(f(n)) 按最坏情况分析。
六、算法举例
例1:以下程序在数组中找出最大值和最小值
void MaxMin(int A[], int n)
{ int max, min, i;
max=min=A[0];
for(i=1; i<n; i++)
if(A[i]>max) max=A[i];
else if(A[i]<min) min=A[i];
printf(“max=%d, min=%d\n”, max, min);
}
若数组为递减排列,比较次数是多少?
if(A[i]>max):n-1次
if(A[i]<min): n-1次
若数组为递增排列,比较次数是多少?
if(A[i]>max):n-1次
if(A[i]<min): 0次
例2:计算f(x)=a0+a1x+a2x2+…+anxn
解法一:先将x的幂存于power[],再分别乘以相应系数。
float eval(float coef[],int n,float x)
{ float power[MAX], f;
int i;
for(power[0]=1,i=1;i<=n;i++)
power[i]=x*power[i-1];
for(f=0,i=0;i<=n;i++)
f+=coef[i]power[i];
return(f);
}
解法二:f(x)=a0+(a1+(a2+……+(an-1+anx)x)… x)x
f(x)=a0+(a1+(a2+(a3+(a4+a5x)x)x)x)x
float eval(float coef[],int n,float x)
{ int i; float f;
for(f=coef[n],i=n-1;i>=0;i–)
f=fx+coef[i];
return(f);
}
五、思考题
1、问:“s=s+ij;”的执行次数?时间复杂度?
for(i=1;i<=n;i++)
if(5i<=n)
for(j=5i;j<=n;j++)
s=s+ij;
2、问:“a[i][j]=x;”的执行次数?时间复杂度?
for(i=0; i<n; i++)
for(j=0; j<=i; j++) a[i][j]=x;
第二章 线性表
线性结构:在数据元素的非空集中,
①存在唯一的一个首元素,
②存在唯一的一个末元素,
③除首元素外每个元素均只有一个直接前驱,
④除末元素外每个元素均只有一个直接后继。
第一节 逻辑结构
形式定义:
Linear_list=(D,S,P)
D = {ai| ai∈ElemSet, i=0,1,2,…,n-1}
S = {<ai-1,ai>| ai-1,ai∈D, i=1,2,…,n-1}
<ai-1,ai>为序偶,表示前后关系
基本操作P:
①插入、删除、修改,存取、遍历、查找。
void ListAppend(List L, Elem e) ;
void ListDelete(List L, int i) ;
int SetElem(List L, int i, Elem e);
int GetElem(List L, int i, Elem &e);
int ListLength(List L);
void ListPrint(List L);
int LocateElem(List L, Elem e);
②合并、分解、排序
基本操作的用途:
集合的并、交、差运算
有序线性表的合并、多项式的运算
例:利用线性表LA和LB分别表示集合A和B,求A=A∪B。
void union(List &La,List Lb)
{ int La_len, Lb_len;
La_len=ListLength(La); // 计算表长
Lb_len=ListLength(Lb);
for(i=1; i<=Lb_len; i++)
{ GetElem(Lb, i, e); // 取Lb的第i个元素
if(!LocateElem(La, e)) // 在La中查找e
ListAppend(La, e); // 在La中插入e
}
}
第二节 顺序存储结构
一、概念
逻辑结构中的“前后关系”:物理结构中的“相邻关系”
loc(ai)=loc(a0)+i*sizeof(单个数据元素)
静态顺序存储结构:一维数组。
注意:第i个元素的下标是i-1
动态顺序存储结构
#define LIST_INIT_SIZE 100
#define LIST_INCREMENT 10 // 存储空间的分配增量
typedef struct
{ ElemType *elem;
int length; //当前表长
int listsize; //当前已分配的存储空间
}SqList;
二、基本操作:
1、在ai之前插入x:
a0 a1 … ai-1 ai … … an-1
如何移动元素?
a0 a1 … ai-1 x ai … … an-1
void SqList_Delete(SqList A, int i)
{ for(j=i+1; j<A.length; j++)
A.elem[j-1] = A.elem[j];
A.length–;
}
三、效率分析
插入、删除时,大量时间用在移动元素上。
设插入位置为等概率事件,时间复杂度?
例1:增序顺序表的合并,设其中无相同元素
Status SqList_Merge(SqList La,SqList Lb,SqList &Lc)
{ ElemType *pa,*pb,*pc,pa_last,pb_last;
Lc.listsize=Lc.length=La.length+Lb.length;
Lc.elem=(ElemType)malloc(Lc.listsizesizeof(ElemType));
if(!Lc.elem) return ERROR;
pa=La.elem; pb=Lb.elem; pc=Lc.elem;
pa_last=La.elem+La.length-1;
pb_last=Lb.elem+Lb.length-1;
while(pa<=pa_last && pb<=pb_last)
if(*pa<=*pb) { *pc=*pa; pc++; pa++; }
else { *pc=*pb; pc++; pb++; }
while(pa<=pa_last) { *pc=*pa; pc++; pa++; }
while(pb<=pb_last) { *pc=*pb; pc++; pb++; }
return OK;
}
时间复杂度?
现实意义:数据库与日志文件的合并。
例2:无序顺序表的并集A∪B
例3:无序顺序表的交集A∩B
例4:无序顺序表的逆序
例5:剔除顺序表中的某种元素
剔除顺序表中的所有0元素,要求较高效率。
1 2 0 3 4 0 0 0 5 6 7 0 0 8 9
=> 1 2 3 4 5 6 7 8 9
void del(int A[],int n)
{ int i,k; // k是0元素的个数
for(k=0,i=0; i<n; i++)
if(A[i]==0) { k++; n–; }
else A[i-k] = A[i];
}
第三节 线性动态链表
一、概念
每个元素除存储自身信息外,还需存储其后继元素的信息。
结点、指针域、指针(链)
链表、线性链表、头指针
二、动态链表结构
typedef struct LNode
{ ElemType data;
struct LNode next;
}LNODE;
申请结点空间:
LNODE p;
p=(LNODE )malloc(sizeof(LNODE));
释放结点空间:
free§;
1、插入结点
在p之后插入新结点q:
q->next=p->next; p->next=q;
在p之前插入?
时间复杂度?
各种插入方法:
①首插:插入在首结点前;
②尾插:入在尾结点后。
③有序插:保持结点的顺序,插在表中间;
编程细节:
①首插:修改头指针
②尾插:链表为空时,修改头指针。
③有序插:链表为空时,修改头指针。
链表不空时,可能修改头指针
// 首插
LNODE * LinkList_InsertBeforeHead(LNODE *head, LNODE *newp)
{ newp->next=head; return(newp); }
// 遍历打印
void LinkList_Print(LNODE *head)
{ LNODE *p;
for(p=head; p; p=p->next)
printf(“…”,p->data);
}
// 尾插
LNODE * LinkList_InsertAfterTail(LNODE *head, LNODE *newp)
{ LNODE *p;
if(headNULL) { newp->next=NULL; return(newp); }
for(p=head->next; p->next; p=p->next);
newp->next=NULL; p->next=p;
return(head);
}
简化程序细节的方法:特殊头结点
// 首插(有特殊头结点)
void LinkList_InsertBeforeHead(LNODE *head, LNODE *newp)
{ newp->next=head->next;
head->next=newp;
}
// 尾插(有特殊头结点)
void LinkList_InsertAfterTail(LNODE *head, LNODE newp)
{ LNODE p;
for(p=head; p->next; p=p->next);
newp->next=NULL; p->next=p;
}
2、删除结点
删除p的后继结点:
q=p->next; p->next=q->next; free(q);
删除p?
时间复杂度?
各种删除方法:
①首删除:
②尾删除: 思考
③查找删除:思考
// 首删除(有特殊头结点)
void LinkList_DeleteHead(LNODE *head)
{ LNODE *p;
if(head->nextNULL) return;
p=head->next; head->next=p->next;
free(q);
}
三、单个链表的例程(设以下链表有特殊头结点)
例1、按序号查找:取链表第i个结点的数据元素。i∈[1,n]
// 注意计数次数的边界条件
Status LinkList_GetElemBySID(LNODE *head,int i,ElemType &e)
{ LNODE *p; int j;
for(p=head->next,j=1; p && j<i; j++) p=p->next;
if(pNULL) return ERROR;
e=p->data; return OK;
}
例2、按值查找:取链表中值为e的结点的地址。
LNODE * LinkList_GetElemByValue(LNODE *head, ElemType e)
{ LNODE *p;
for(p=head->next; p; p=p->next)
if(p->datae) break;
return§;
}
例3、释放链表中所有结点。
void LinkList_DeleteAll(LNODE *head)
{ LNODE *p;
while(head)
{ p=head->next; free(head); head=p; }
}
例4、复制线性链表的结点
// 适合有/无特殊头结点的链表
LNODE * LinkList_Copy(LNODE *L)
{ LNODE *head=NULL,*p,*newp,*tail;
if(!L) return(NULL);
for(p=L; p; p=p->next)
{ newp=(LNODE *)malloc(sizeof(LNODE));
if(headNULL) head=tail=newp;
else { tail->next=newp; tail=newp; }
newp->data=p->data;
}
tail->next=NULL; return(head);
}
例5、线性链表的逆序
LNODE * LinkList_Reverse(LNODE *head)
{ LNODE *newhead,*p;
newhead=(LNODE *)malloc(sizeof(LNODE));
newhead->next=NULL;
while(head->next!=NULL)
{ p=head->next; head->next=p->next; //卸下
p->next=newhead->next; newhead->next=p; //安装
}
free(Head);
return(newhead);
}
例6、利用线性表的基本运算,实现清除L中多余的重复节点。
3 5 2 5 5 5 3 5 6
=> 3 5 2 6
void LinkList_Purge(LNODE *head)
{ LNODE *p, *q, *prev_q;
for(p=head->next; p; p=p->next)
for(prev_q=p,q=p->next; q; )
if(p->dataq->data)
{ prev_q=q->next; free(q); q=prev_q->next; }
else
{ prev_q=q; q=q->next; }
}
四、多个链表之间的例程(设以下链表有特殊头结点)
例1、增序线性链表的合并,设其中无相同元素。
//破坏La和Lb,用La和Lb的结点组合Lc
LNODE *LinkList_Merge(LNODE *La,LNODE *Lb)
{ LNode *pa,*pb,*pctail,*Lc;
Lc=pctail=La; // Lc的头结点是原La的头结点
pa=La->next; pb=Lb->next; free(Lb);
while(pa!=NULL && pb!=NULL)
if(pa->data <= pb->data)
{ pctail->next=pa; pctail=pa; pa=pa->next; }
else { pctail->next=pb; pctail=pb; pb=pb->next; }
if(pa!=NULL) pctail->next=pa;
else pctail->next=pb;
return(Lc);
}
例2、无序线性链表的交集A∩B
//不破坏La和Lb,
//新链表Lc的形成方法:向尾结点后添加结点
LNODE *LinkList_Intersection(LNODE *La,LNODE *Lb)
{ LNode *pa,*pb,*Lc,*newp,*pctail;
Lc=pctail=(LNODE *)malloc(sizeof(LNODE));
for(pa=La->next; pa; pa=pa->next)
{ for(pb=Lb->next; pb; pb=pb->next)
if(pb->data==pa->data) break;
if(pb)
{ newp=(LNODE *)malloc(sizeof(LNODE));
newp->data=pa->data;
pctail->next=newp; pctail=newp;
}
}
pctail->next=NULL;
return(Lc);
}
作业与上机:(选一)
1、A-B
2、A∪B
3、A∩B
4、(A-B)∪(B-A)
第五节 循环链表
尾结点的指针域指向首结点。
实际应用:手机菜单
遍历的终止条件?
例:La、Lb链表的合并
// 将Lb链表中的数据结点接在La链表末尾
void Connect(LNODE * La,LNODE * Lb)
{ LNODE *pa, *pb;
for(pa=La->next; pa->next!=La; pa=pa->next);
for(pb=Lb->next; pb->next!=Lb; pb=pb->next);
pa->next=Lb->next; pb->next=La;
free(Lb);
}
第六节 双向链表
1、在p之后插入结点q
q->rLink=p->rLink; q->lLink=p;
p->rLink=q; q->rLink->lLink=q
在p之前插入结点q ?
2、删除p
(p->lLink)->rLink=p->rLink;
(p->rLink)->lLink=p->lLink;
free§;
删除p的前驱结点?
删除*p的后继结点?
第七节 实例:一元多项式的存储、运算
一、多项式的存储结构
f(x) = anxn +......+a2x2 + a1x + a0
1、顺序结构
int coef[MAX];
f(x) = 14x101+ 5x50 - 3
2、链式结构
typedef struct polynode
{ int coef;
int index;
struct node *next;
}PolyNode;
3、示例
4、结构细节设计
带特殊头结点的单向链表;
结点按指数降序排列;
特殊头结点的index域:多项式的项数。
5、程序框架设计
main()
{ PolyNode *L1,*L2;
L1=PolyNode_Create(); PolyNode_Print(L1);
L2=PolyNode_Create(); PolyNode_Print(L2);
PolyNode_Add(L1,L2); // L1+L2=>L1
PolyNode_Print(L1);
}
void PolyNode_Print(PolyNode *head)
{ PolyNode *p;
printf(“count=%d\n”,head->index); //打印项数
for(p=head->next; p; p=p->next)
printf(“coef=%d,index=%d\n”,p->coef, p->index);
}
二、以插入为基础的算法
1、将newp插入到降序链表head中
void PolyNode_Insert(PolyNode *head, PolyNode *newp);
{ PolyNode *p,pre;
// 定位
for(pre=head,p=pre->next; p; pre=p,p=p->next)
if(p->index <= newp->index) break;
if(p==NULL || p->index < newp->index) //在pre之后插入
{ newp->next=p; pre->next=newp; head->index++; }
else
{ p->coef += newp->coef; //合并同类项
free(newp);
if(p->coef==0) //删除
{ pre->next=p->next; free§; head->index–; }
}
}
2、利用PolyNode_Insert函数,建立有序链表
PolyNode *PolyNode_Create()
{ int i,count;
PolyNode *head,*newp;
scanf(“%d\n”,&count);
head=(PolyNode *)malloc(sizeof(PolyNode));
head->coef=0; head->next=NULL;
for(i=0; i<count; i++)
{ newp=(PolyNode *)malloc(sizeof(PolyNode));
scanf(“%d,%d\n”,&newp->coef,&newp->index);
PolyNode_Insert(head, newp);
}
return(head);
}
3、利用PolyNode_Insert函数,实现多项式加法
// L1+L2=>L1 不破坏L2链表
void PolyNode_Add(PolyNode *L1, PolyNode *L2)
{ NODE *p,*newp;
for(p=L2->next; p; p=p->next)
{ newp=(PolyNode )malloc(sizeof(PolyNode));
newp->coef=p->coef; newp->index=p->index;
PolyNode_Insert(L1,newp);
}
}
时间复杂度?
设L1长度为M,L2长度为N,则O(MN)。
三、两表合并算法
L1+L2=>L1 破坏L2链表
将L2中的每个结点卸下,合并到L1中
void PolyNode_Add(PolyNode *L1, PolyNode L2)
{ PolyNode p1, p1pre; // p1pre 是p1的直接前驱
PolyNode p2, p2next; // p2next是p2的直接后继
p1pre=L1; p1=L1->next; // p1指向L1中第一个数据结点
p2=L2->next; free(L2); // p2指向L2中第一个数据结点
while(p1 && p2)
{ switch(compare(p1->index, p2->index))
{ case ‘>’: // p1指数大于p2指数
p1pre=p1; p1=p1->next; break;
case ‘<’: // p1指数小于p2指数
p2next=p2->next; // 小心①:准备在p1pre后插入p2
p2->next=p1; p1pre->next=p2;
p1pre=p2; // 小心②:保持p1pre和p1的关系
p2=p2next; // p2指向下一个待合并的结点
break;
case ‘=’: // p1和p2是同类项
p1->coef+=p2->coef; // 系数合并
p2next=p2->next; free(p2); // 删除p2
p2=p2next; // p2指向下一个待合并的结点
if(pa->coef==0) // 合并后系数为0
{ p1pre->next=p1->next; free(p1); // 删除p1
p1=p1pre->next; // 保持p1pre和p1的关系
}
} // switch
} // while
if(p2) p1pre->next=p2; // 链上p2的剩余结点
}
时间复杂度?
设L1长度为M,L2长度为N,则O(M+N)。
作业与上机:(选一)
一、多项式加法、减法
二、多项式乘法:ab=>c(建立新表c)
三、任意整数的加法、乘法
第三章 栈与队列
栈与队列:限定操作的线性表。
第一节 栈
一、逻辑结构
1、栈的定义
限定只能在表的一端进行插入、删除的线性表。
栈顶top,栈底bottom。
后进先出LIFO表(Last In First Out)
实例:“进制数转换”、“表达式求值”、“函数调用关系”、“括号匹配问题”、“汉诺塔问题”、“迷宫问题”……
2、基本操作
进栈Push/出栈Pop
取栈顶元素GetTop
判别栈空StackEmpty/栈满StackFull
3、栈的应用背景
许多问题的求解分为若干步骤,而当前步骤的解答,是建立在后继步骤的解答基础上的。=》问题分解的步骤和求解的步骤次序恰好相反。
二、顺序存储结构
动态顺序存储结构:存储空间随栈的大小而变化。
SqStack S; //定义一个栈结构
1、初始化栈
Status SqStack_Init(SqStack &S)
{ S.base=malloc(STACK_INIT_SIZE*sizeof(ElemType));
if(!S.base) return(OVERFLOW);
S.top=S.base; S.stacksize=STACK_INIT_SIZE;
return(OK);
}
栈空: S.topS.base
栈满: S.topS.base+S.stacksize
2、进栈
Status SqStack_Push(SqStack &S,ElemType e)
{ if(S.top==S.base+S.stacksize) return(OVERFLOW);
S.top=e; S.top++;
return(OK);
}
3、出栈算法
Status SqStack_Pop(SqStack &S,ElemType &e)
{ if(S.top==S.base) return(UNDERFLOW);
S.top–; e=S.top;
return(OK);
}
缺点:空间浪费
思考:二栈合用同一顺序空间?
#define maxsize=100
int stack[maxsize], top0=0, top1=maxsize-1;
三、链栈
LinkStack S;
初始化: S.topNULL; S.stacksize0
栈 空:S.top==NULL
栈 满:系统内存不够
1、进栈
Status LinkStack_Push(LinkStack &S,ElemType e)
{ LinkNode *p;
p=(LinkNode *)malloc(sizeof(LinkNode));
if(p==NULL) return(OVERFLOW); //上溢
p->data=e;
p->link=S.top; S.top=p;
return(OK);
}
2、出栈
Status LinkStack_Pop(LinkStack &S,ElemType &e)
{ LinkNode *p;
if(S.top==NULL) return(UNDERFLOW);
p=S.top;
e=S.top->data; S.top=S.top->link; free§;
return(OK);
}
3、特殊头结点的讨论
进栈、出栈是最常用的操作
=》链栈的头指针频繁变更
=》参数传递的负担
=》应约定链栈具有特殊头结点。
第二节 栈的应用
1、函数调用栈
f(n)=f(n-1)+f(n-2)
2、将十进制数转换为八进制数。
例如 (1348)10=(2504)8,除8取余的过程:
void Conversion(int dec,int oct[])
{ InitStack(S);
for(; dec!=0; dec=dec/8)
push(S, dec%8); /* 进栈次序是个位、十位… /
for(i=0; !StackEmpty(S); i++)
oct[i]=pop(S); / 先出栈者是高位,最后是个位 */
}
3、括号匹配的检验
Equal( "((a)(b)) " ) : 0
Equal( “(((a)(b))” ) : 1
Equal( “((a)(b)))” ) : -1
int Equal(char s[])
{ int i=0;
for(i=0; s[i]; i++)
switch(s[i])
{ case ‘(’: push(s[i]); break;
case ‘)’: if(StackEmpty(S)) return(-1); // )多
pop(); break;
}
if(!StackEmpty(S)) return(1); // (多
return(0); // 完全匹配
}
4、进栈出栈的组合问题
已知操作次序:push(1); pop(); push(2); push(3); pop(); pop( ); push(4); pop( ); 请写出出栈序列。
5、中缀表达式求值的算法
如: 1 + 2 *(3 + 4 *(5 + 6))
分析:先乘除,后加减;从左到右;先括号内,再括号外。
①一个运算是否立即执行?必须和“上一个运算符”比较优先级。
3 * 5 + 2
3 - 5 * 2 + 8
②“上一个运算符”存于何处? 运算符栈
③对应的运算数存于何处? 运算数栈
④为保证第一个运算符有“上一个运算符”: Push(OPTR,‘#’)
为保证最后一个运算符能被执行: “3*5+2#”
#:优先级最低的运算符。
⑤运算结果:运算数栈最后的元素。
charStack:
类型定义:
基本函数:InitStack1(charStack *);
char GetTop1(charStack *);
void Push1(charStack *, char);
char Pop1(charStack *);
intStack:
类型定义:
基本函数:InitStack2(intStack *);
int GetTop2 (intStack *);
void Push2(intStack , int);
int Pop2(intStack );
float MidExpression_Eval(char Express[]) // Express[]:"35+2#"
{ int i=0; char c,pre_op;
charStack OPTR; intStack OPND;
InitStack1(&OPTR); Push1(&OPTR,‘#’); / 运算符栈 /
InitStack2(&OPND); / 运算数栈 /
while(Express[i]!=‘#’ || GetTop1(&OPTR)!=‘#’)
{ c=Express[i];
if(!InOPTR©)
{ Push2(&OPND, c-‘0’); i++; } / getnum(Express,&i) /
else
{ pre_op=GetTop1(&OPTR);
switch(Precede(pre_op, c))
{ case ‘<’: // pre_op < c
Push1(&OPTR,c); i++; break;
case ‘=’:
Pop1 (&OPTR); i++; break;
case ‘>’: // pre_op > c 执行pre_op
b=Pop2(&OPND); a=Pop2(&OPND); Pop1(&OPTR);
Push2(&OPND,Operate(a,pre_op,b));
}
}
return(GetTop2(&OPND));
}
// 优先级的处理技巧
+ - * / ( ) #
int priority[7][7]={{‘>’, ‘>’, ‘<’, ‘<’, ‘<’, ‘>’, ‘>’}, // +
{‘>’, ‘>’, ‘<’, ‘<’, ‘<’, ‘>’, ‘>’}, // -
{‘>’, ‘>’, ‘>’, ‘>’, ‘<’, ‘>’, ‘>’}, // *
{‘>’, ‘>’, ‘>’, ‘>’, ‘<’, ‘>’, ‘>’}, // /
{‘<’, ‘<’, ‘<’, ‘<’, ‘<’, ‘=’, }, // (
{‘>’, ‘>’, ‘>’, ‘>’, , ‘>’, ‘>’}, // )
{‘<’, ‘<’, ‘<’, ‘<’, ‘<’, , ‘=’}, // #
}
int OpID(char op)
{ switch (op)
{ case ‘+’ : return(0);
case ‘-’ : return(1);
case '’ : return(2);
case ‘/’ : return(3);
case ‘(’ : return(4);
case ‘)’ : return(5);
case ‘#’ : return(6);
}
}
char Precede(char op1, char op2)
{ return(priority[OpID(op1)][OpID(op2)]); }
测试案例:“#”, “3#”,“3+5#”,“3+5+2#”
“35+2#", "3+52#”,“(3+5)2#",
"(3+5)(2+1)#”,“((3+5)/2+1)*2#” …………
作业与上机:
1、表达式求值
第三节 队列
一、逻辑结构
只能在一端(队尾rear)插入,在另一端(队头front)删除的线性表。
先进先出表FIFO(First In First Out)
基本操作:进/出队列
判别队列满/空
队列的应用背景:排队模型。排队问题无处不在,各种服务行业、甚至生产管理中都存在排队问题。
二、链式存储结构
LinkQueue Q;
初始化空队列: Q.frontNULL Q.rearNULL
1、入队列
Status LinkQueue_Enter(LinkQueue &Q, ElemType e)
{ QueueNode *p;
p=(QueueNode *)malloc(sizeof(QueueNode));
if(!p) return(OVERFLOW);
p->data=e; p->link=NULL;
if(Q.front==NULL) Q.front=Q.rear=p;
else { Q.rear->link=p; Q.rear=p; }
return(OK);
}
2、出队列
Status LinkQueue_Leave(LinkQueue &Q,ElemType &e)
{ QueueNode *p;
if(Q.frontNULL) return(UNDERFLOW);
p=Q.front; Q.front=p->link;
if(Q.rearp) Q.rear=NULL;
e=p->data; free§;
return(OK);
}
3、销毁队列
void LinkQueue_Destroy(LinkQueue &Q)
{ QueueNode *p;
while(Q.front)
{ p=Q.front;
Q.front=p->link;
free§;
}
Q.rear=NULL;
}
三、顺序存储结构
动态顺序存储结构:
SqQueue Q; //定义一个队列结构
rear为下一个进队列元素的位置。
front在队列不空时,指向首元素;
在队列为空时,等于rear。
1、初始化队列
Status SqQueue_Init(SqQueue &Q)
{ Q.base=malloc(QUEUE_SIZEsizeof(ElemType));
if(!Q.base) return(OVERFLOW);
Q.front=Q.rear=0;
return(OK);
}
队列空: Q.front==Q.rear
进队列:(Q.base+Q.rear)=e; Q.rear++;
出队列:e=*(Q.base+Q.front); Q.front++;
队列满: Q.rearQUEUE_SIZE
=》假溢出
进队列:Q.rear =(Q.rear +1) % QUEUE_SIZE
出队列:Q.front=(Q.front+1) % QUEUE_SIZE
队列空:Q.frontQ.rear
当队列中有QUEUE_SIZE个元素时:
Q.front==Q.rear
=》必须浪费一个结点空间
队列满:(Q.rear +1) % QUEUE_SIZE == Q.front
2、入队列
Status SqQueue_Enter(SqQueue &Q,ElemType e)
{ if((Q.rear +1) % QUEUE_SIZE==Q.front) return(OVERFLOW);
*(Q.base+Q.rear)=e;
Q.rear=(Q.rear +1) % QUEUE_SIZE;
return(OK);
}
3、出队列
Status SqQueue_Leave(SqQueue &Q,ElemType &e)
{ if(Q.rear==Q.front) return(UNDERFLOW);
e=*(Q.base+Q.front);
Q.front=(Q.front+1) % QUEUE_SIZE;
return(OK);
}
4、元素计数
(Q.rear-Q.front+QUEUE_SIZE)% QUEUE_SIZE
值的范围0 …… QUEUE_SIZE-1
思考:一定要浪费一个结点空间?
利用一个标志变量Q.flag (0:非满,1:非空)。
int Empty(SqQueue Q)
{ if(Q.frontQ.rear && Q.flag0) return(1);
return(0);
}
int Full(SqQueue Q)
{ if(Q.frontQ.rear && Q.flag1) return(1);
return(0);
}
1、初始化队列
Status SqQueue_Init(SqQueue &Q)
{ Q.front=Q.rear=0; Q.flag=0; }
2、入队列
Status SqQueue_Enter(SqQueue &Q,ElemType e)
{ if(Full(Q)) return(OVERFLOW);
…………
Q.flag=1; //非空
return(OK);
}
3、出队列
Status SqQueue_Leave(SqQueue &Q,ElemType &e)
{ if(Empty(Q)) return(UNDERFLOW);
…………;
Q.flag=0; //非满
return(OK);
}
第四节 队列的实例:离散事件的模拟
一、排队问题
加油站的问题:
一个加油站只有一台加油机,平均为每辆车加油需要5分钟,假定一个小时内,有20辆车随机进入加油站加油,计算每辆车的平均等待时间.
银行营业所应设置几个柜台?
1、设置N柜台时,计算顾客的平均等待时间;
2、选择合适的N值。
二、两个栈组合出一个队列
制定两个栈与一个队列的对应规则:
stack s1,s2;
void Enter(ElemType e)
{ Push(s1,e); }
ElemType Leave()
{ ElemType e;
if( !Empty(s2) ) return( Pop(s2) );
while( !Empty(s1) )
Push(s2, Pop(s1));
return( Pop(s2) );
}