贪婪算法
求一个问题的最优解可以考虑使用贪婪算法和动态规划算法。
贪婪算法就是我啥也不管,走一步看一步,只要当前的结果最优我就心满意足了,最后的结果可能是最好的也可能不是。看起来是不是特别的荒唐与滑稽,所以在使用贪婪算法的时候往往需要通过数学方式来证明贪婪选择是正确的。它的优点就是可以以较快的速度求出复杂问题的最优解或最优解的近似解(近似解的结果概率偏大)。
一般背包问题
给定n种物品和一个背包,物品i的重量是wi,其价值是vi,背包的容量为C,问应该如何选择装入背包的物品,使得装入背包中物品的总价值最大。物品可以不装入,可以全部装入,也可以部分装入。
0-1背包问题是不能取物品的一部分,只能取或不取该物品,一般背包问题则不同,一般背包可以取物品的一部分来实现最大价值。
import java.util.Scanner;
public class KnapSack {
static void knapsack(int n, float c, float[] v, float[] w, float[] x) {
Sort(n, v, w);
int i;
for (i = 1; i <= n; i++) {
if (w[i] > c)
break;
x[i] = 1;
c -= w[i];
}
if (i <= n)
x[i] = c / w[i];
}
static void Sort(int n, float[] v, float[] w) {
// 冒泡
for (int i = 0; i < n - 1; i++)
for (int j = i + 1; j < n; j++)
if (v[i] / w[i] < v[j] / w[j]) {
float temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
temp = w[i];
w[i] = w[j];
w[j] = temp;
}
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println(" 输入背包中物品的个数: ");
int n = scan.nextInt();
System.out.println(" 输入背包的容量: ");
float c = scan.nextFloat();
float[] v = new float[n + 1];
float[] w = new float[n + 1];
float[] x = new float[n + 1];
for (int i = 1; i <= n; i++) {
System.out.println(" 输入第 " + i + " 的重量 ");
w[i] = scan.nextFloat();
System.out.println(" 输入第 " + i + " 的价值 ");
v[i] = scan.nextFloat();
}
knapsack(n, c, v, w, x);
System.out.println(" 一般背包问题的解为: ");
for (int i = 1; i <= n; i++)
System.out.print(x[i] + " ");
}
}
动态规划算法
实现动态规划算法一般需要推导出递推式,有着良好的编程基础和数学素养才能写出漂亮的代码。
动态规划的三个特点:1、求一个问题的最优解;2、整体问题的最优解是依赖各个子问题的最优解;3、大问题分解为多个小问题,小问题之间还有相互重叠的更小问题。
它和贪婪算法最大的差别就是,它的各个子问题通常不独立,他也想像贪婪那样只思考当前问题,可是环境它不允许呀,因为当前问题往往还和其他问题有交集。
子问题之间有交集意为着重复,所以对于动态规划的优化也是很值得思考的。
01背包问题
import java.util.Scanner;
public class ZeroOneKnapSack {
int n, c;
private int[] w;
private int[] v;
private int[][] x;
public void ZeroOneKnapsack() {
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= c; j++) {
// 如果容量为j的背包放得下第i个物体
if (j >= w[i]) {
x[i][j] = Math.max(x[i - 1][j - w[i]] + v[i], x[i - 1][j]);
} else {
x[i][j] = x[i - 1][j];
}
}
}
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= c; j++) {
System.out.print(x[i][j] + " ");
}
System.out.println();
}
}
public void printResult(int n, int v) {
boolean[] isAdd = new boolean[n + 1];
for (int i = n; i >= 1; i--) {
if (x[i][v] == x[i - 1][v])
isAdd[i] = false;
else {
isAdd[i] = true;
v -= w[i];
}
}
for (int i = 1; i <= n; i++) {
System.out.print(isAdd[i] + " ");
}
System.out.println();
}
/**
* 输入格式: 5 10
* 2 2 6 5 4
* 6 3 5 4 6
* result:15
* 第一行是物体个数、背包总空间;
* 第二行是每个物体的空间;
* 第三行是每个物体的收益。
*/
public void init() {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
c = sc.nextInt();
w = new int[n + 1];
v = new int[n + 1];
x = new int[n + 1][c + 1];
for (int i = 1; i <= n; i++) {
w[i] = sc.nextInt();
}
for (int i = 1; i <= n; i++) {
v[i] = sc.nextInt();
}
}
public static void main(String[] args) {
ZeroOneKnapSack zoKnapSack = new ZeroOneKnapSack();
zoKnapSack.init();
zoKnapSack.ZeroOneKnapsack();
zoKnapSack.printResult(zoKnapSack.n, zoKnapSack.c);
}
}
分治算法
分而治之,将问题分解为多个子问题,且子问题之间相互独立与原问题性质相同。
快速排序
package sort;
public class QuickSort implements ISort {
@Override
public int[] sort(int[] arr) {
return quickSort(arr, 0, arr.length - 1);
}
private int[] quickSort(int[] arr, int left, int right) {
if (left < right) {
int partitionIndex = partition(arr, left, right);// 选出基准值后,分成两个子序列(不包含以前的基准值),然后各玩各的
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
private int partition(int[] arr, int left, int right) {
// 设定基准值(pivot)
int pivot = left;
int index = pivot + 1;
for (int i = index; i <= right; i++) {
if (arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);// 和比基准值小的最后一位交换位置
return index - 1;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
回溯法
回溯法和分支界限法,可以看作是对空间树的DFS(深度优先遍历-深搜)和BFS(广度优先遍历-宽搜)。
怎么对空间树进行深搜呢,比方说,现在有一个节点,其子节点有许多个,我们选择一个子节点,然后呢继续在当前节点的多个子节点中进行选择,一直向下搜索直到其子节点为空的时候,返回到其父节点。
回溯法就是穷举法+剪枝,在当前节点已经不满足条件的情况下还有必要搜索其子节点吗?
回溯法通常用递归来实现,那就需要注意许多东西了。。。
(回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含(剪枝过程),则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。)
八皇后
任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
#include<iostream>
#include<cstdio>
using namespace std;
#define maxn 10
int n;
int tot=0;
int c[maxn];
void sreach(int row){
if(row==n)tot++;
else {
for(int i=0;i<n;i++){
int ok=1;
c[row]=i;
for(int j=0;j<row;j++){
if(c[row]==c[j]||c[row]+row==c[j]+j||c[row]-row==c[j]-j){
ok=0;
break;
}
}
if(ok)sreach(row+1);
}
}
}
int main(){
cin>>n;
sreach(0);
cout<<tot<<endl;
return 0;
}
分支界限法
怎么对空间树进行宽搜呢,比方说,现在有一个节点,其子节点有许多个,我们选择哪个子节点呢?小孩子才做选择,我都要,一个子节点都不放过。
好,然后怎么实现呢?在当前子节点后,返回父节点再找另外一个子节点吗,感觉有点麻烦哈,那下一层呢?。。。队列这个数据结构不错。
(分支限界法常以广度优先的方式搜索问题的解空间树。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。)
长草
问题描述
小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。
小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。
这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,这四小块空地都将变为有草的小块。
请告诉小明,k 个月后空地上哪些地方有草。
输入格式
输入的第一行包含两个整数 n, m。
接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。
接下来包含一个整数 k。
输出格式
输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。
样例输入
4 5
. g. . .
. . . . .
. . g . .
. . . . .
2
样例输出
gggg.
gggg.
ggggg
.ggg.
import java.io.BufferedInputStream;
import java.util.LinkedList;
import java.util.Scanner;
public class zhangcao {
public static void main(String[] args) {
int[] dx = { 0, -1, 0, 1 };
int[] dy = { 1, 0, -1, 0 };
Scanner scanner = new Scanner(new BufferedInputStream(System.in));
int n = scanner.nextInt();
int m = scanner.nextInt();
scanner.nextLine();
boolean[][] vis = new boolean[1005][1005];
LinkedList<Cao> linkedList = new LinkedList<Cao>();
for (int i = 0; i < n; i++) {
String temp = scanner.nextLine();
for (int j = 0; j < m; j++) {
if (temp.charAt(j) == 'g') {
linkedList.addLast(new Cao(i, j, 0));
vis[i][j] = true;
}
}
}
int k = scanner.nextInt();
while (!linkedList.isEmpty()) {
Cao cao = linkedList.removeFirst();
int month = cao.month;
if (month < k) {
for (int i = 0; i < 4; i++) {
int x = cao.i + dx[i];
int y = cao.j + dy[i];
if (0 <= x && x < n && 0 <= y && y < m && !vis[x][y]) {
linkedList.addLast(new Cao(x, y, month + 1));
vis[x][y] = true;
}
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (vis[i][j]) {
System.out.print("g");
} else {
System.out.print(".");
}
}
System.out.println();
}
}
static class Cao {
private int i;
private int j;
private int month;
public Cao(int i, int j, int month) {
this.i = i;
this.j = j;
this.month = month;
}
}
}