栈:
栈是限定仅在表尾进行插入和删除操作的线性表【注意它也是属于之前我们学习的线性表的一类】,允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表,下面用示例图来瞅一下它:
栈的实现:
顺序方式:
先来看一下它的示意图:
而它的实际应用在JDK中有一个现成的Stack类,如下:
而关于啥是Vector,下面可以看一下下面这段文字来了解它:
所以可以大致瞅下在Vector中基本都是同步方法:
其中看一下它的添加方法:
其中elementData当然就是一个数组啦:
那咱们来看一下它的扩容,基本上跟ArrayList差不多:
而在ArrayList中是1.5倍的增长,其实现差不多,接下来再看一下移除元素的方法:
对于栈经典的方法可以瞅下:
链式方式:
用个示意图来瞅下啥叫链式方式?
跟之前咱们学的单向链表好像,其中入栈操作用示例图可以表示为:
比较容易理解,接着出栈:
说到链表,在经典算法中会有一个链表逆序的问题,其实就可以利用进栈的方式来实现,具体说下思路:
其实就可以先将将链表的结点相继的压栈既可,比如先拿node1入栈:
然后此时再相继将其它两个结点压栈:
此时再从栈中拿元素,其实就实现了链表的逆序了~~
逆波兰表达式【了解其推导过程】:
先来看两个表达式:
①、a / 2 和 a >> 1
②、a * 2 和 a << 1
我们都知道上面两个表达式的写法一个是传统的,一个是位移的,他们的结果其实是等价的,但是!!我们知道位移方式的性能肯定是比传统的方式要高效得多,那,为啥要高效呢?这是因为位移操作底层转到汇编只需要一个指令集既可算出结果,而我们传统的方式最少得6个以上的指令集,为啥呢?这里就需要了解逆波兰表达式了,因为计算机对于四则运算其实就是用它来进行计算的,下面来对它进行了解:
对于我们看到的一个四则运算表达式,如:“9+(3-1)X3+10/2=20”,它其实是一个中缀表达式,但是像到了高级语言像java、c++,它会将其转换成后缀表达式,那怎么将其转换成后缀表达式呢?有这么一个规则:“数字输出,运算符进栈,括号匹配出栈,比栈顶优先级低就出栈”,啥意思?下面用示意图来将上面的中缀表达式转换成后缀表达式的整个过程给推一下,过程还是蛮复杂的,得要有耐心,下面开始:
“9+(3-1)X3+10/2=20”,由于它是数字,则直接输出:
继续往下:
由于它是运算符,所以直接进栈,不过在进栈之前先来了解一下运算符的关系表,如下:
其中在栈顶会有一个“#”符,所以此时栈的情况就为:
其中运算符有这么个规则:“数字输出,运算符进栈,括号匹配出栈,比栈顶优先级低就出栈”,由根据表中可以发现"#"<"+",如:
所以此时的"+"就不会出栈,接着继续:
根据括号的规则: “数字输出,运算符进栈,括号匹配出栈,比栈顶优先级低就出栈”,目前只有一个左括号,还没有碰到右括号匹配的情况,所以直接入栈:
接下来碰到数字3,则直接输出:
接着碰到运算符-,直接压栈:
接下来则是数字1,直接输出:
接下来则到了右括号,入栈:
此时别忘了:“数字输出,运算符进栈,括号匹配出栈,比栈顶优先级低就出栈”,由于目前括号已经左右都有成匹配了,所以直接将其中的符号给输出出来,如下:
接下来继续:
入栈:
继续碰到一个数字3,则直接输出:
接着继续就会遇到+号运算符:
在入栈时,由于它比x优先级要低,所以此时先将x号出栈,如下:
然后此时由于在栈中也有一个+,而根据表中的优先级来看,其实可以看成是小于,所以直接可以出栈了,如下:
接下来又到数字了:
直接输出既可:
接着到/运算符,由于它比+优先级要高,所以直接入栈既可:
最后数字2直接输出:
接下来将栈中的运算符给输出,除了最底下的那个"#",所以最终后缀表达式则为:
好,接下来就是根据后缀表达式来计算结果,其规则为:
1、数字入栈;
2、符号就取2个进行计算,再入栈;
所以接下来咱们按照此规则来对后缀表达式进行结果计算,看是否为20,如下:
都是数字,则相继入栈既可:
接下来则遇到了一个减号符:
则从栈中取两个数,从右往左放,则就是:
接下来继续来根据后缀表达式往后走,取到一个3,直接入栈:
接下来是"*"号运算符了,则同理,从栈中取2个值从右往左放,算出结果再放回栈中,所以此时的形式又变为:
此时又遇到一个"+"号运算符,还以从栈中取俩数字,然后进行加号运算并将结果又压入栈中:
接下来则是2个数字,则直接入栈,如下:
接下来再是"/"运算符了,从栈中搞两个数值进行计算后再压栈:
然后最后还剩一个"+"运算符,最终从栈中取两数相加得到20,再入栈,最终的结果就为:
这就是后缀表达式最终算出的结果,跟咱们前缀表达式9+(3-1)X3+10/2=20。以上就是对于高级语言对于我们的运算所做的操作,知道了这个之后,我们再回过头来审视一下:
①、a / 2 和 a >> 1
很明显a / 2是前缀表达式,对于程序背后首先会将其转换成一个后缀表达式为a 2 /,然后再进行相应的入栈出栈的操作,最终算出结果,很明显这种方式的性能没有a>>1的位移操作高。
递归基础:
定义:
程序调用自身的编程技巧称为递归(recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,
它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的能力在于用有限的语句来定义对象的无限集合。
一般来说,递归需要有边界条件、递归前进段和递归返回段。
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
执行特点:
先来看一个简单的递归,然后对其进行分析:
那这个代码执行的结果是啥呢?直接运行看结果,会让你亮瞎眼的:
为啥是这样呢?程序虽简单,但是利用这个简单的递归来了解它的一个特点是很好的一个方法,所以下面来解析下:
阶乘算法:
这个比较简单,先来看一下啥叫阶乘:
下面直接上代码:
运行结果:
斐波那契数列算法:
这个在之前https://www.cnblogs.com/webor2006/p/6743573.html的博客中用C++实现过,这里用Java再来回忆一下它,先来回忆一下它的定义:
具体实现为:
运行结果:
汉诺塔算法:
啥是汉诺塔呢?先网上搜一下:
其规则为:在进行转移操作时,都必须确保大盘在小盘下面,且每次只能移动一个圆盘,最终c柱上有所有的盘子且也是从上到下按从小到大的顺序。
以下以三个盘为例,来看一下整个的移动过程:
这样经过上面的步骤之后,就将左边的盘子最终移到了最右边,且顺序还是保持小盘子在上面,大盘子在下面。那如果用代码怎么来算出整个移动的过程呢,下面来看下:
先定义方法:
首先如果是只有一个盘子,则直接输出既可:
那如果是有多个盘子,那逻辑要如何写呢?这里就得通过图来进行理解,需要用形象的思维来理解就比较容易理解了:
第一步:第n-1个盘子借助于第3个柱子,将其挪到第2个柱子上:
所以代码可以这样写:
第二步:左边的最底部的直接挪到最右边,如下:
所以代码如:
第三步:n-1从中间开始,通过左边的柱子,最终放到最后边既完成了整个过程,如下:
那,难道真就解出这个解了么?下面调用一下来看结果就晓得了:
那下面咱们对照着之前图解的完整图来看一下咱们的输出是否如预期:
1------>3:
1------>2:
3------>2:
1------>3:
2------>1:
2------>3:
1------>3:
可以看到,用递归思想就是将大的步骤用小的步骤来进行梳理,接下来咱们把整个代码的执行过程再梳理一下,会有一个很神奇的现象出现:
以上的过程,是不是其实就是树的中序遍历过程~~