递归
一、关于递归
递归(Recursion)是指在函数的定义中使用函数自身的方法,通俗的说就是函数本身自己调用自己。格外重要的是,这个函数必须有明确的结束条件,否则就会导致无限递归的情况。如下:
public static int sumToN(int paraN) {
// 结束条件
if (paraN <= 0) {
return 0;
} // Of if
// 在函数中调用自己
return sumToN(paraN - 1) + paraN;
} // Of sumToN
关于递归还需注意与循环的区别,递归顾名思义有一层一层往下的递,还有重要的归。当函数被调用时,它的变量空间是创建于运行时的堆栈上的,假定我以5这个值调用上述递归函数。堆栈内容如下表,栈顶位于最下方:
paraN | sumToN | return | |
---|---|---|---|
第一次调用 | 5 | sumToN(5) | sumToN(4) + 5 |
第二次调用 | 4 | sumToN(4) | sumToN(3) + 4 |
第三次调用 | 3 | sumToN(3) | sumToN(2) + 3 |
第四次调用 | 2 | sumToN(2) | sumToN(1) + 2 |
第五次调用 | 1 | sumToN(1) | sumToN(0) + 1 |
第六次调用 | 0 | sumToN(0) | 0 |
从上表的信息很容易看出,递归是如何将一个问题逐渐最小化解决的,从方法调用开始,函数return结果都会被压入栈,直到方法调用结束,最后从栈顶逐个返回得到结果。经过逐个代入,结果如下:
paraN | sumToN | return |
---|---|---|
0 | sumToN(0) | 0 |
1 | sumToN(1) | 0 + 1 |
2 | sumToN(2) | 0 + 1 + 2 |
3 | sumToN(3) | 0 + 1 + 2 + 3 |
4 | sumToN(4) | 0 + 1 + 2 + 3 + 4 |
5 | sumToN(5) | 0 + 1 + 2 + 3 + 4 + 5 = 15 |
二、递归求和与斐波那契数列
关于递归求和的体会在上段中已经作为例子详细阐述了,这段中就不再说明了。
斐波那契数列是指从第三项开始,每一项都等于前两项之和的这样一个数列(第一个和第二个数分别是1和1),可表示为如下形式:
a
n
=
a
n
−
1
+
a
n
−
2
a_n = a_{n-1} + a_{n-2}
an=an−1+an−2显然这是一个线性递推数列。求N的斐波那契数列代码实现如下:
public static int fibonacci(int paraN) {
if (paraN <= 0) {
// Negative values are invalid. Index 0 corresponds to the first element 0.
return 0;
} if (paraN ==1 ) {
// Basis.
return 1;
} // Of if
return fibonacci(paraN - 1) + fibonacci(paraN - 2);
} // Of fibonacci
三、代码与运行结果
package datastructure.stack;
/**
*
*
*@author Weijie Pu weijiepu@163.com.
*/
public class Recursion {
/**
*
********************
* Sum to N. No loop, however a stack is used.
*
* @param paraN
* The given value.
* @return The sum.
********************
*/
public static int sumToN(int paraN) {
if (paraN <= 0) {
// Basis.
return 0;
} // Of if
return sumToN(paraN - 1) + paraN;
} // Of sumToN
/**
********************
* Fibonacci sequence.
*
* @param paraN
* The given value.
* @return The sum.
********************
*/
public static int fibonacci(int paraN) {
if (paraN <= 0) {
// Negative values are invalid. Index 0 corresponds to the first element 0.
return 0;
} if (paraN ==1 ) {
// Basis.
return 1;
} // Of if
return fibonacci(paraN - 1) + fibonacci(paraN - 2);
} // Of fibonacci
/**
********************
* The entrance of the program
*
* @param args
* Not used now.
********************
*/
public static void main(String args[]){
int tempValue = 5;
System.out.println("0 sum to " + tempValue + " = " + sumToN(tempValue));
tempValue = -1;
System.out.println("0 sum to " + tempValue + " = " + sumToN(tempValue));
for (int i = 0; i < 10; i++) {
System.out.println("Fibonacci " + i + ": " + fibonacci(i));
} // Of for i
} // Of main
} // class Recursion
运行结果:
总结
递归算法的一个重要思路就是把规模大的问题转化为规模小的子问题来解决,这些问题的演化是一个从大到小,由近及远的过程,并且会有一个明确的终点(临界点),一旦到达了这个终点,就不用再往更小、更远的地方走下去。最后,从这个终点开始,原路返回到原点,原问题以此解决。
在初学递归时,容易分不清递归和循环,两者有一个显然的共同特点,即做重复任务,但递归通常很直白地描述一个问题的求解过程,如这段代码中求和函数里 sumToN(paraN - 1) + paraN,而使用循环的算法有时并不会那么清晰地描述解决问题的步骤。这两者是两种不同的解决问题的思路。递归的简洁与可读性是最大的优点,而其效率问题是难以避免的缺点。
链队列
一、关于链队列
链队列的实现并不难,与前两天写的链表结构相似。不同的是链队列不仅有头指针,还有链表没有的尾指针,且入队仅操作尾部,出队仅操作头部(队列特点,先进先出,后进后出)。
二、链队列实现逻辑
创建一个内嵌类,节点Node由一个需要存储的对象data以及对下个节点的引用组成。
class Node {
int data;
Node next;
public Node(int paraValue) {
data = paraValue;
next = null;
} // Of the constructor
} // Of class Node
头尾两个指针
Node header;
Node tail;
构造一个空链队列,头指针和尾指针都指向头结点。
public LinkedQueue() {
header = new Node(-1);
tail = header;
}
接下来就需要实现入队和出队的功能,参考前面链表部分的插入和删除操作,注意插入和删除分别在链队列的尾指针和头指针处进行。入队操作如下:
首先创建一个新的节点。
Node tempNode = new Node(paraValue);
然后将新节点插入到尾指针指向的节点之后,再使尾指针指向新节点,完成入队。
tail.next = tempNode;
tail = tempNode;
出队操作时,要检查队列是否为空,若为空,则无对象可出列。因为队列先进先出的特性,出队操作是在头指针指向的下一个节点处进行的,在进行几次出队操作后,若头指针指向的下一个节点为空,即只剩下头结点,则尾指针也指向头结点,即tail = header。
header.next = header.next.next;
三、代码与数据测试
package datastructure.queue;
/**
* Linked queue
*
* @auther Weijie Pu weijiepu@163.com.
*/
public class LinkedQueue {
/**
* An inner class
*/
class Node {
/**
* The data
*/
int data;
/**
* The reference to the next node.
*/
Node next;
/**
********************
* The constructor.
*
* @param paraValue
********************
*/
public Node(int paraValue) {
data = paraValue;
next = null;
} // Of the constructor
} // Of class Node
/**
* The header of the queue.
*/
Node header;
/**
* The tail of the queue.
*/
Node tail;
/**
********************
* Construct an empty sequential list.
********************
*/
public LinkedQueue() {
header = new Node(-1);
// header.next = null;
tail = header;
} // Of the first constructor
/**
********************
* Enqueue.
*
* @param paraValue
* The value of the new node.
********************
*/
public void enqueue(int paraValue) {
Node tempNode = new Node(paraValue);
tail.next = tempNode;
tail = tempNode;
} // Of enqueue
/**
********************
* Dequeue
*
* @return The value at the header.
********************
*/
public int dequeue() {
if (header == tail) {
System.out.println("No element in the queue");
return -1;
} // Of if
int resultValue = header.next.data;
header.next = header.next.next;
// The queue becomes empty.
if (header.next == null) {
tail = header;
} // Of if
return resultValue;
} // Of dequeue
/**
********************
* Overrides the method claimed in Object, the superclass of any class.
********************
*/
public String toString() {
String resultString = "";
if (header.next == null) {
return "empty";
} // Of if
Node tempNode = header.next;
while (tempNode != null) {
resultString += tempNode.data + ", ";
tempNode = tempNode.next;
} // Of while
return resultString;
} // Of toString
/**
********************
* The entrance of the program.
*
* @param args
* Not used now.
********************
*/
public static void main(String args[]) {
LinkedQueue tempQueue = new LinkedQueue();
System.out.println("Initialized, the list is: " + tempQueue.toString());
for (int i = 0; i < 5; i++) {
tempQueue.enqueue(i + 1);
} // Of for i
System.out.println("Enqueue, the queue is: " + tempQueue.toString());
tempQueue.dequeue();
System.out.println("Dequeue, the queue is: " + tempQueue.toString());
int tempValue;
for (int i = 0; i < 5; i++) {
tempValue = tempQueue.dequeue();
System.out.println("Looped delete " + tempValue + ", the new queue is: " + tempQueue.toString());
} // Of for i
for (int i = 0; i < 3; i++) {
tempQueue.enqueue(i + 10);
} // Of for i
System.out.println("Enqueue, the queue is: " + tempQueue.toString());
}// Of main
}// Of calss LinkedQueue
运行结果:
总结
链队列可以动态的申请和释放空间,在入队操作时不用检测队列是否已满,这也是链队列相较于顺序表的优点。队列是顺序结构,其存储的数据一个挨着一个,链表则是相当于一根线把存储数据连接起来,不难看出,在查找上,队列肯定是优于链表的,而在删除和插入上,链表只需把连接的线剪断再在断处用新的接上,所以实现起来很快。
今日代码量:217
累积:1506