最后
资料过多,篇幅有限
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。
postorder(root.right);
System.out.println(root.element + " ");
}
* 代码15 二叉查找树的简单实现
/**
-
@author JackalTsc
*/
public class MyBinSearchTree<E extends Comparable> {// 根
private TreeNode root;// 默认构造函数
public MyBinSearchTree() {
}// 二叉查找树的搜索
public boolean search(E e) {TreeNode<E> current = root; while (current != null) { if (e.compareTo(current.element) < 0) { current = current.left; } else if (e.compareTo(current.element) > 0) { current = current.right; } else { return true; } } return false;
}
// 二叉查找树的插入
public boolean insert(E e) {// 如果之前是空二叉树 插入的元素就作为根节点 if (root == null) { root = createNewNode(e); } else { // 否则就从根节点开始遍历 直到找到合适的父节点 TreeNode<E> parent = null; TreeNode<E> current = root; while (current != null) { if (e.compareTo(current.element) < 0) { parent = current; current = current.left; } else if (e.compareTo(current.element) > 0) { parent = current; current = current.right; } else { return false; } } // 插入 if (e.compareTo(parent.element) < 0) { parent.left = createNewNode(e); } else { parent.right = createNewNode(e); } } return true;
}
// 创建新的节点
protected TreeNode createNewNode(E e) {
return new TreeNode(e);
}
}
// 二叉树的节点
class TreeNode<E extends Comparable> {
E element;
TreeNode<E> left;
TreeNode<E> right;
public TreeNode(E e) {
element = e;
}
}
上面的代码15主要展示了一个自己实现的简单的二叉查找树,其中包括了几个常见的操作,当然更多的操作还是需要大家自己去完成。因为在二叉查找树中删除节点的操作比较复杂,所以下面我详细介绍一下这里。
* **二叉查找树中删除节点分析**
要在二叉查找树中删除一个元素,首先需要定位包含该元素的节点,以及它的父节点。假设current指向二叉查找树中包含该元素的节点,而parent指向current节点的父节点,current节点可能是parent节点的左孩子,也可能是右孩子。这里需要考虑两种情况:
1. current节点没有左孩子,那么只需要将patent节点和current节点的右孩子相连。
2. current节点有一个左孩子,假设rightMost指向包含current节点的左子树中最大元素的节点,而parentOfRightMost指向rightMost节点的父节点。那么先使用rightMost节点中的元素值替换current节点中的元素值,将parentOfRightMost节点和rightMost节点的左孩子相连,然后删除rightMost节点。
// 二叉搜索树删除节点
public boolean delete(E e) {
TreeNode<E> parent = null;
TreeNode<E> current = root;
// 找到要删除的节点的位置
while (current != null) {
if (e.compareTo(current.element) < 0) {
parent = current;
current = current.left;
} else if (e.compareTo(current.element) > 0) {
parent = current;
current = current.right;
} else {
break;
}
}
// 没找到要删除的节点
if (current == null) {
return false;
}
// 考虑第一种情况
if (current.left == null) {
if (parent == null) {
root = current.right;
} else {
if (e.compareTo(parent.element) < 0) {
parent.left = current.right;
} else {
parent.right = current.right;
}
}
} else { // 考虑第二种情况
TreeNode<E> parentOfRightMost = current;
TreeNode<E> rightMost = current.left;
// 找到左子树中最大的元素节点
while (rightMost.right != null) {
parentOfRightMost = rightMost;
rightMost = rightMost.right;
}
// 替换
current.element = rightMost.element;
// parentOfRightMost和rightMost左孩子相连
if (parentOfRightMost.right == rightMost) {
parentOfRightMost.right = rightMost.left;
} else {
parentOfRightMost.left = rightMost.left;
}
}
return true;
}
>
> 平衡二叉树
>
>
>
平衡二叉树又称AVL树,它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMjQzNjkwLTY1NDhhNWNhNTIwNGEyMTkucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy82MDIvZm9ybWF0L3dlYnA?x-oss-process=image/format,png)
平衡二叉树
AVL树是最先发明的自平衡二叉查找树算法。在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
>
> 红黑树
>
>
>
红黑树是平衡二叉树的一种,它保证在最坏情况下基本动态集合操作的事件复杂度为O(log n)。红黑树和平衡二叉树区别如下:(1) 红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。(2) 平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。[点击查看更多](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
#### 四、图
* **简介**
图是一种较线性表和树更为复杂的数据结构,在线性表中,数据元素之间仅有线性关系,在树形结构中,数据元素之间有着明显的层次关系,而在图形结构中,节点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。图的应用相当广泛,特别是近年来的迅速发展,已经渗入到诸如语言学、逻辑学、物理、化学、电讯工程、计算机科学以及数学的其他分支中。
* **相关阅读**
因为图这部分的内容还是比较多的,这里就不详细介绍了,有需要的可以自己搜索相关资料。
(1) [《百度百科对图的介绍》](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
(2) [《数据结构之图(存储结构、遍历)》](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
>
> 这篇文章是常见数据结构与算法整理总结的下篇,上一篇主要是对常见的数据结构进行集中总结,这篇主要是总结一些常见的算法相关内容,文章中如有错误,欢迎指出。
>
>
>
一、概述
二、查找算法
三、排序算法
四、其它算法
五、常见算法题
六、总结
#### 一、概述
以前看到这样一句话,语言只是工具,算法才是程序设计的灵魂。的确,算法在计算机科学中的地位真的很重要,在很多大公司的笔试面试中,算法掌握程度的考察都占据了很大一部分。不管是为了面试还是自身编程能力的提升,花时间去研究常见的算法还是很有必要的。下面是自己对于算法这部分的学习总结。
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMjQzNjkwLWNiNzMyOGM4Nzk4NWY0YTIucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy82MDAvZm9ybWF0L3dlYnA?x-oss-process=image/format,png)
>
> 算法简介
>
>
>
算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。对于同一个问题的解决,可能会存在着不同的算法,为了衡量一个算法的优劣,提出了空间复杂度与时间复杂度这两个概念。
>
> 时间复杂度
>
>
>
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间度量记为 \*\* T(n) = O(f(n)) \*\*,它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度。这里需要重点理解这个增长率。
举个例子,看下面3个代码:
1、{++x;}
2、for(i = 1; i <= n; i++) { ++x; }
3、for(j = 1; j <= n; j++)
for(j = 1; j <= n; j++)
{ ++x; }
上述含有 ++x 操作的语句的频度分别为1 、n 、n^2,
假设问题的规模扩大了n倍,3个代码的增长率分别是1 、n 、n^2
它们的时间复杂度分别为O(1)、O(n )、O(n^2)
>
> 空间复杂度
>
>
>
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。
#### 二、查找算法
查找和排序是最基础也是最重要的两类算法,熟练地掌握这两类算法,并能对这些算法的性能进行分析很重要,这两类算法中主要包括二分查找、快速排序、归并排序等等。
>
> 顺序查找
>
>
>
顺序查找又称线性查找。它的过程为:从查找表的最后一个元素开始逐个与给定关键字比较,若某个记录的关键字和给定值比较相等,则查找成功,否则,若直至第一个记录,其关键字和给定值比较都不等,则表明表中没有所查记录查找不成功,它的缺点是效率低下。
>
> 二分查找
>
>
>
* **简介**
二分查找又称折半查找,对于有序表来说,它的优点是比较次数少,查找速度快,平均性能好。
二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x。
二分查找的时间复杂度为O(logn)
* **实现**
//给定有序查找表array 二分查找给定的值data
//查找成功返回下标 查找失败返回-1
static int funBinSearch(int[] array, int data) {
int low = 0;
int high = array.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (data == array[mid]) {
return mid;
} else if (data < array[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
#### 三、排序算法
排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。下面主要对一些常见的排序算法做介绍,并分析它们的时空复杂度。
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMjQzNjkwLTkwMGU0NjVhOTdkMjFkNzYucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy80NjcvZm9ybWF0L3dlYnA?x-oss-process=image/format,png)
常见排序算法
常见排序算法性能比较:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMjQzNjkwLWRhMWM4Yjk5N2ExNmMxN2MucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy82MzAvZm9ybWF0L3dlYnA?x-oss-process=image/format,png)
图片来自网络
上面这张表中有稳定性这一项,排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前和排序后他们的相对位置不发生变化。
下面从冒泡排序开始逐一介绍。
>
> 冒泡排序
>
>
>
* **简介**
冒泡排序的基本思想是:设排序序列的记录个数为n,进行n-1次遍历,每次遍历从开始位置依次往后比较前后相邻元素,这样较大的元素往后移,n-1次遍历结束后,序列有序。
例如,对序列(3,2,1,5)进行排序的过程是:共进行3次遍历,第1次遍历时先比较3和2,交换,继续比较3和1,交换,再比较3和5,不交换,这样第1次遍历结束,最大值5在最后的位置,得到序列(2,1,3,5)。第2次遍历时先比较2和1,交换,继续比较2和3,不交换,第2次遍历结束时次大值3在倒数第2的位置,得到序列(1,2,3,5),第3次遍历时,先比较1和2,不交换,得到最终有序序列(1,2,3,5)。
需要注意的是,如果在某次遍历中没有发生交换,那么就不必进行下次遍历,因为序列已经有序。
* **实现**
// 冒泡排序 注意 flag 的作用
static void funBubbleSort(int[] array) {
boolean flag = true;
for (int i = 0; i < array.length - 1 && flag; i++) {
flag = false;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
flag = true;
}
}
}
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
* **分析**
最佳情况下冒泡排序只需一次遍历就能确定数组已经排好序,不需要进行下一次遍历,所以最佳情况下,时间复杂度为\*\* O(n) \*\*。
最坏情况下冒泡排序需要n-1次遍历,第一次遍历需要比较n-1次,第二次遍历需要n-2次,...,最后一次需要比较1次,最差情况下时间复杂度为\*\* O(n^2) \*\*。
>
> 简单选择排序
>
>
>
* **简介**
简单选择排序的思想是:设排序序列的记录个数为n,进行n-1次选择,每次在n-i+1(i = 1,2,...,n-1)个记录中选择关键字最小的记录作为有效序列中的第i个记录。
例如,排序序列(3,2,1,5)的过程是,进行3次选择,第1次选择在4个记录中选择最小的值为1,放在第1个位置,得到序列(1,3,2,5),第2次选择从位置1开始的3个元素中选择最小的值2放在第2个位置,得到有序序列(1,2,3,5),第3次选择因为最小的值3已经在第3个位置不需要操作,最后得到有序序列(1,2,3,5)。
* **实现**
static void funSelectionSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
int mink = i;
// 每次从未排序数组中找到最小值的坐标
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[mink]) {
mink = j;
}
}
// 将最小值放在最前面
if (mink != i) {
int temp = array[mink];
array[mink] = array[i];
array[i] = temp;
}
}
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
* **分析**
简单选择排序过程中需要进行的比较次数与初始状态下待排序的记录序列的排列情况\*\* 无关**。当i=1时,需进行n-1次比较;当i=2时,需进行n-2次比较;依次类推,共需要进行的比较次数是(n-1)+(n-2)+…+2+1=n(n-1)/2,即进行比较操作的时间复杂度为** O(n^2) **,进行移动操作的时间复杂度为** O(n) **。总的时间复杂度为** O(n^2) \*\*。
最好情况下,即待排序记录初始状态就已经是正序排列了,则不需要移动记录。最坏情况下,即待排序记录初始状态是按第一条记录最大,之后的记录从小到大顺序排列,则需要移动记录的次数最多为3(n-1)。
简单选择排序是不稳定排序。
>
> 直接插入排序
>
>
>
* **简介**
直接插入的思想是:是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。
例如,排序序列(3,2,1,5)的过程是,初始时有序序列为(3),然后从位置1开始,先访问到2,将2插入到3前面,得到有序序列(2,3),之后访问1,找到合适的插入位置后得到有序序列(1,2,3),最后访问5,得到最终有序序列(1,2,3,5).
* **实现**
static void funDInsertSort(int[] array) {
int j;
for (int i = 1; i < array.length; i++) {
int temp = array[i];
j = i - 1;
while (j > -1 && temp < array[j]) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = temp;
}
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
* **分析**
最好情况下,当待排序序列中记录已经有序时,则需要n-1次比较,不需要移动,时间复杂度为\*\* O(n) **。最差情况下,当待排序序列中所有记录正好逆序时,则比较次数和移动次数都达到最大值,时间复杂度为** O(n^2) **。平均情况下,时间复杂度为** O(n^2) \*\*。
>
> 希尔排序
>
>
>
希尔排序又称“缩小增量排序”,它是基于直接插入排序的以下两点性质而提出的一种改进:(1) 直接插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。(2) 直接插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。[点击查看更多关于希尔排序的内容](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
>
> 归并排序
>
>
>
* **简介**
归并排序是分治法的一个典型应用,它的主要思想是:将待排序序列分为两部分,对每部分递归地应用归并排序,在两部分都排好序后进行合并。
例如,排序序列(3,2,8,6,7,9,1,5)的过程是,先将序列分为两部分,(3,2,8,6)和(7,9,1,5),然后对两部分分别应用归并排序,第1部分(3,2,8,6),第2部分(7,9,1,5),对两个部分分别进行归并排序,第1部分继续分为(3,2)和(8,6),(3,2)继续分为(3)和(2),(8,6)继续分为(8)和(6),之后进行合并得到(2,3),(6,8),再合并得到(2,3,6,8),第2部分进行归并排序得到(1,5,7,9),最后合并两部分得到(1,2,3,5,6,7,8,9)。
* **实现**
//归并排序
static void funMergeSort(int[] array) {
if (array.length > 1) {
int length1 = array.length / 2;
int[] array1 = new int[length1];
System.arraycopy(array, 0, array1, 0, length1);
funMergeSort(array1);
int length2 = array.length - length1;
int[] array2 = new int[length2];
System.arraycopy(array, length1, array2, 0, length2);
funMergeSort(array2);
int[] datas = merge(array1, array2);
System.arraycopy(datas, 0, array, 0, array.length);
}
}
//合并两个数组
static int[] merge(int[] list1, int[] list2) {
int[] list3 = new int[list1.length + list2.length];
int count1 = 0;
int count2 = 0;
int count3 = 0;
while (count1 < list1.length && count2 < list2.length) {
if (list1[count1] < list2[count2]) {
list3[count3++] = list1[count1++];
} else {
list3[count3++] = list2[count2++];
}
}
while (count1 < list1.length) {
list3[count3++] = list1[count1++];
}
while (count2 < list2.length) {
list3[count3++] = list2[count2++];
}
return list3;
}
* **分析**
归并排序的时间复杂度为O(nlogn),它是一种稳定的排序,java.util.Arrays类中的sort方法就是使用归并排序的变体来实现的。
>
> 快速排序
>
>
>
* **简介**
快速排序的主要思想是:在待排序的序列中选择一个称为主元的元素,将数组分为两部分,使得第一部分中的所有元素都小于或等于主元,而第二部分中的所有元素都大于主元,然后对两部分递归地应用快速排序算法。
* **实现**
// 快速排序
static void funQuickSort(int[] mdata, int start, int end) {
if (end > start) {
int pivotIndex = quickSortPartition(mdata, start, end);
funQuickSort(mdata, start, pivotIndex - 1);
funQuickSort(mdata, pivotIndex + 1, end);
}
}
// 快速排序前的划分
static int quickSortPartition(int[] list, int first, int last) {
int pivot = list[first];
int low = first + 1;
int high = last;
while (high > low) {
while (low <= high && list[low] <= pivot) {
low++;
}
while (low <= high && list[high] > pivot) {
high--;
}
if (high > low) {
int temp = list[high];
list[high] = list[low];
list[low] = temp;
}
}
while (high > first && list[high] >= pivot) {
high--;
}
if (pivot > list[high]) {
list[first] = list[high];
list[high] = pivot;
return high;
} else {
return first;
}
}
* **分析**
在快速排序算法中,比较关键的一个部分是主元的选择。在最差情况下,划分由n个元素构成的数组需要进行n次比较和n次移动,因此划分需要的时间是O(n)。在最差情况下,每次主元会将数组划分为一个大的子数组和一个空数组,这个大的子数组的规模是在上次划分的子数组的规模上减1,这样在最差情况下算法需要(n-1)+(n-2)+...+1= \*\* O(n^2) \*\*时间。
最佳情况下,每次主元将数组划分为规模大致相等的两部分,时间复杂度为\*\* O(nlogn) \*\*。
>
> 堆排序
>
>
>
* **简介**
在介绍堆排序之前首先需要了解堆的定义,n个关键字序列K1,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):(1) ki <= k(2i)且 ki <= k(2i+1) (1 ≤ i≤ n/2),当然,这是小根堆,大根堆则换成>=号。
如果将上面满足堆性质的序列看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有的非终端节点的值均不大于(或不小于)其左右孩子节点的值。
堆排序的主要思想是:给定一个待排序序列,首先经过一次调整,将序列构建成一个大顶堆,此时第一个元素是最大的元素,将其和序列的最后一个元素交换,然后对前n-1个元素调整为大顶堆,再将其第一个元素和末尾元素交换,这样最后即可得到有序序列。
* **实现**
//堆排序
public class TestHeapSort {
public static void main(String[] args) {
int arr[] = { 5, 6, 1, 0, 2, 9 };
heapsort(arr, 6);
System.out.println(Arrays.toString(arr));
}
static void heapsort(int arr[], int n) {
// 先建大顶堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapAdjust(arr, i, n);
}
for (int i = 0; i < n - 1; i++) {
swap(arr, 0, n - i - 1);
heapAdjust(arr, 0, n - i - 1);
}
}
// 交换两个数
static void swap(int arr[], int low, int high) {
int temp = arr[low];
arr[low] = arr[high];
arr[high] = temp;
}
// 调整堆
static void heapAdjust(int arr[], int index, int n) {
int temp = arr[index];
int child = 0;
while (index * 2 + 1 < n) {
child = index * 2 + 1;
// child为左右孩子中较大的那个
if (child != n - 1 && arr[child] < arr[child + 1]) {
child++;
}
// 如果指定节点大于较大的孩子 不需要调整
if (temp > arr[child]) {
break;
} else {
// 否则继续往下判断孩子的孩子 直到找到合适的位置
arr[index] = arr[child];
index = child;
}
}
arr[index] = temp;
}
}
* **分析**
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。堆排序时间复杂度也为O(nlogn),空间复杂度为O(1)。它是不稳定的排序方法。与快排和归并排序相比,堆排序在最差情况下的时间复杂度优于快排,空间效率高于归并排序。
#### 四、其它算法
在上面的篇幅中,主要是对查找和常见的几种排序算法作了介绍,这些内容都是基础的但是必须掌握的内容,尤其是二分查找、快排、堆排、归并排序这几个更是面试高频考察点。(这里不禁想起百度一面的时候让我写二分查找和堆排序,二分查找还行,然而堆排序当时一脸懵逼...)下面主要是介绍一些常见的其它算法。
>
> 递归
>
>
>
* **简介**
在平常解决一些编程或者做一些算法题的时候,经常会用到递归。程序调用自身的编程技巧称为递归。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。上面介绍的快速排序和归并排序都用到了递归的思想。
* **经典例子**
斐波那契数列,又称黄金分割数列、因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N\*)。
//斐波那契数列 递归实现
static long funFib(long index) {
if (index == 0) {
return 0;
} else if (index == 1) {
return 1;
} else {
return funFib(index - 1) + funFib(index - 2);
}
}
上面代码是斐波那契数列的递归实现,然而我们不难得到它的时间复杂度是O(2^n),递归有时候可以很方便地解决一些问题,但是它也会带来一些效率上的问题。下面的代码是求斐波那契数列的另一种方式,效率比递归方法的效率高。
static long funFib2(long index) {
long f0 = 0;
long f1 = 1;
long f2 = 1;
if (index == 0) {
return f0;
} else if (index == 1) {
return f1;
} else if (index == 2) {
return f2;
}
for (int i = 3; i <= index; i++) {
f0 = f1;
f1 = f2;
f2 = f0 + f1;
}
return f2;
}
>
> 分治算法
>
>
>
分治算法的思想是将待解决的问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后合并这些子问题的解来建立最终的解。分治算法中关键地一步其实就是递归地求解子问题。关于分治算法的一个典型例子就是上面介绍的归并排序。[查看更多关于分治算法的内容](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
>
> 动态规划
>
>
>
动态规划与分治方法相似,都是通过组合子问题的解来求解待解决的问题。但是,分治算法将问题划分为互不相交的子问题,递归地求解子问题,再将它们的解组合起来,而动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题。动态规划方法通常用来求解最优化问题。[查看更多关于动态规划的内容](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
动态规划典型的一个例子是[最长公共子序列](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)问题。
常见的算法还有很多,比如贪心算法,回溯算法等等,这里都不再详细介绍,想要熟练掌握,还是要靠刷题,刷题,刷题,然后总结。
#### 五、常见算法题
下面是一些常见的算法题汇总。
>
> 不使用临时变量交换两个数
>
>
>
static void funSwapTwo(int a, int b) {
a = a ^ b;
b = b ^ a;
a = a ^ b;
System.out.println(a + " " + b);
}
>
> 判断一个数是否为素数
>
>
>
static boolean funIsPrime(int m) {
boolean flag = true;
if (m == 1) {
flag = false;
} else {
for (int i = 2; i <= Math.sqrt(m); i++) {
if (m % i == 0) {
flag = false;
break;
}
}
}
return flag;
}
>
> 其它算法题
>
>
>
1、[15道使用频率极高的基础算法题](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
2、[二叉树相关算法题](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
### JavaScript 和 ES6
在这个过程你会发现,有很多 JS 知识点你并不能更好的理解为什么这么设计,以及这样设计的好处是什么,这就逼着让你去学习这单个知识点的来龙去脉,去哪学?第一,书籍,我知道你不喜欢看,我最近通过刷大厂面试题整理了一份前端核心知识笔记,比较书籍更精简,一句废话都没有,这份笔记也让我通过跳槽从8k涨成20k。
![JavaScript部分截图](https://img-blog.csdnimg.cn/img_convert/cac778dc45492a41e2f3e7cd6b0134e5.png)
**[如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**
b;
System.out.println(a + " " + b);
}
判断一个数是否为素数
static boolean funIsPrime(int m) {
boolean flag = true;
if (m == 1) {
flag = false;
} else {
for (int i = 2; i <= Math.sqrt(m); i++) {
if (m % i == 0) {
flag = false;
break;
}
}
}
return flag;
}
其它算法题
JavaScript 和 ES6
在这个过程你会发现,有很多 JS 知识点你并不能更好的理解为什么这么设计,以及这样设计的好处是什么,这就逼着让你去学习这单个知识点的来龙去脉,去哪学?第一,书籍,我知道你不喜欢看,我最近通过刷大厂面试题整理了一份前端核心知识笔记,比较书籍更精简,一句废话都没有,这份笔记也让我通过跳槽从8k涨成20k。