算法是思想的体现形式,常见的算法做一些总结
算法简介
算法—Algorithm
解题方案的准确而完整的描述,是一系列解决问题的清晰指令
特征
有穷性,确切性,输入项,输出项,可行性
算法运算要素
算术运算:加减乘除等运算
逻辑运算:或、且、非等运算
关系运算:大于、小于、等于、不等于等运算
数据传输:输入、输出、赋值等运算
算法优劣评定
时间复杂度,空间复杂度,正确性,可读性,健壮性
LogN
二分法查找最坏的情况:对于N个元素的数组,第一次查找未找到则舍弃 N/2 个元素,剩下 N/2,同理第二次剩 N/4 ···,一直到最后剩 N/2^k >= 1,所以二分法查找的次数 k 满足 N/2^k = 1,于是
2 ^ k = N
k = log2N
所以二分法查找的最坏时间复杂度是O(logN)
NLogN
首先决策树是一颗二叉树,每个节点表示元素之间一组可能的排序
先来说明一些二叉树的性质,令T是深度为d的二叉树,则T最多有2^片树叶
具有L片树叶的二叉树的深度至少是logL
所以,对n个元素排序的决策树必然有n!片树叶(因为n个数有n!种不同的大小关系),所以决策树的深度至少是log(n!),即至少需要log(n!)次比较,而
log(n!) = logn + log(n-1) + log(n-2) +...+ log2 + log1
>= logn + log(n-1) + log(n-2) +...+ log(n/2)
>= (n/2)log(n/2)
>= (n/2)logn - n/2
= O(nlogn)
算法分析方法
递归法:汉诺塔
穷举法:暴力密码破解法
贪心算法:加勒比海盗偷宝藏
分治法:乐毅连下齐72城 二分搜索
动态规划法:导弹拦截
迭代法:超能生的兔子
回溯法:八皇后
排序
由于前面已经对排序进行过总结了,这里不再赘述其思想,直接贴出示例代码
前面使用的是C语言,这里使用Java,原理在前面已经说明,推荐看前面的,包括代码
由于下面的排序涉及到交换的比较多,这里直接先定义一个交换的方法
private void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
交换排序
冒泡排序
public void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length - 1 - i; j++) {
if(array[j] > array[j + 1]) {
swap(array, j, j + 1);
}
}
}
}
快速排序
private void quickSort(int[] array, int left, int right) {
if(left < right) {
int mid = getMid(array, left, right);
quickSort(array, 0, mid -1);
quickSort(array, mid + 1, right);
}
}
private int getMid(int[] array, int left, int right) {
int temp = array[left];
while(left < right) {
while(left < right && array[right] >= temp) {
right--;
}
array[left] = array[right];
while(left < right && array[left] <= temp) {
left++;
}
array[right] = array[left];
}
array[left] = temp;
return left;
}
选择排序
简单选择排序
public void selectSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int key = i;
for (int j = i; j < array.length; j++) {
if(array[key] > array[j]) {
key = j;
}
}
if(key != i) {
swap(array, key, i);
}
}
}
堆排序
public void heapSort(int[] array) {
if(array == null || array.length <= 1) {
return;
}
//构建大堆
buildMaxHeap(array);
for (int i = array.length - 1; i > 0; i--) {
swap(array, 0, i);
adjustHeap(array, i, 0);
}
}
private void buildMaxHeap(int[] array) {
int half = (array.length - 1) / 2;
for (int i = half; i >= 0 ; i--) {
adjustHeap(array,array.length,i);
}
}
private void adjustHeap(int[] array, int length, int i) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if(left < length && array[left] > array[largest]) {
largest = left;
}
if(right < length && array[right] > array[largest]) {
largest = right;
}
if(i != largest) {
swap(array,i,largest);
adjustHeap(array, length, largest);
}
}
插入排序
直接插入排序
public void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int temp = array[i];
int j;
for (j = i - 1; j >= 0; j--) {
if(array[j] > temp) {
array[j + 1] = array[j];
}else {
break;
}
}
array[j + 1] = temp;
}
}
二分插入排序
public void binaryInsertSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int temp = array[i];
int left = 0;
int right = i - 1;
int mid = 0;
while(left <= right) {
mid = (left + right) / 2;
if(temp < array[mid]) {
right = mid - 1;
}else {
left = mid + 1;
}
}
for (int j = i - 1; j >= left; j--) {
array[j + 1] = array[j];
}
if(left != i) {
array[left] = temp;
}
}
}
希尔排序
public void heerSort(int[] array) {
int k = -1;
int temp = -1;
int gap = array.length;
while(gap > 1) {
gap = gap / 3 + 1; //通常取3 O(n 1.3)
for (int i = gap; i < array.length; i += gap)
{
k = i;
temp = array[k];
for (int j = i - gap; (j >= 0) && (array[j] > temp); j -= gap)
{
array[j + gap] = array[j];
k = j;
}
array[k] = temp;
}
}
}
归并排序
public void mergeSort(int[] array) {
mergeSort(array,0,array.length - 1);
}
private void mergeSort(int[] array, int left, int right) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(array, left, mid);
mergeSort(array, mid + 1, right);
mergeSort(array, left, mid, right);
}
}
private void mergeSort(int[] array, int left, int mid, int right) {
int[] temp = new int[array.length];
int rightStart = mid + 1;
int leftStart = left;
int tempIndex = left;
while(leftStart <= mid && rightStart <= right) {
if(array[leftStart] <= array[rightStart]) {
temp[tempIndex++] = array[leftStart++];
}else {
temp[tempIndex++] = array[rightStart++];
}
}
while(leftStart <= mid) {
temp[tempIndex++] = array[leftStart++];
}
while(rightStart <= right) {
temp[tempIndex++] = array[rightStart++];
}
tempIndex = left;
while(tempIndex <= right) {
array[tempIndex] = temp[tempIndex];
tempIndex++;
}
}
基数排序
基数排序原理:由于数字都是由0~9组成,所以在排序的时候可以按照0~9排序
这里只考虑了正数,这是体现其思想实现,负数的话处理一下就可以了
public void basicSort(int[] array) {
int maxNum = 0; //记录最大值
int count = 0; //记录最大值位数
for (int i = 0; i < array.length; i++) {
if(maxNum < array[i]) {
maxNum = array[i];
}
}
while(maxNum > 0) {
maxNum /= 10;
count++;
}
List<ArrayList> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ArrayList arrayList = new ArrayList<>();
list.add(arrayList);
}
for (int i = 0; i < count; i++) {
for (int j = 0; j < array.length; j++) {
int x = array[j] % (int)Math.pow(10, i + 1) / (int)Math.pow(10, i);
ArrayList aList = list.get(x);
aList.add(array[j]);
list.set(x, aList);
}
int index = 0;
for (int j = 0; j < 10; j++) {
while(list.get(j).size() > 0) {
ArrayList<Integer> aList = list.get(j);
array[index] = aList.get(0);
aList.remove(0);
index++;
}
}
}
}
排序的java源代码:链接:https://pan.baidu.com/s/1mmiJV6k4vIAS53u2eNf63g 密码:q6rq
递归
递归在程序中很常见,那么接下来就用一些经典的递归来探索递归的运用吧
二分法查找
public int binarySearch(int[] array, int elem, int low, int high) {
if(low > high) {
return -1;
}
int mid = (low + high) / 2;
if(array[mid] == elem) {
return mid;
}
if(array[mid] < elem) {
return binarySearch(array, elem, mid + 1, high);
}
if(array[mid] > elem) {
return binarySearch(array, elem, low, mid - 1);
}
return -1;
}
汉诺塔
有三根柱子,在一根柱子上从下往上按照大小顺序摞着圆盘,把圆盘按大小顺序重新摆放在另一根柱子上,并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘
实现思路:
- 将1针上n-1个盘借助3针移动到2针上
- 将1针上剩下的一个盘移动到3针上
- 将n-1个盘从2针借助1针移动到3针上
那么这里用C语言来描述应该是这个样子
void hanoi(int n, int one, int two, int three)
{
if (n == 1)
printf("%d->%d\n", one, three);
else
{
hanoi(n - 1, one, three, two);
printf("%d->%d\n", one, three);
hanoi(n - 1, two, one, three);
}
}
用Java也是一样
public void hanoi(int n, int one, int two, int three) {
if (n == 1) {
System.out.println(one + "->" + three);
} else {
hanoi(n - 1, one, three, two);
System.out.println(one + "->" + three);
hanoi(n - 1, two, one, three);
}
}
这里可以得出一个结论,n个盘子需要移动zn-1次
欧几里得扩展算法
定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。最大公约数(Greatest Common Divisor)缩写为GCD。gcd(a,b) = gcd(b,a mod b)(不妨设a>b 且r=a mod b ,r不为0)
证明:
第一步:令c = gcd(a,b)
,则设a = mc,b = nc
第二步:可知r = a - kb = mc - knc = (m - kn)c
第三步:根据第二步结果可知c也是r的因数
第四步:可以断定m - kn
与n
互素【否则,可设m - kn = xd, n = yd, (d > 1)
,则m = kn + xd = kyd + xd = (ky + x)d
,则a = mc = (ky + x)dc
,b = nc = ycd
,故a与b最大公约数≥cd,而非c,与前面结论矛盾】
从而可知gcd(b,r) = c
,继而gcd(a,b) = gcd(b,r)
,得证
下面来实现
public int gcd(int m, int n) {
if(n == 0) {
return m;
}else {
return gcd(n, m % n);
}
}
阶乘求解算法
public int factorial(int num) {
if(num == 1) {
return 1;
}else {
return num * factorial(num - 1);
}
}
穷举
泊松分酒
在多个不同容积的杯子里,通过反复倒酒,得到规定的酒量
例如:
有3个容器,容量分别为12升,8升,5升。其中12升中装满,另外两个空着。要求你只用3个容器操作,最后使得某个容器中正好有6升
这里制定一个规则,倒酒顺序:12 -> 8 -> 5 -> 12,防止错乱
private int b1 = 12;
private int b2 = 8;
private int b3 = 5;
private int target = 6;
public void backBottle(int cup1, int cup2, int cup3) {
System.out.println("cup1:" + cup1 + ",cup2:" + cup2 + ",cup3:" + cup3);
if(cup1 == target || cup2 == target || cup3 == target) {
System.out.println("success");
return;
}
if(cup2 != 0 && cup3 != b3) {
if(cup2 + cup3 <= b3) {
backBottle(cup1, 0, cup2 + cup3);
}else {
backBottle(cup1, cup2 - (b3 - cup3), b3);
}
}else if(cup3 == b3){
if(cup1 + cup3 <= b1) {
backBottle(cup1 + cup3, cup2, 0);
}else {
backBottle(b1, cup2, cup3 - (b1 - cup1));
}
}else if(cup2 == 0){
if(cup1 >= b2) {
backBottle(cup1 - b2, b2, cup3);
}else {
backBottle(0, cup1, cup3);
}
}
}
贪心
背包算法
public void packageGreedy(int capacity,int weights[],int[] values){
int num = weights.length;
double[] priceRatio = new double[num];
int[] index = new int[num];
for(int i = 0;i < num; i++){
priceRatio[i] = (double)values[i] / weights[i];
index[i] = i;
}
double temp = 0;//对性价比进行排序
for(int i = 0; i < num - 1; i++){
for(int j = i + 1; j < num; j++){
if(priceRatio[i] < priceRatio[j]){
temp = priceRatio[i];
priceRatio[i] = priceRatio[j];
priceRatio[j] = temp;
int x = index[i];
index[i] = index[j];
index[j] = x;
}
}
}
//排序好的重量和价值分别存到数组
int[] w1 = new int[num];
int[] v1 = new int[num];
for(int i = 0;i < num; i++){
w1[i] = weights[index[i]];
v1[i] = values[index[i]];
}
int[] x = new int[num];
int maxValue = 0;
for(int i = 0;i<num;i++){
if(w1[i] < capacity){
//还可以装得下
x[i] = 1;//表示该物品被装了
maxValue += v1[i];
System.out.println("装入物品:" + w1[i]);
capacity = capacity - w1[i];
}
}
System.out.println("放下物品数量:" + Arrays.toString(x));
System.out.println("最大价值:" + maxValue);
}
测试
public static void main(String[] args) {
int maxWeight = 150;
int[] weights = {10, 20, 30, 40, 50};
int[] values = {30, 50, 60, 60, 60};
GreedyBackPack backPack = new GreedyBackPack();
backPack.packageGreedy(maxWeight, weights, values);
}
分治
分治法的设计思想是:
- 分–将问题分解为规模更小的子问题
- 治–将这些规模更小的子问题逐个击破
- 合–将已解决的子问题合并,最终得出问题的解
循环赛日程表
n个队伍,n-1天完成比赛
一个先自顶向下,再自底向上的过程
public void scheduleTable(int[][] table, int n) {
if(n == 1) {
table[0][0] = 1;
}else {
//填充左上区域矩阵
int m = n / 2;
scheduleTable(table, m);
//填充左下区域矩阵
for (int i = 0; i < m; i++) {
for (int j = m; j < n; j++) {
table[i][j] = table[i][j - m] + m;
}
}
//填充右上区域矩阵
for (int i = m; i < n; i++) {
for (int j = 0; j < m; j++) {
table[i][j] = table[i - m][j] + m;
}
}
//填充右下区域矩阵
for (int i = m; i < n; i++) {
for (int j = m; j < n; j++) {
table[i][j] = table[i - m][j - m];
}
}
}
}
输出
[1, 2, 3, 4, 5, 6, 7, 8]
[2, 1, 4, 3, 6, 5, 8, 7]
[3, 4, 1, 2, 7, 8, 5, 6]
[4, 3, 2, 1, 8, 7, 6, 5]
[5, 6, 7, 8, 1, 2, 3, 4]
[6, 5, 8, 7, 2, 1, 4, 3]
[7, 8, 5, 6, 3, 4, 1, 2]
[8, 7, 6, 5, 4, 3, 2, 1]
棋盘问题
在一个2^k×2^k(k≥0)个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为特殊方格
private int[][] board; // 棋盘
private int sRow; // 特殊点的行下标
private int sCol; // 特殊点的列下标
private int size;
private int type = 0;
public ChessBoard(int[][] borad, int sRow, int sCol) {
this.board = borad;
this.sRow = sRow;
this.sCol = sCol;
size = borad.length;
System.out.println(size);
}
public void chessBoard() {
chessBoard(sRow, sCol, 0, 0, size);
}
private void chessBoard(int sRow, int sCol, int leftRow, int leftCol, int size) {
if (size == 1) {
return;
}
int subSize = size / 2;
type = type % 4 + 1;
int n = type;
// 特殊点在左上角
if (sRow < leftRow + subSize && sCol < leftCol + subSize) {
chessBoard(sRow, sCol, leftRow, leftCol, subSize);
} else {
board[leftRow + subSize - 1][leftCol + subSize - 1] = n;
chessBoard(leftRow + subSize - 1, leftCol + subSize - 1, leftRow, leftCol, subSize);
}
// 特殊点在右上角
if (sRow < leftRow + subSize && sCol >= leftCol + subSize) {
chessBoard(sRow, sCol, leftRow, leftCol + subSize, subSize);
} else {
board[leftRow + subSize - 1][leftCol + subSize] = n;
chessBoard(leftRow + subSize - 1, leftCol + subSize, leftRow, leftCol + subSize, subSize);
}
// 特殊点在左下角
if (sRow >= leftRow + subSize && sCol < leftCol + subSize) {
chessBoard(sRow, sCol, leftRow + subSize, leftCol, subSize);
} else {
board[leftRow + subSize][leftCol + subSize - 1] = n;
chessBoard(leftRow + subSize, leftCol + subSize - 1, leftRow + subSize, leftCol, subSize);
}
// 特殊点在右下角
if (sRow >= leftRow + subSize && sCol >= leftCol + subSize) {
chessBoard(sRow, sCol, leftRow + subSize, leftCol + subSize, subSize);
} else {
board[leftRow + subSize][leftCol + subSize] = n;
chessBoard(leftRow + subSize, leftCol + subSize, leftRow + subSize, leftCol + subSize, subSize);
}
}
动态规划
最长公共子序列
public int findLCS(String A, String B) {
int n = A.length();
int m = B.length();
char[] a = A.toCharArray();
char[] b = B.toCharArray();
int[][] dp = new int[n][m];
//第一列判断
for (int i = 0; i < n; i++) {
if(a[i] == b[0]) {
dp[i][0] = 1;
for (int j = i + 1; j < n; j++) {
dp[j][0] = 1;
}
break;
}
}
//第一行判断
for (int i = 0; i < m; i++) {
if(a[0] == b[i]) {
dp[0][i] = 1;
for (int j = i + 1; j < m; j++) {
dp[0][j] = 1;
}
break;
}
}
//遍历内部
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
if(a[i] == b[j]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n -1][m - 1];
}
回溯
八皇后问题
public class Queen {
public static int num = 0; // 累计方案
public static final int MAX_QUEEN = 8;
public static int[] cols = new int[MAX_QUEEN]; // 表示8列棋子摆放的位置
public void getCount(int n) {
boolean[] rows = new boolean[MAX_QUEEN]; // 记录列方格是否可以放
for (int m = 0; m < n; m++) {
rows[cols[m]] = true;
int d = n - m;
if (cols[m] - d >= 0) {
rows[cols[m] - d] = true;
}
if (cols[m] + d <= MAX_QUEEN - 1) {
rows[cols[m] + d] = true;
}
}
for (int i = 0; i < MAX_QUEEN; i++) {
if (rows[i]) {
continue;
}
cols[n] = i;
if (n < MAX_QUEEN - 1) {
getCount(n + 1);
} else {
num++;
printQueen();
}
}
}
private void printQueen() {
System.out.println("第" + num + "种方案");
for (int i = 0; i < MAX_QUEEN; i++) {
for (int j = 0; j < MAX_QUEEN; j++) {
if (i == cols[j]) {
System.out.print("0 ");
} else {
System.out.print("+ ");
}
}
System.out.println();
}
}
}
约瑟夫问题
public class Joseph {
private static int COUNT = 20;
private static int NUM = 5;
public void killNode() {
Node header = new Node(1);
Node xNode = header;
for (int i = 2; i <= COUNT; i++) {
xNode = (xNode.next = new Node(i));
}
xNode.next = header;
while(xNode != xNode.next) {
for (int i = 1; i < NUM; i++) {
xNode = xNode.next;
}
System.out.println("剔除节点:" + xNode.next.val);
xNode.next = xNode.next.next;
}
System.out.println("剩下的节点为:" + xNode.val);
}
class Node{
int val;
Node next;
public Node(int val) {
this.val = val;
}
}
}