二分(折半)算法 [O(log n)]
特点:1.查找的元素列表必须有序
2.所取中间数向下取整
3.当n是2的倍数时,所得结果+1
//非递归实现
public class Solution{
public static void main(String[] args) {
int[] my_list = {1, 2, 3, 4, 5, 6, 7};
int temp = binary_search(my_list, 4);
System.out.println(temp + 1);
}
//折半查找
private static int binary_search(int[] list, int item) {
int low = 0;
int high = list.length - 1;
int sum=0;
while (low <= high) {
sum++;//记录需要运行几次
int mid = (low + high) / 2;
if (list[mid] == item) {
System.out.println(sum);
return mid;//索引
}
if (list[mid] > item) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
}
//递归实现二分法
public static int sort(int []array,int a,int lo,int hi){
if(lo<=hi){
int mid=(lo+hi)/2;
if(a==array[mid]){
return mid+1;
}
else if(a>array[mid]){
return sort(array,a,mid+1,hi);
}else{
return sort(array,a,lo,mid-1);
}
}
return -1;
}
大O表示法
数组
1.数组内的数在内存中相连
2.可以顺序访问也可以随机访问
链表
1.可以在内存中不连续
2.只能顺序访问
*需要同时读取所有元素时,链表效率更高,需要随机读取时,数组效率更高
*数组读取快,插入慢
*链表读取慢,插入快
选择排序 [O(n^2)]
如遍历所有元素,选出最大的,再遍历,选出剩下元素中最大的…
遍历一次的复杂度是O[n],遍历n次,所以复杂度O[n^2]
递归
1.自己调用自己
2.每个递归函数有两个部分
*递归条件:函数自己调用自己
*基线条件:函数不在调用自己,避免形成无限循环
3.跟循环比起来只是解决方案更清晰,并没有性能上的提升
栈
1.先进后出
2.栈在递归中扮演重要角色
3.每次递归中的变量都储存在调用栈中(自动完成)
缺点:储存详尽的信息(调用栈可能很长)可能占用大量的内存
解决办法:1.重新编码,转而使用循环
2.使用尾递归(高级递归主题)
快速排序 [O(nlog n)]
在平均情况(最佳情况)下,快速排序运行时间为O(nlog n),在最糟糕情况下,运行时间为O(n^2)不管怎么划分数组,每次都会涉及O(n)个元素,最佳情况是调用栈高度为O(log n),最糟糕情况是调用栈高度为O(n)
import java.util.Arrays;
public class Solution{
public static void main(String[] args) {
int[] a = {5, 3, 4, 6, 7, 8, 2, 33};
System.out.println(sum(a.length, a));
System.out.println(findmax(a.length, a));
System.out.println(Arrays.toString(a));
el(a);
System.out.println(Arrays.toString(a));
}
//集合所有元素之和(递归版)
public static int sum(int n, int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (n == 1) {
return arr[n - 1];
} else {
return sum(n - 1, arr) + arr[n - 1];
}
}
//找集合中的最大数
public static int findmax(int n, int[] arr) {
int max = arr[0];
for (int i = 0; i < n; i++) {
if (max <= arr[i]) {
max = arr[i];
}
}
return max;
}
//快速排序
public static void el(int[] arr) {
int n = 0;
int m = arr.length - 1;
long start = System.nanoTime();
//普通版
//sortTest(arr,n,m);
//递归版
quickSort1(arr, n, m);
long end = System.nanoTime();
//程序运行时间测算(单位纳秒)
System.out.println(end-start);
}
public static void sortTest(int[] arr, int n, int m) {
if (n < m) {
//分区界限索引
int index = quicksort(arr, n, m);
//左分区快速排序
quicksort(arr, n, m - 1);
//右分区快速排序
quicksort(arr, n + 1, m);
}
}
public static int quicksort(int[] arr, int n, int m) {
int i = n;
int j = m;
int x = arr[i];
while (i < j) {
while (arr[j] >= x && i < j) {
j--;
}
if (i < j) {
arr[i] = arr[j];
i++;
}
while (arr[i] <= x && i < j) {
i++;
}
if (i < j) {
arr[j] = arr[i];
j--;
}
}
arr[i] = x;
return i;
}
//快速排序(递归)
public static void quickSort1(int[] n, int left, int right) {
int pivot;
if (left < right) {
//pivot作为枢轴,较之小的元素在左,较之大的元素在右
pivot = partition(n, left, right);
//对左右数组递归调用快速排序,直到顺序完全正确
quickSort1(n, left, pivot - 1);
quickSort1(n, pivot + 1, right);
}
}
public static int partition(int[] n, int left, int right) {
int pivotkey = n[left];
//枢轴选定后永远不变,最终在中间,前小后大
while (left < right) {
while (left < right && n[right] >= pivotkey) --right;
//将比枢轴小的元素移到低端,此时right位相当于空,等待低位比pivotkey大的数补上
n[left] = n[right];
while (left < right && n[left] <= pivotkey) ++left;
//将比枢轴大的元素移到高端,此时left位相当于空,等待高位比pivotkey小的数补上
n[right] = n[left];
}
//当left == right,完成一趟快速排序,此时left位相当于空,等待pivotkey补上
n[left] = pivotkey;
return left;
}
}
散列表(Hashtable)[O(1)]
内部机制:实现、冲突和散列函数
散列表:结合使用散列函数和数组
缓存的数据存储在散列表中(如web服务器上)
冲突很糟糕,应该使用可以最大限度减少冲突的散列函数
一旦填装因子超过0.7,就该调整散列表的长度
避免冲突需要:1.较低的填装因子
2.良好的散列函数
广度优先算法 [O(V+E)]
广度优先算法可以用于求非加权图最短路径
搜索列表必须是队列
每检查一个节点,就将其设置为1,避免再次检查,否可能导致无限循环
无向图和有向图
检测完一度关系后再将二度关系入队,以此类推
V指节点数,E为边数
狄克斯特拉算法
1.狄克斯特拉算法用于在加权图中查找最短路径(不能用于含有负权边)
2.需要三张散列表来记录数据
*GRAPH用于记录每个节点所有的邻居以及他们间的权重
*COSTS用于储存每个节点的开销(从起点到该节点的权重)
*PARENTS用于储存每个节点的父节点
COSTS和PARENTS不断更新
贝尔曼-福德算法
该算法可以弥补狄克斯特拉算法的缺陷,可以在含有负权边的加权图中查找最短路径
贪心算法/近似算法 [O(n^2)]
1.每步都选取最优做法,因为每步都选择局部最优解,最终会得到的就是全局最优解
2.广度优先算法和狄克斯特拉算法都是贪心算法
3.NP完全问题[O(n!)]的简单定义是难解著称的,面对NP完全问题的最佳做法就是近似算法
4.如何识别NP完全算法:
*元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会变得非常慢。
*涉及“所有组合”的问题通常是NP完全问题。
*不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题。
*如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题。
*如果问题涉及集合(如广播台集合)且难以解决,它可能就是NP完全问题。
*如果问题可转换为集合覆盖问题O(2^n)或旅行商问题,那它肯定是NP完全问题。
5.贪心算法易于实现、运行速度快,是不错的近似算法
K最近邻算法(KNN)
重点在于特征抽取,可以用于推荐最相似的类型
如比较橙子和柚子,可以抽离个头大小和颜色数据化,假设有一个水果需要判断是橙子还是柚子,就可以判断距离这个水果离哪边距离更近的多,距离的测算公式是毕达哥拉斯公式
*毕达哥拉斯公式:根号下(a1-a2)2+(b1-b2)2
更多的数据也可以使用这个公式