清华邓俊辉数据结构学习笔记(2)- 栈、队列

第四章 栈与队列

(a)栈接口与实现

Stack()
empty()
push(5)
pop()
size()
top()

实现:由向量派生

template <typename T> class Stack: public Vector<T>{
public: //size()、empty()以及其他开放接口均可直接沿用
	void push(T const  & e){insert(size(),e);} //入栈
	T pop(){return remove(size() - 1);} //出栈
	T & top() {return (*this)[size() - 1];} //取顶
};//以向量为首/末端为栈底/顶

(c1)栈应用:进制转换

典型应用场合
1、逆序输出(conversion):输出次序与处理过程颠倒;递归深度和输出长度不易预知;
2、递归嵌套(stack permutation + parenthesis):具有自相似性的问题可递归描述,但分支位置和嵌套深度不固定;
3、延迟缓冲(evaluation):线性扫描算法模式中,在预读足够长之后,方能确定可处理的前缀;
4、栈式计算(RPN):基于栈结构的特定计算模式。
算法实现

void convert( Stack<char> & S,__int64 n, int base){
	static char digit[] = //新进制下的数位符号,可视base取值范围适当扩充
	{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
	while ( n > 0 ){//由低到高,逐一计算新进制下各数位
		S.push( digit[n % base]); //余数(对应的数位)入栈
		n /= base; //n更新为其对base的除商
	}
}
main(){
	Stack<char> S; convert(S, n, base);//用栈记录转换得到的各数位
	while(!S.empty()) printf("%c",S.pop());//逆序输出
}

(c2)栈应用:括号匹配

尝试:(0)平凡情况。无括号的表达式是匹配的;(1)减而治之;(2)分而治之。(1和2均为必要性,可以举出反例,要充分借助充分性)
构思:消去一对紧邻的左右括号,不影响全局的匹配判断。
Q:如何找到这对括号?如何使问题简化?
A:顺序扫描表达式,用栈记录已扫描的部分,反复迭代,遇到“(”则进栈,遇到“)”则出栈。
实现

bool paren(const char exp[], int lo, int hi){//exp[lo,hi)
	Stack<char> S; //使用栈记录已发现但尚未匹配的左括号
	for (int i = lo; i < hi; i++)//逐一检查当前字符
		if('('==exp[i]) S.push(exp[i]); //遇左括号,则进栈
		else if(!S.empty()) S.pop(); //遇右括号:若占非空,则弹出左括号
		else return false; //否则(遇右括号时栈已空),则不匹配
	return S.empty(); //最终,栈空当且仅当匹配
}

以上思路及算法,可以便捷推广至多种括号并存的情况。
Q:能否使用多个计数器?
A:不行。反例:[ ( ] )
只需约定“括号”的通用格式,而不必事先固定括号的类型与数目。

(c3)栈应用:栈混洗

考查栈A=<a1,a2,…an](左端为栈顶),B=S=∅,,只允许将A的顶元素弹出并压入S,或将S的顶元素弹出并压入B,经一系列以上操作后,A中元素全部转入B中,B=[ak1,…,akn>(右端为栈顶),则成为A的一个栈混洗。

S.push(A.pop())
B.push(S.pop())

同一输入序列,可有多种栈混洗,长度为n的序列,可能的混洗总数SP(n)<=n! ∑SP(k-1)×SP(n-k)=(2n)!/[(n+1)!n!]
Q:判断某一输入序列的任一排列是否为栈混洗?
A:观察任意三个元素能否按照相对次序出现在混洗中,与其它元素无关,如1≤i<j<k≤n,[…,k,…,i,…,j,…>必非栈混洗,即对于输入序列<1,2,3],不存在312模式的序列。
O(n)算法:直接借助栈A、B、S,模拟混洗过程,每次S.pop()之前检测S是否已空;或需弹出的元素在S中,却非顶元素。每一次栈混洗,都对应于栈S的n次push与n次pop操作构成的序列。

(c4)栈应用:中缀表达式求值

给定语法正确的算术表达式S,计算与之对应的数值。
求值算法=栈+线性扫描
实现:主算法

float evaluate( char* S, char* & RPN){//中缀表达式求值
	Stack<float> opnd; Stack<char> optr; //运算数栈、运算符栈
	optr.push('\0'); //尾哨兵‘\0’也作为头哨兵首先入栈
	while(!optr.empty()){//逐个处理各字符,直至运算符栈为空
		if(isdigit(*S)) //若当前字符为操作数,则
			readNumber(S,opnd); //读入(可能多位)操作数
		else //若当前字符为运算符,则视其与栈顶运算符之间优先级高低
			switch(orderBetween(optr.top(),*S)){/*分别处理*/}
	}//while
	return opnd.pop();//弹出并返回最后的计算结果
}

实现:优先级表

const char pri[N_OPTR][N_OPTR]={//运算符优先等级[栈顶][当前]//};

实现:不同优先级处理方法

switch(orderBetween(optr.top(),*S)){
	case '<': //栈顶运算符优先级更低
		optr.push(*S);S++;break; //计算推迟,当前运算符进栈
	case '=': //优先级相等(当前运算符为右括号,或尾部哨兵'\0')
		optr.pop();S++;break; //脱括号并接受下一个字符
	case '>':{//栈顶运算符优先级更高,实施相应的计算,结果入栈
		char op = optr.pop();
		if('!' == op) opnd.push(calcu(op,opnd.pop()));//一元运算符
		else{float pOpnd2 = opnd.pop(), pOpnd1 = opnd.pop()};//二元运算符
		opnd.push(calcu(pOpnd1,op,pOpnd2)); //实施计算,结果入栈 
	}
	break;
}

(c5)栈应用:逆波兰表达式

RPN:逆波兰表达式。在由运算符和操作数组成的表达式中,不使用括号,即可表示带优先级的运算关系。
infix到postfix:手工转换:(1)用括号显式地表示优先级;(2)将运算符移到对应的右括号后;(3)抹去所有括号;(4)稍事整理。
infix到postfix:转换算法

float evaluate(char* S, char* & RPN){//RPN转换
	/*.........*/
	while(!optr.empty()){//逐个处理各字符,直至运算符栈空
		if (isdigit(*S)) //若当前字符为操作数,将其直接接入RPN
			{readNumber(S,opnd)};append(RPN,opnd.top();)
		else //若当前字符为运算符
			switch(orderBetween(optr.top,*S))
			/*.............................*/
				case '>' {//可立刻执行,在执行相应计算同时将其接入RPN
					char op = optr.pop();append(RPN,op);//接入RPN
					/*.........................*/
				}
	/*................................*/
	}
}

(d)队列接口与实现

队列也是受限的序列
只能在队尾插入(查询):enqueue() + rear()
只能在队头删除(查询):dequeue() + front()
操作实例

Queue()
empty()
enqueue(3)
dequeue() //返回队首元素
front() //返回队首元素
size()

模板类:属于序列的特列,可基于向量或列表派生

template<typename T> class Queue: public List<T>{//由列表派生
public://size()empty()直接沿用
	void enqueue(T const & e){insertAsLast(e);} //入队
	T dequeue(){return remove(first());}//出队
	T & front() {return first()->data;}//队首
}//以列表首/末尾队列头/尾

均只需O(1) 时间

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值