说在前面
本篇笔记总结DSPv2b_4(栈与队) for
student 内的相关内容。各位如果在学习过程中对于一些概念不理解,可以考虑问问AI们,从他们那里获得一些形象的描述来辅助理解。以及,如果实在de不出来bug了,也可以问问AI,虽然不一定有用,但是万一有什么bug被你忽略缺被AI发现了呢。最后,本人学艺不精,只是一边看着ppt一边敲敲改改,如有疏漏,欢迎提出喵~o(
=∩ω∩= )m
栈
1 栈的基本概念
1.1 栈的定义
1.2 栈的基本操作
特殊性:
其操作仅仅是一般线性表的操作的一个子集
插入和删除操作的位置受到限制
基本操作:
插入(进栈、入栈、压栈)
删除(出栈、退栈、弹出)
测试栈是否为空
测试栈是否已满
获取栈顶元素
2 栈的顺序存储结构(顺序栈)
2.1 构造原理
2.2 基本算法
2.2.1 初始化堆栈
1 2 3 void initStack ( ) { Top= –1 ; }
2.2.2 进栈
1 2 3 4 5 6 7 8 9 10 11 void push (ElemType s[ ], ElmeType item ) { if (isFull()) Error("Full Stack!" ); else s[++Top] = item; }void Error (char s[]) { printf ("%s\n" , s); exit (-1 ); }
2.2.3 出栈
1 2 3 4 5 6 ElemType pop (ElemType s[ ]) { if (isEmpty()) Error("Empty Stack!" ); else return s[Top--]; }
2.2.4 测试栈是否为空
1 2 3 int isEmpty ( ) { return Top == -1 ; }
2.2.5 测试栈是否已满
1 2 3 int isFull ( ) { return Top == MAXSIZE-1 ; }
2.3
多栈共享连续空间问题——以两个栈共享一个数组为例
已知数组STACK[0...M-1]
、第一个与第二个栈的栈顶元素的位置Top1, Top2
2.3.1 进栈
1 2 3 4 5 6 7 8 9 10 11 12 void push (ElemType s[ ], int i, ElemType item) { if (top1==top2–1 ){ Error("Full Stack!" ); }else { if (i==1 ) STACK[++top1]=item; else STACK[--top2]=item; return ; } }
2.3.2 出栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 EleType pop (ElemType s[ ], int i) { if (i==1 ){ if (top1==–1 ) Error("Empty Stack1!" ); else return s[top1--]; }else { if (top2==MAXSIZE) Error("Empty Stack2!" ); else return s[top2++]; } }
3
栈的链式存储结构(链接栈)(链栈)
3.1 构造原理
用一个线性链 表来实现一个栈结构,
同时设置一个指针变量(top)指出当前栈顶元素所在链结点的位置。栈为空时,有top=NULL。
链栈是一种特殊的链表,其结点的插入(进栈)和删除(出栈)操作始终在链表的头。
类型定义
1 2 3 4 5 6 7 struct node { ElmeType data; struct node *link ; };typedef struct node *Nodeptr ;typedef struct node Node ; Nodeptr Top;
3.2 基本算法
3.2.1 初始化堆栈
1 2 3 void initStack ( ) { Top=NULL ; }
3.2.2 进栈
1 2 3 4 5 6 7 8 9 10 11 void push (ElemType item) { Nodeptr p; if ( (p=(Nodeptr)malloc (sizeof (Node)))==NULL ){ Error("No memory!" ); }else { p->data=item; p->link=Top; Top=p; } }
3.2.3 出栈
1 2 3 4 5 6 7 8 9 10 11 12 13 ElemType pop ( ) { Nodeptr p; ElemType item; if (isEmpty( )){ Error("Empty Stack!" ); }else { p=Top; item=Top->data; Top=Top->link; free (p); return item; } }
3.2.4 测试栈是否为空
1 2 3 int isEmpty ( ) { return Top==NULL ; }
4 例题:计算器(表达式计算)
4.1 提出问题
问题描述:从标准输入中读入一个整数算术运算表达式,如24 / ( 1 + 2 + 36 / 6 / 2 - 2) * ( 12 / 2 / 2 )=
,计算表达式结果,并输出。
要求:
表达式运算符只有+
、-
、*
、/
,表达式末尾的=
字符表示表达式输入结束,表达式中可能会出现空格;
表达式中会出现圆括号,括号可能嵌套,不会出现错误的表达式;
出现除号/
时,以整数相除进行运算,结果仍为整数,例如:5/3结果应为1。
输入形式:从键盘输入一个以=
结尾的整数算术运算表达式。
输出形式:在屏幕上输出计算结果(为整数)。
样例输入:24 / ( 1 + 2 + 36 / 6 / 2 - 2) * ( 12 / 2 / 2
)=
样例输出:18
4.2 问题分析
4.3 算法设计
对于问题3.1我们没有必要象编译程序那样先将中缀表达式转换为后缀表达式,然后再进行计算。我们可以设两个栈,一个为数据栈,另一个为运算符栈,在转换中缀表达式的同时进行表达式的计算。主要思路为:当一个运算符出栈时,即与数据栈中的数据进行相应计算,计算结果仍存至数据栈中 。算法如下:
从输入(中缀表达式)中获取一项:
若是数字则压入数据栈中;
若是符号:
若是)
,则将符号栈中元素弹出并与数据栈中元素进行计算、计算结果放回数据栈中,直到遇到(
,
(
弹出但不计算;
若是(
,+
,*
等符号,则从符号栈中弹出优先级高于当前的符号并与数据栈中元素进行计算、计算结果放回数据栈中,直到遇到一个优先级低的符号;然后将当前符号压入栈中。
若是=
号,将符号栈中所有元素依次弹出并与数据栈中元素进行计算、计算结果放回数据栈中,直到符号栈为空,此时数据栈中为计算结果;否则继续从输入中获取一项。
4.4 代码实现
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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #define MAXSIZE 100 typedef int DataType; enum symbol { NUM, OP, EQ, OTHER};enum oper { EPT, ADD, MIN, MUL, DIV, LEFT, RIGHT}; int Pri[]={-1 ,0 ,0 ,1 ,1 ,2 ,2 }; union sym { DataType num; enum oper op ; } ; DataType Num_stack[MAXSIZE]; enum oper Op_stack [MAXSIZE ]; int Ntop=-1 ; int Otop=-1 ; enum symbol getSym ( union sym *item) ;void operate (enum oper op ) ;void compute (enum oper op ) ; void pushNum (DataType num) ; DataType popNum () ;void pushOp (enum oper op) ;enum oper popOp () ;enum oper topOp () ;int main () { union sym item ; enum symbol s ; while ( (s = getSym(&item)) != EQ){ if (s == NUM){ pushNum(item.num); }else if (s == OP){ operate(item.op); }else { printf ("Error in the expression!\n" ); return 1 ; } } while (Otop >= 0 ) compute(popOp()); if (Ntop == 0 ) printf ("%d\n" , popNum()); else printf ("Error in the expression!\n" ); return 0 ; }enum symbol getSym ( union sym *item) { int c, n; while ((c = getchar()) != '=' ) { if (c >= '0' && c <= '9' ){ for (n=0 ; c >= '0' && c <= '9' ; c= getchar()) n = n*10 + c-'0' ; ungetc(c, stdin ); item->num = n; return NUM; }else { switch (c) { case '+' :item->op = ADD;return OP; case '-' :item->op = MIN;return OP; case '*' :item->op = MUL;return OP; case '/' :item->op = DIV;return OP; case '(' :item->op = LEFT;return OP; case ')' :item->op = RIGHT;return OP; case ' ' :case '\t' :case '\n' :break ; default :return OTHER; } } } return EQ; }void operate (enum oper op) { enum oper t ; if (op != RIGHT){ while (Pri[op] <= Pri[topOp()] && topOp() != LEFT) compute(popOp()); pushOp(op); } else while ((t = popOp()) != LEFT) compute(t); }void compute (enum oper op ) { DataType tmp; switch (op) { case ADD: pushNum(popNum() + popNum()); break ; case MIN: tmp = popNum(); pushNum(popNum() - tmp); break ; case MUL: pushNum(popNum() * popNum()); break ; case DIV: tmp = popNum(); pushNum(popNum() / tmp); break ; } }void pushNum (DataType num) { if (Ntop == MAXSIZE -1 ){ printf ("Data stack is full!\n" ); exit (1 ); } Num_stack[++Ntop] = num; } DataType popNum () { if (Ntop == -1 ) { printf ("Error in the expression!\n" ); exit (1 ); } return Num_stack[Ntop--] ; }void pushOp (enum oper op) { if (Ntop == MAXSIZE -1 ) { printf ("operator stack is full!\n" ); exit (1 ); } Op_stack[++Otop] = op; }enum oper popOp () { if (Otop != -1 ){ return Op_stack[Otop--] ; } return EPT; }enum oper topOp () { return Op_stack[Otop]; }
队
1 队的基本概念
1.1 队的定义
队列 简称队 。是一种只允许在表的一端进行插入操作,而在表的另一端进行删除操作的线性表(先进先出
FIFO(First-In-First-Out) )。允许插入的一端称为队尾 ,队尾元素的位置由rear
指出;允许删除的一端称为队头 ,
队头元素的位置由front
指出。
### 1.2 队的基本操作
特殊性
其操作仅是一般线性表的操作的一个子集
插入和删除操作的位置受到限制
队的基本操作
插入(进队、入队)
删除(出队、退队)
测试队是否为空
测试队是否为满
检索当前队头元素
创建一个空队
2 队的顺序存储结构
2.1 构造原理
借助一个一维数组QUEUE[0...M–1]
来描述队的顺序存储结构,同时,设置两个变量front
与rear
分别指出当前队头元素与队尾元素的位置,再设置变量count
指出实际队中元素个数。
初始时队为空,front = 0, rear = -1, count = 0
;测试队为空的条件为count == 0
2.2 循环队列
2.3(循环队列)基本算法
2.3.1 初始化队列
1 2 3 4 5 void initQueue ( ) { Front = 0 ; Rear = MAXSIZE-1 ; Count = 0 ; }
2.3.2 测试队列是否为空或满
1 2 3 4 5 6 7 int isEmpty ( ) { return Count == 0 ; }int isFull ( ) { return Count == MAXSIZE; }
2.3.3 进队
1 2 3 4 5 6 7 8 9 void enQueue (ElemType queue [ ], ElemType item) { if (isFull()){ Error(“Full queue !”); }else { Rear = (Rear+1 ) % MAXSIZE; queue [Rear]=item; Count++; } }
2.3.3 出队
1 2 3 4 5 6 7 8 9 10 11 ElemType deQueue (ElemType queue [ ]) { ElemType e; if (isEmpty()){ Error(“Empty queue !”); }else { e=queue [Front]; Count--; Front = (Front+1 )%MAXSIZE; return e; } }
3 队的链式存储结构
3.1 构造原理
3.2 基本算法
3.2.1 初始化队列
1 2 3 4 void initQueue () { Front=NULL ; Rear=NULL ; }
3.2.2 测试队列是否为空
1 2 3 int isEmpty () { return Front==NULL ; }
3.2.3 进队
1 2 3 4 5 6 7 8 9 10 11 12 void enLQueue (ElemType data) { QNodeptr p; if ((p=(QNodeptr)malloc (sizeof (QNode))) ==NULL ) Error("No memory!" ); p->data = data; p->link = NULL ; if (Front == NULL ) Front = p; else Rear->link = p; Rear=p; }
3.2.4 出队
1 2 3 4 5 6 7 8 9 10 11 12 13 ElemType deLQueue ( ) { QNodeptr p; ElemType data; if (isEmpty()){ Error("Empty queue!" ); }else { p = Front; Front = Front->link; data = p->data; free (p); return data; } }
3.2.5 销毁一个队
所谓销毁一个队是指将队列所对应的链表中所有结点都删除,并且释放其存储空间,使队成为一个空队(空链表)。
1 2 3 4 5 6 7 void destroyLQueue () { while (Front != NULL ){ Rear=Front->link; free (Front); Front=Rear; } }
4 例题:银行排队模拟
4.1 提出问题
某银行网点有五个服务窗口,分别为三个对私、一个对公和一个外币窗口。通常对私业务人很多,其它窗口人则较少,可临时改为对私服务。假设当对私窗口客户平均排队人数超过7人时,客户将有抱怨,此时银行可临时将其它窗口中一个或两个改为对私服务,当客户少于7人时,将恢复原有业务。设计一个程序用来模拟银行服务。
输入:首先输入一个整数表示时间周期数,然后再依次输入每个时间周期中因私业务的到达
客户数。注:一个时间周期指的是银行处理一笔业务的平均处理时间,可以是一分钟、三分钟或其它。例如:
6
2 5 13 11 15 9
说明:表明在6个时间周期内,第1个周期来了2个(ID分别为1,2),第2个来了5人(ID分别为3,4,5,6,7),以此类推。
输出:每个客户等待服务的时间周期数。
4.2 问题分析
在生产者-消费者应用中消费者显然是先来先得到服务。在此,显然可用一个队列来存放等待服务的客户队列。每个客户有二个基本属性:排队序号和等待时间(时间周期数):
1 2 3 4 5 struct cust { int id; int wtime; };struct cust Cqueue [MAXSIZE ];
为了简化问题,可用一个变量来表示银行当前提供服务的窗口数:int snum;
在本问题中,该变量的取值范围为3<=snum<= 5
4.3 算法设计
1 2 3 4 5 6 7 8 9 10 11 12 13 主要算法:for (clock=1 ; ; clock++){ { if (客户等待队列非空) 将每个客户的等待时间增加一个时间单元; if (clock <= simulationtime) 如果有新客户到来(从输入中读入本周期内新来客户数),将其入队; 根据等待服务客户数重新计算服务窗口数; if (客户等待队列非空) 从客户队列中取(出队)相应数目(按实际服务窗口数)客户获得服务; 然后根据等待服务客户数重新计算服务窗口数; else 结束模拟 }
4.4 代码实现
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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include <stdio.h> #include <stdlib.h> #define MAXSIZE 200 #define THRESHOLD 7 #define MAXSVR 5 #define MINSVR 3 typedef struct { int id; int wtime; } CustType;int Winnum=MINSVR; void updateCustqueue () ; void enCustqueue (CustType c) ; CustType deCustqueue () ; int getCustnum () ; int isFull () ;int isEmpty () ;void arriveCust () ; int service () ; #define MAXSIZE 200 static CustType Cqueue[MAXSIZE];static int Cfront=0 ; static int Crear = -1 ; static int Cnum=0 ; int main () { int clock, simulationtime; scanf ("%d" , &simulationtime); for (clock = 1 ; ; clock++){ updateCustqueue(); if (clock <= simulationtime) arriveCust(); if (service()==0 && clock>simulationtime) break ; } return 0 ; }void arriveCust () { int i,n; static int count=1 ; CustType c; scanf ("%d" , &n); for (i=0 ; i<n; i++){ c.id = count++; c.wtime = 0 ; enCustqueue(c); } while ((getCustnum() / Winnum) >= THRESHOLD && Winnum<MAXSVR) Winnum++; }int service () { int i; CustType c; for (i=0 ; i<Winnum; i++) if (isEmpty()){ return 0 ; }else { c = deCustqueue(); printf ("%d :%d\n" , c.id, c.wtime); } if ((getCustnum() / Winnum) < THRESHOLD && Winnum>MINSVR) Winnum--; return 1 ; }void enCustqueue (CustType c) { if (isFull()){ printf ("Full queue!" ); exit (-1 ); }else { Crear = (Crear+1 ) % MAXSIZE; Cqueue[Crear] = c; Cnum++; } } CustType deCustqueue () { CustType c; if (isEmpty()){ printf ("Empty queue!" ); exit (-1 ); }else { c=Cqueue[Cfront]; Cnum--; Cfront = (Cfront+1 )%MAXSIZE; return c; } }void updateCustqueue () { int i; for (i=0 ; i<Cnum; i++) Cqueue[(Cfront+i)%MAXSIZE].wtime++; }int isEmpty () { return Cnum == 0 ; }int isFull () { return Cnum == MAXSIZE; }int getCustnum () { return Cnum; }
其他
1 枚举类型(enum)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 enum 枚举名 { 值表} enum color { red, green, yellow, white, black} enum color chair ; enum color suite [10]; chair = red; suite[5 ] = yellow; if (chair == green)... enum color { red, green, yellow = 5 , white, black }; red=0 , green=1 , yellow=5 , white=6 , black=7 enum Boolean { FALSE, TRUE }; enum { SIZE=1024 };
2 静态变量(static)
内部静态变量
外部静态变量
在函数外部定义的变量前加上static
关键字便成了外部静态变量。
作用域为定义它的文件,即成为该文件的的“私有”(private)变量,其它文件上的函数一律不得直接进行访问,除非通过它所在文件上的各种函数来对它进行操作,这可实现数据隐藏。(在C++中提供进一步的数据隐藏。)