Check Balance with Stack
新开了一门外教课程,Object-oriented Programming(JAVA), 记录一些学习经验,以及部分和c++的区别感悟。本来本次作业不准备记录,但课上老师的一些point个人感觉很重要,故决定记录。
本文主要有三部分:
- Check Balance的面向题目实现和面向工程实现的感悟(Java API, Stack<>)
- Stack的 ArrayList 实现 – ArrayStack
- Stack的 linked-list(ListNode) 实现 – ListStack
Plus: 之前学习数据结构对队列栈等的知识有所掌握,但都是基于c++实现的,且部分过程没有考虑太深。这次学习中学习到了很多。题目要求基本和 leetcode有效的括号 题目一样,在此不再赘述。
1. Check Balance的面向题目实现和面向工程实现的感悟(Java API, Stack<>)
首先是面向题目的实现,很容易对这三类括号做if else判断,并且很容想到结合栈解决。代码如下:
public class CheckBalance {
private String str;
private int index_num; // 题目要求记录错误位置,本文可忽略
public CheckBalance(String input) {
this.str = input;
this.index_num = 0;
}
public boolean balanced() {
Stack<Character> sta = new Stack<Character>();
for (int i = 0; i < str.length(); i++)
{
if (str.charAt(i) == '[' || str.charAt(i) == '{' || str.charAt(i) == '(')
{
sta.push(str.charAt(i));
}
if (str.charAt(i) == ']')
{
if (sta.isEmpty() || sta.peek() != '[') { this.index_num = i; return false; }
else { sta.pop(); }
}
if (str.charAt(i) == '}')
{
if (sta.isEmpty() || sta.peek() != '{') { this.index_num = i; return false; }
else { sta.pop(); }
}
if (str.charAt(i) == ')')
{
if (sta.isEmpty() || sta.peek() != '(' ) { this.index_num = i; return false; }
else { sta.pop(); }
}
}
if (sta.isEmpty()) { return true; }
else { this.index_num = str.length(); return false; }
}
public int index() {
return this.index_num;
}
然后是面向工程的实现,用一个字符串记录保存括号的情况,这样以后维护起来容易(比如增加检查 <> 需求,只需要修改一小部分,在数组里添加这对括号即可);并且这样相当于完成了对检查括号平衡这一类问题的解决逻辑,相对于只是解决简单的三类括号的平衡问题,更加符合需求。这里借用老师的话: Good code is dynamic code. 代码如下:
Plus. 再增加一些成员函数,封装类似于sta.peek() != OPENINGS.charAt(temp)这样的语句,使得代码的可读性会更好,但为了与之前的对比,本文不做进一步逻辑抽象。
public class CheckBalance {
private static final String OPENINGS = "([{";
private static final String CLOSINGS = ")]}";
private String str;
private int index_num;
public CheckBalance(String input) {
this.str = input;
this.index_num = 0;
}
public boolean balanced() {
Stack<Character> sta = new Stack<Character>();
for (int i = 0; i < str.length(); i++)
{
if (OPENINGS.indexOf(str.charAt(i)) > -1)
{
sta.push(str.charAt(i));
}
if (CLOSINGS.indexOf(str.charAt(i)) > -1)
{
int temp = CLOSINGS.indexOf(str.charAt(i));
if (sta.isEmpty() || sta.peek() != OPENINGS.charAt(temp) ) { this.index_num = i; return false; }
else { sta.pop(); }
}
}
if (sta.isEmpty()) { return true; }
else { this.index_num = str.length(); return false; }
}
public int index() {
return this.index_num;
}
}
2. Stack的ArrayList实现 – ArrayStack
用ArrayList(Java API)实现Stack。本部分无特别之处。个人感觉与cpp中用vector实现stack区别不大。需要注意的是,创建ArrayList时指定类型必须是对象类型,它不能是原始类型。(int 改为 Integer,即primitive types to wrapper classes)
3. Stack的ListStack实现 – ListStack
这里的interesting point比较多。
1.首先,与cpp相同的是,java new出来的也是动态内存,但是,cpp需要手动delete释放(即cpp内存管理知识,堆区等等),不然会内存泄漏,但是java自带垃圾回收机制,所以不需要手动释放空间。(这里具体的垃圾回收机制目前没学到,JVM知识之后若决定继续学习Java再补充)
2.第二,Java的内存回收机制,导致在链表情况下,如给定一个链表和头指针(java不存在指针,即指向第一个节点的一个头,如ListNode head = new ListNode(val);),若调用head = head.next; 随着调用同时也在丢失链表(因为没有东西再指向那块new出来的动态内存,会直接被回收)。所以,要ListNode current = head; curren = current.next;这样改变cur不会损坏链表。这点与cpp不同,因为cpp中new出来在堆区的动态内存,不delete是不会释放的。
3.第三,这点无关cpp还是java,算是用链表实现栈这种数据结构,进行设计时需要考虑的。因为栈是后入先出的结构,即后push的先pop,所以考虑这种情况下,不应该push or pop在链表尾(即push的数据不应该添加到链表尾,pop的数据不应该去pop尾丢)。原因:首先栈是一头开口的数据结构,若每次在尾部添加,需要用一个while(cur.next != null)循环把cur指到尾节点,这样使得当已存链表已经数据量很大时,push和pop的时间复杂度都为O(n),很不合理。所以用链表实现栈时,我们应该在链表头push,在链表头pop,这样无论链表数据多大,再进行push和pop的时间复杂度都是常数时间。
4.总结
1.代码除了实现功能需求,在工程实现时,要多考虑代码的易维护性,想一想有没有办法使得代码更容易维护,若增加需求对源代码的改动是否很大。同时,要学会在编写代码时进行整体架构的逻辑抽象封装,这样会增加代码的易读性。
2. 使用ArrayList等API,注意< primitive types to wrapper classes >
3. cpp的手动内存管理与java的垃圾回收机制的区别,导致某些场景java需注意是否已经改变了动态内存数据。
4. 除了一些具体的场景题目,设计各种数据结构或其他结构时,要注意算法的时间复杂度。