日撸 Java 三百行(第 15 天: 栈的应用——括号匹配)
注意:这里是JAVA自学与了解的同步笔记与记录,如有问题欢迎指正说明
前言
今天学的内容不算特别难,是在14天补充的栈的对象基础上进行对栈的应用——括号匹配的模拟。
主要通过今天的编写目的是为了着重体会下关于栈的具体使用情景。
一、什么是括号匹配
所谓括号匹配可以认为是任意的一个字符串中,都能发现一对完整的左括号与右括号按照正常的顺序实现完整匹配的过程。
1.出现括号(
之后能保证第一个出现的括号字符是)
即可实现匹配,若是]、}
都不能匹配。
2.括号匹配要以最近的左括号为基准,若上次出现的括号是{
,但是本次出现的括号若是(
,那么就可以打断这个大括号基准,从而以自我为基准,下次再出现}
时因为基准从大括号变为小括号,因为{)
无法匹配而失败。
3.这而这个匹配必须是左右成对的,若首次出现的括号是)
而之前没有出现任何左括号那么也无法匹配
例如()[]{}
是正常匹配。(]
不算,]][[
不算,(({{[]}}))
是匹配,(([[]}))
不算。
当然我们的符号中间可以加任何其余干扰数字,(1 + 3 / ( 90 % {1 + {99 + [3 - 1] + 3} / 9} - 9) * 101)
是匹配。
二、一些常见策略
括号匹配其实可看做是两个回合的较量。
不断遍历字符串,这个过程中我们只紧紧盯住括号就好,其余字符在我们眼中一律透明。一旦发现括号,就其左右的不同从而进入不同回合:
Round1: 左括号入栈——休战
因为上述介绍括号匹配的第二条说过的基准问题,左括号【‘[’、’{’、’(’ 等】入栈不应该受到任何影响或者限制,只要它想入栈就入栈,不作任何处理。
Round2: 右括号入栈——开战:进行匹配工作
虽然我们说是“入栈”,但是,本质上这个括号 根本不会入栈 ,它只是一个识别器:比如当前括号是]
,它其实传达的消息是:“ 我希望现在栈顶是[
,不然无法匹配 ”。
所以它本身完全没必要入栈,只需要把栈首元素拿出来判断就好了。这个时候,会发生三种情况:
First,是[
,匹配了,栈顶的[
退栈,程序继续
Second,不是[
,程序输出不匹配
Third,栈空了,程序输出不匹配
注意:若不想额外费时费力判断栈空,有种缩短代码的技巧:在遍历开始前,于括号栈中塞入任何一个非括号字符就可以了,这样遇到栈空时,栈顶的字符绝对不是当前右括号想要的左括号(因为它甚至不是括号),从而默认触发第二个情况
若整个过程都完美匹配,但是最后发现括号栈不空,那么这也不对,因为栈内的左括号还没有匹配完毕,这个时候也要报错。
三、代码实现
根据上述策略,不难完成代码:
代码如下:
/**
*********************
* Is the bracket matching?
*
* @param paraString The given expression.
* @return Match or not.
*********************
*/
public static boolean bracketMatching(String paraString) {
// Step 1. Initialize the stack through pushing a '#' at the bottom.
CharStack tempStack = new CharStack();
tempStack.push('#');
char tempChar, tempPopedChar;
// Step 2. Process the string. For a string, length() is a method
// instead of a member variable.
for (int i = 0; i < paraString.length(); i++) {
tempChar = paraString.charAt(i);
switch (tempChar) {
case '(':
case '[':
case '{':
tempStack.push(tempChar);
break;
case ')':
tempPopedChar = tempStack.pop();
if (tempPopedChar != '(') {
return false;
} // Of if
break;
case ']':
tempPopedChar = tempStack.pop();
if (tempPopedChar != '[') {
return false;
} // Of if
break;
case '}':
tempPopedChar = tempStack.pop();
if (tempPopedChar != '{') {
return false;
} // Of if
break;
default:
// Do nothing
}// Of switch
} // Of for
tempPopedChar = tempStack.pop();
if (tempPopedChar != '#') {
return false;
} // Of if
return true;
}// Of bracketMatching
代码层面,我们用的switch语句完成了这种不同左右括号匹配的条件,增加了可读性。
这里我们按照策略的提示,使用了非括号字符’#'放在栈内垫底避免栈空的额外判断。
上面的switch的默认情况就是针对非括号情况,按照我们的题意,我们对于一切非括号一律按照透明处理。
三、数据模拟
模拟代码如下:
public static void main(String args[]) {
CharStack tempStack = new CharStack();
boolean tempMatch;
String tempExpression = "[2 + (1 - 3)] * 4";
tempMatch = bracketMatching(tempExpression);
System.out.println("Is the expression " + tempExpression + " bracket matching? " + tempMatch);
tempExpression = "( ) )";
tempMatch = bracketMatching(tempExpression);
System.out.println("Is the expression " + tempExpression + " bracket matching? " + tempMatch);
tempExpression = "()()(())";
tempMatch = bracketMatching(tempExpression);
System.out.println("Is the expression " + tempExpression + " bracket matching? " + tempMatch);
tempExpression = "({}[])";
tempMatch = bracketMatching(tempExpression);
System.out.println("Is the expression " + tempExpression + " bracket matching? " + tempMatch);
tempExpression = ")(";
tempMatch = bracketMatching(tempExpression);
System.out.println("Is the expression " + tempExpression + " bracket matching? " + tempMatch);
}// Of main
简单来说,设置了五种情况判断:
"[2 + (1 - 3)] * 4"
"( ) )"
"()()(())"
"({}[])"
")("
目测来看,只有第一、三、四是匹配的,其余不匹配
运行结果:
证明我们是正确的。
总结:
括号匹配是栈的一个基础功能,同时也是非常重要的功能,因为这个方法是栈后面许多经典算法的一个基础,包括算术字符串的识别计算。
算术字符串就是一个可用识别计算的字符串,这个算术字符串可以是我们正常的字符串“ [2 + (1 - 3)] * 4 ”这种,也可以是“ +1-*6-9#1/9#4 ”这种前缀表达式,它们都符合括号匹配相同的思想)
解决这种问题的关键在于什么时候匹配,与谁匹配,以及怎么进行出入栈操作,怎么判断出入栈的条件等等。要针对不同的问题导出不同的原则(比如这里的符号左右匹配,算术字符串的加减乘数优先级),并且为不同的原则采取适当的栈内优先级,有选择的一步步解决问题。
另外,本代码使用的预先在栈底填入一个非法字符的方案可以方便避免一些空栈的麻烦判断,是一种非常常用的栈技巧,要记住。