上一节说了递归控制,最后分析了递归函数的缺点,我们现在使用循环控制将递归函数进行改造,使stack的压力减小,提高程序的运行效率。
循环控制要点:
1.循环不变式(loop invariant)
循环不变式是一句断言定义各变量所需满足的条件,在写循环控制的时候需要重点思考,这么说太抽象,具体看后面例题~
2.循环书写方法:
- 定义循环不变式,并在循环体每次结束后保持循环不变式
- 先一般,后特殊
- 每次必须向前推进循环不变式中涉及的变量值
- 每次推进的规模必须为1
- 编写时假设已经循环了很多次,思考下一次该怎么循环,而不是思考第一次应该怎么循环
3.技巧:
把问题切开,假设一部分已经完成,一部分未完成,思考下一步如何往前推进。
例子1:链表反转
上节中的链表反转使用递归实现,假设链表有一万活着更多元素,运行起来效率极低,而且还有stackoverflow的问题,使用循环实现,效率很高。
思路分析如图:
我们将链表逻辑分开,假设链表左边已经完成了反转,右边没有完成反转,那么只需要思考下一步我怎么把值为3的这个节点反转,我们只需要将currentHead节点指向newHead节点,并将currentHead节点和newHead向后移动即可。完成后,在判断一些边界条件,在while循环中思考如何去除边界条件和退出循环。
我们具体看代码分析:
public class LinkedListReverser {
public Node reverseLinkedList(Node head){
Node newHead = null;
Node curHead = head;
//loop invariant
//newHead points to the linked list already reversed
//curHead points to the linked list not yet reversed
while (curHead!=null){
//loop invariant holds.
Node next = curHead.getNext();
curHead.setNext(newHead);
newHead = curHead;
curHead = next;
}
//loop invariant holds.
return newHead;
}
}
例子2:删除链表中的某个元素
题目如图:
我们现在要把链表中的节点值为2的节点删除,思路不变,首先假设左边某部分的链表意见删除完毕(比如3节点旁边的2),下面思考如何删除右边部分的节点,我们首先需要判断3右边的节点是否为2,若是,则将指针指向下一个节点,这样就实现了删除该节点,如果3右边节点不是2,next直接指向右边的节点即可。需要思考的是一些边界条件,比如链表为空,或者链表第一个节点就为要删除的元素等等,使用while、if循环处理边界情况。
代码实现如下:
public class LinkedListDeletor {
public Node deleteIfEquals(Node head,int value){
while (head.getValue()==value){
head = head.getNext();
}
if (head==null){
return null;
}
Node prev = head;
//loop invariant:list nodes from head up to prev has been
//processed.(Nodes with values equals to value are deleted)
while (prev.getNext()!=null) {
if (prev.getNext().getValue()==value){
//delete it
prev.setNext(prev.getNext().getNext());
}else {
prev = prev.getNext();
}
}
return head;
}
}
只要明确了循环不变式,了解了边界条件,写一个循环控制的程序也就不难了,而且循环控制有很多好处,但是循环函数也不是万能的,很多问题无法解决,需要我们具体问题具体分析。下一节是一些关于边界控制的考虑问题。
ps:本打算每天(至少2天)就更新的,结果实验室来个个临时项目,估计要花点时间了~