今天看了算法图解第三章递归 写一下收货 省着以后忘
递归算法
递归就是不断的调用自己
递归和循环的关系
如果使用循环,程序效率会更高,如果使用递归,程序可能更容易被理解
递归的基线条件和递归条件
1.递归条件:自己调用自己
2.基线条件:如何让自己停下来
栈
push(Eitem):元素入栈
// push方法 元素入栈
public E push(E item)
{
addElement(item);
return item;
}
public synchronized void addElement(E obj)
{
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
pop():取出栈顶元素,并且将其从栈中移除
public synchronized E pop()
{
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
peek():取出栈顶元素,但是不从栈中移除元素
public synchronized E peek()
{
// 数组长度
int len = size();
if (len == 0)
throw new EmptyStackException();
// 取最后一个元素
return elementAt(len - 1);
}
empty():判断栈是否为空,为空返回true,否则返回false
public boolean empty()
{
// 写的挺美不是
return size() == 0;
}
search(Objecto):在栈中查找元素位置,位置从栈顶开始往下算,栈顶为1,
public synchronized int search(Object o)
{
int i = lastIndexOf(o);
if (i >= 0)
{
return size() - i;
}
return -1;
}
// 底层用的循环 写的真美
public synchronized int lastIndexOf(Object o, int index)
{
if (index >= elementCount)
throw new IndexOutOfBoundsException(index + " >= "+ elementCount);
if (o == null)
{
for (int i = index; i >= 0; i--)
if (elementData[i]==null)
return i;
}
else
{
for (int i = index; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
栈:先进后出 ,计算机内部是如何使用调用栈的
public static void main(String[] args)
{
int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
}
1、int i=1;分两步:第一步,操作数栈中放1,第二步赋值操作,把操作数栈中的1赋值给局部变量表中的位置1的变量i,同时操作数栈中的1消除
2、i++;也分两步,第一步,先把局部变量表中i的值取出放入操作数栈中的栈顶,第二步,把局部变量表中的i为1的值自增1,变成2,这需要记住。也就是自增、自减操作都是直接修改变量的值,不经过操作数栈。
所以 i = i++ 后 i的值还是1 同理 j 也是 1
3、这里我简化了 因为j=i++的时候 局部变量表里的情况为 j = 1; i = 2
4、K的计算 i = 2 ,2压栈 ,++i ,局部变量自增后压栈 局部变量表 i = 3,栈为 2,3 , 在计算 i++,局部变量表自增,3 压入栈,局部变量里现在为 j = 1,i = 4 , 栈中 2,3,3现在计算K = 2 + 3*3 = 11
思考的问题:
自己手动实现一个栈1
自己手动实现一个栈2
栈数据结构能做什么
递归的问题
1.递归写错了会造成栈内存溢出
2.如果效率低 考虑尾部递归
正常的递归
public static int recursive(int n)
{
return (n == 1) ? 1 : n * recursive (n - 1);
}
recursive(5)
{5 * recursive(4)}
{5 * {4 * recursive(3)}}
{5 * {4 * {3 * recursive(2)}}}
{5 * {4 * {3 * {2 * recursive(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120
尾部递归
public static int tailRescuvie(int n, int a)
{
return (n == 1) ? a : tailRescuvie(n - 1, a * n);
}
public static int tailRescuvie(int n)
{
return (n == 0) ? 1 : tailRescuvie(n, 1);
}
tailRescuvie(5)
tailRescuvie(5, 1)
tailRescuvie(4, 5)
tailRescuvie(3, 20)
tailRescuvie(2, 60)
tailRescuvie(1, 120)
很容易看出, 普通的线性递归比尾递归更加消耗资源, 在实现上说, 每次重复的过程
调用都使得调用链条不断加长. 系统不得不使用栈进行数据保存和恢复.而尾递归就
不存在这样的问题, 因为他的状态完全由n和a保存
原理:
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。