算法期末考试程序详解
马上就期末考试了,自己梳理一下后面大题的思路和代码,理解好各个算法的理念和不同
写这篇文章也是为了以后复习方便
最短路径问题
问题:求两个城市之间最短距离(距离为边的权值之和,下图各边权值为1)
题目思路:
【广度优先算法】
老师给的题是没有权值的无向图,下面的程序是有权值的无向图,老师给的可以转换成每条边的权值为1。
运用广度优先搜索的方法,以写入邻接矩阵的数据为准,比如最开始的一行是v0,最后一行是v(n-1),求出这两个结点之间的最短路径。
需要设置以下变量:
boolean[] isLabel布尔值一维数组,用于储存数组对应结点是否被选中,选中的标号,如{1,0,0,1}就是第一个第四个结点被选中。
int[] indexs整数一维数组,存储已经标号的下标集合
int[] distance 用选中的那一行作为初始化的距离数组
int index 储存当前结点标号
intpresentShortest 当前最短距离
int top 栈的顶点
算法描述:
为选中的那个起始行初始化
首结点标记,并入栈
如果栈顶没有越界(用top和一行的长度比较),进行大循环
//找到栈顶结点到相邻结点中最短路径的结点
遍历一行的长度
如果这个结点1.有边2.没有被标号3.不是当前结点4.是这一趟循环的最小值
取距离数组的值赋给当前最小值
下标改为当前下标
如果当前结点是最后的结点,结束循环
对新找到的结点标记,入栈
//计算起点到该节点最短路径的长度(一种就是等于对应的距离数组本身,一种是累加上一个距离)
如果两个点没有相连 或者 两个点的路径之和大于最短路径
更新为当前对应距离数组值
否则
累加两点之间距离
//为距离数组更新,更新方式为每一个结点均为已走过的路径长度,没有相邻就写-1
(未更新前为上一次的距离数组,只有以下两种情况需要改变距离数组的内容)
如果1.以前不能到达2.现在更新完结点之后可以到达
距离更新为当前储存的最短长度加上当前路径权值
或者如果1.以前可以到达2.更新之后比以前的路径更短
距离更新同上
返回距离数组里起始和终止的差值
源代码
package 最短路径问题; public class ShortestPath { //每次确定一个边,不仅仅是从当前出发,也可以从原来的边出发,目的就是确定下一个边就是最短的路径,直到确定到最后一个点 //每个被选中的结点都会被标号 static int dijkstra(int start, int end, int w[][]) { boolean[] isLabel = new boolean[w[0].length];//选取一行的长度 //是否标号 int[] indexs = new int[w[0].length];//已经标号点的下标集合,以标号先后顺序储存,是用数组实现的栈 int top = -1;//栈的顶点 int[] distance = w[start].clone();//直接复制了w的start行的内容到了一个新的内存空间,代表起点到对应下标代表结点走过的最短距离 int index = start;//从起始点开始 int presentShortest = 0;//当前最短距离 indexs[++top] = index;//已经标号的顶点存在下标集里面 isLabel[index] = true;//顺便标记为已经标号 while(top < w[0].length) { //元素还在栈之内 //标号v0,w[0][0]找到和v0最近的点 int min = Integer.MAX_VALUE;//定义范围里面最大的数 for(int i = 0; i < distance.length; i++) { //遍历一行 if(!isLabel[i] && distance[i] != -1 && i != index && distance[i] < min) { //这个节点没有被标号,有边,当前点,当前最小 min = distance[i];//每次取较小者,全部遍历完之后就是这一行最小的 index = i;//把下标改为当前下标 } } if(index == end) {//如果当前点就是最后节点,结束程序 break; } isLabel[index] = true;//对新找到的结点进行标号 indexs[++top] = index;//把已经标号的结点存到下标集里面 if(w[indexs[top-1]][index] == -1 || presentShortest + w[indexs[top-1]][index] > distance[index]) { //如果两个点没有直接相连,两个点的路径大于最短路径,比如说三角形的关系,一条边比两条边的和小,就直接赋值距离,否则累加 //而且只需要比较上一个点即可,过三个点一定会有直连的边,不会出现落下边的情况 presentShortest = distance[index]; } else { presentShortest += w[indexs[top-1]][index]; } for(int i = 0; i < distance.length; i++) { // 如果vi到那个点有边,则v0到后面点的距离加 if (distance[i] == -1 && w[index][i] != -1) { // 如果以前不可达,则现在可达了 distance[i] = presentShortest + w[index][i]; } else if (w[index][i] != -1 && presentShortest + w[index][i] < distance[i]) { // 如果以前可达,但现在的路径比以前更短,则更换成更短的路径 distance[i] = presentShortest + w[index][i]; } } } //如果全部点都遍历完,则distance中存储的是开始点到各个点的最短路径 for(int i = 0; i < w[0].length; i++) { System.out.print(isLabel[i] + " "); } System.out.print("\n"); return distance[end] - distance[start]; } public static void main(String[] args) { // TODO Auto-generated method stub int[][] jz = { //测试数据1 { 0, 1, 1, 1,-1, 1,-1,-1}, { 1, 0,-1,-1,-1, 1,-1,-1}, { 1,-1, 0, 1, 1,-1,-1,-1}, { 1,-1, 1, 0,-1,-1, 1,-1}, {-1,-1, 1,-1, 0,-1, 1, 1}, { 1, 1,-1,-1,-1, 0,-1, 1}, {-1,-1,-1, 1, 1,-1, 0, 1}, {-1,-1,-1,-1, 1, 1, 1, 0} }; //该路径的邻接矩阵 int[][] W = { //测试数据2 { 0, 1, 4,-1,-1,-1 }, { 1, 0, 2, 7, 5,-1 }, { 4, 2, 0,-1, 1,-1 }, {-1, 7,-1, 0, 3, 2 }, {-1, 5, 1, 3, 0, 6 }, {-1,-1,-1, 2, 6, 0 } }; int[][] P = { { 0,10,-1, 8,19,25,11,10}, {10, 0, 7, 9,-1,-1,-1,-1}, {-1, 7, 0,12,-1,-1,-1,-1}, { 8, 9,12, 0,20,-1,-1,-1}, {19,-1,-1,20, 0, 5,-1,-1}, {25,-1,-1,-1, 5, 0,22,-1}, {11,-1,-1,-1,-1,22, 0, 4}, {10,-1,-1,-1,-1,-1, 4, 0} }; int[][] W2 = { //测试数据2 { 0, 1, 2,-1,-1,-1 }, { 1, 0, 2, 2, 5,-1 }, { 2, 2, 0,-1, 3,-1 }, {-1, 2,-1, 0, 1, 2 }, {-1, 5, 3, 1, 0, 6 }, {-1,-1,-1, 2, 6, 0 } }; // System.out.println(dijkstra(0, 7, jz)); //传入的end数据为一行的长度减一,也就是最后一个数据的下标 System.out.println(dijkstra(0, 5, W2)); //其实就是想求哪两个点就输入哪两个点 // System.out.println(dijkstra(2, 7, P)); } }
或者不考虑 路径权值,可以简化成如下代码:
#include<stdio.h> int jz[8][8] = { //测试数据1 { 0, 1, 1, 1, 0, 1, 0, 0}, { 1, 0, 0, 0, 0, 1, 0, 0}, { 1, 0, 0, 1, 1, 0, 0, 0}, { 1, 0, 1, 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0, 0, 1, 1}, { 1, 1, 0, 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 1, 0, 0, 1}, { 0, 0, 0, 0, 1, 1, 1, 0} }; struct { int city; int pre; }sq[100]; int qh, qe, visited[100]; int main(){ void out(); int n = 8; qh = 0; qe = 1; sq[0].city = 1; sq[0].pre = 0; visited[1] = 1; while(qh != qe){ qh += 1; for(int i = 0; i < n; i++){ if(jz[sq[qh].city][i] == 1 && visited[i] == 0){ //有边且未访问 qe += 1; sq[qe].city = i; sq[qe].pre = qh; visited[i] = 1; if(sq[qe].city == 7){ out(); return 0; } } } } printf("No solution!"); return 0; } void out(){ printf("%d",sq[qe].city); while(sq[qe].pre != 0){ qe = sq[qe].pre; printf("--%d",sq[qe].city); } }
子集和问题
问题:给一组数和一个数,解出这组数哪些的和等于这个数,并列出所有情况
题目思路:
【回溯法】(深度优先的思想)
包含或不包含为0/1,保存在向量数组中,最后输出时用向量数组乘源数据集合中对应每一个数即可
递归从循环为向量数组赋值0,1开始逐级向下延伸,延伸过程累加,累加之前判断剪枝。
当一趟触底的时候判断是否触底,然后跳出当前函数进行回溯,直到把所有0,1的情况都遍历完或者剪枝剪段。
算法描述:
(一共写了四个方法,向量赋值方法,前面累加,后面累加,输出)
向量赋值方法
0,1循环:
向量数组赋值
如果累加和等于要求解
调用输出方法
或者如果1.没有触底2.这一项及其以前和小于等于解3.这一项及其以后和大于等于解(剪枝)
递归调用向量赋值方法,其中参数层级加一
前面各项累加方法
0~k循环
向量数组乘源数据数组,累加
返回累加值
后面各项累加方法
K~n-1循环
累加源数据数组即可
返回累加值
源代码:
package 子集和问题; public class NewSum_son { final static int n = 12; static int[] x = new int[n]; static int[] w = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16};//储存初始的数据 final static int M = 8; static void judge(int level) { //level是当前层级,一个整数 for(int i = 1; i >= 0; i--) { //这个循环起始是从根节点开始的,下面的x是为当前结点增加属性(0或1) x[level] = i; //向量数组赋值,直接为当前结点赋值 if(sum(x, w, level) == M) { //判断当前的结点及其 以前的所有和是否应该输出 show(); } //判断下一个结点是否符合限制条件,是否应该剪枝 else if(level < n-1 && (sum(x, w, level) + i*w[level + 1] + tailsum(w, level + 1)) >= M && (sum(x, w, level) + i*w[level + 1]) <= M) { judge(level + 1); } } } static int sum(int x[], int w[], int k) { //这个k肯定不是最后的n,而是当前的数值 int sum = 0; for(int i = 0; i <= k; i++) { sum = sum + x[i] * w[i]; } return sum; } static int tailsum(int w[], int k) { int sum = 0; for(int i = k; i < n; i++) { sum = sum + w[i]; } return sum; } static void show() { System.out.print("{ "); for(int i = 0; i < n; i++) { if(x[i] != 0) { System.out.print(w[i] + " "); //输出一组解 } } System.out.print("}\n"); } public static void main(String[] args) { // TODO Auto-generated method stub judge(0); } }
N皇后问题
问题:所有皇后不重列,不重左右对角线,储存到矩阵之中
题目思路:
【回溯法】
一维数组存储结果,矩阵状态用下标和对应值两部分表现
在一个数组长度的循环中递归,每一次输出一个皇后的位置,假如位置不合法,循环继续执行,直到都合法为止
判断是否在对角线或者竖直线用j是否相等和横纵坐标差值比来判断
代码描述:
(三个方法 确定皇后位置方法,判断位置是否合法的方法,输出方法)
确定皇后位置方法
1~max循环(max是一共有几个位置)
为当前皇后赋值位置
如果位置合法
如果是最后一行
输出
否则
调用下一个皇后的位置方法
判断位置合法化方法
1~n循环,n是第几个皇后
如果皇后储存数组中1.当前值和原来某值相等2.当前值和原来值的差值之比为±1
返回 false
返回 true
源代码:
package n皇后问题; public class NQueen { final static int max = 4; static int sum = 0; static int[] queen = new int[max]; static void NQUEENS(int n) { for(int i = 0; i < max; i++) { queen[n] = i; //n是第几行,也就是角标,i是这一行所在的位置,也就是求出的结果输出的内容,这也恰好用了两个数据,等效于二维数组 if(Place(n)) { //这个判断,如果是直线或者斜率为±1,就不会进入判断,直接进入这一行的下一个位置 if(n == max-1) { //如果是第四行,则输出,如果不是,则进入下一行 show(); } else { NQUEENS(n + 1); } } } } static boolean Place(int n) { //传入的也是角标,也就是第几行,i变化是因为i在第n行之上,所以要i<n for(int i = 0; i < n; i++) { //比较的是下角标,也就是元素所在行 if(queen[i] == queen[n] || Math.abs(queen[i] - queen[n]) == Math.abs(n - i)) { //判断是否是竖直的,是否是斜率为±1的 return false; } } return true;//返回值是真,说明这个位置合法 } static void show() { System.out.print("( "); for(int i = 0; i < max; i++) { System.out.print(queen[i]+1 + " ");//输出数组内容 } System.out.print(")\n"); sum ++; } public static void main(String[] args) { NQUEENS(0); System.out.print("\n"); System.out.println("总共的解法有" + sum +"种"); } }
七巧板涂色问题
问题:给定七块板,四种颜色,要求每相邻两块板之间颜色不能重复
题目思路:
【回溯法】
将每一块板看成是一个节点,整体看成是一个无向图
建立邻接矩阵保存节点信息
尝试涂色方法在四种颜色的循环中递归调用,每次递归都要比较一下是否有相邻颜色相同且相邻的板
没有的话就可以继续调用继续涂下一个颜色
代码描述:
(三个方法,尝试涂色,比较颜色,打印)
尝试涂色方法:
传入结点下标为参数
如果结点下标大于结点最大下标
输出
否则
1~4中颜色循环
为颜色保存数组上色
如果比较颜色方法返回1
递归下一次,下标加一
比较颜色方法
传入结点下标为参数
0~n-1循环
如果1.有边2.颜色相等
返回 1
返回 0
源代码:
package 七巧板涂色; import java.util.Scanner; public class Seven_point { final static int n = 4; static int[][] data = new int[n][n]; static int[] color = new int[n]; static int count = 0; private static Scanner in; public static void main(String[] args) { in = new Scanner(System.in); for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { data[i][j] = in.nextInt(); } } for(int i = 0; i < n; i++) { color[i] = 0; } Try(0); System.out.println("总共有" + count +"种方法"); } static void Try(int s) { if(s > n-1) { Output(); } else { for(int i = 1; i <= 4; i++) { color[s] = i;//上色 if(samecolor(s) == 0) { Try(s + 1); } } } } static int samecolor(int s) { int flag = 0; for(int i = 0; i <= s-1; i++) { if(data[i][s] == 1 && color[i] == color[s]) { flag = 1; break; } } return flag; } static void Output() { for(int i = 0; i < n; i++) { System.out.print(color[i] + " "); } count ++; System.out.print("\n"); } }
日程安排问题
问题:给一些活动的起始时间和终止时间,要求在规定时间内排尽可能多的活动
题目思路:
【贪婪算法】
提前把活动按照结束时间拍好顺序,否则时间复杂度就是排序的时间复杂度
设置一个量储存当前已经确定进行的活动的结束时间,和循环中的活动起始时间比较,符合条件则修改前面那个量,每次修改计数,最后输出有几个活动。
代码描述:
计数器初始化为1,中间值初始化为1
向量数组的首项改为true
1~n-1循环
如果当前开始时间大于等于储存的结束时间
向量数组置真
当前活动更新
计数器累加
否则
向量数组置假
源代码:
package 活动安排问题; public class ActivityManage { //活动起始时间和结束时间都储存在了s[]和f[]里面,已经按照非递减排好序了 final static int n = 11;//总共11个活动里面选取最多的活动举办 static int[] s = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12}; static int[] f = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; static boolean[] a = new boolean[n]; //储存每个活动状态 static int activity() { int j = 1, count = 1; //第一个活动肯定进行,所以计数器初始为1,j储存当前活动的序号,因为起始时间和终止时间是分开的,所以需要两个指针指向 a[1] = true; System.out.println("Activity 1"); for(int i = 1; i < n; i++) { if(f[j] <= s[i]) { a[i] = true; j = i; //当前活动更新 count ++; //计数器更新 System.out.println("Activity " + i); } else { a[i] = false; } } return count; } public static void main(String[] args) { // TODO Auto-generated method stub int num = activity(); System.out.println("一共可以安排" + num + "个活动"); } }
可拆被包问题
问题:给一些商品,商品拥有体积和价值(商品可以拆成小部分价值随之减少),给一个体积固定的背包,问怎么装使得背包内商品价值最大
题目思路:
背包内物品已经按照价值比质量排好序了
先尽可能把背包填满,最后把剩下的那个分解开,塞进背包里面即可
代码描述:
定义需求量数组
如果当前物品体积小于剩余体积,进入循环
更新需求量
增加总价值
减少背包剩余质量
计数器增加
计算最后一个物品能装进去多少
增加总价值
返回 总价值
源代码:
package 可拆背包问题; public class Knapsack { //n个物品已经按照 价值/质量 即 vi/wi 排好序了(按照比值降序排列) final static int n = 3;//一共n种物品 static float[] w = {10, 30, 20}; static float[] v = {50, 120, 60}; static float[] x = new float[n]; //每个物品需求量,有可能是分数 static float c = 50;//背包一共可以承受的最大质量 static float packsack(float c, float w[], float v[], float x[]) { //返回的是总价值,毕竟是要求计算最大化价值的算法 float total = 0; //总价值初始化 int i = 0;//初始化背包选择指针 while(w[i] < c) { //这个c是一直在改变的 x[i] = 1; //确认需求量 total = total + v[i]; //每次增加其价值 c = c - w[i]; //减少背包剩余质量 i ++; }//循环结束的时候剩下的空间不足以满足一整个物品 x[i] = c/w[i]; //计算出最后一件物品可以装进去多少比例 total = total + x[i] * v[i]; //最后增加其价值 return total; } public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("最大可以装载的价值为" + packsack(c, w, v, x)); } }
旅行商问题
源代码:
package 旅行商问题; public class TSP { //蛮力法解决,核心算法为全排列,时间复杂度是O(n!) final static int n = 4;//这个图一共四个结点 static int[][] dist = {{0,2,6,5},{2,0,4,4},{6,4,0,2},{5,4,2,0}};//储存路径的邻接矩阵,自己和自己为0 static int[] A = {0,1,2,3};//等待求组合的数组 static int[] path = new int[n + 1];//走过的路径,最后一个储存了0,是用于输出起点的,不能改变 static int start = 0;//挑选的起点,初始化为0,也就是第一个起点 static int step = 0;//步数 static int max;//全排列的数目 static int count = 0; static int mindist = 999999;//最小路径和比较的中间值 static int factorial(int n) { //求取n的阶乘 int s = 1; for(int i = 1; i <= n; i++) { s = s*i; } return s; } static void arrange(int step, int start) { //生成全排列 if(step == n) { int len = 0; count ++; //每次调用这个方法都会使count增加,直到所有情况都被遍历完输出最优解 len = print(len);//计算路径长度,顺便输出 if(len < mindist) { //更新最短路径 mindist = len; for(int i = 0; i < n; i++) { path[i] = A[i]; if(i == n-1) path[i+1] = A[0]; //给path的第五个元素附上起点 } } } //以下是递归部分 if(count == max) { //在上一个if里面,通过循环得到从i开始的循环回路的所有情况,在这些情况里面得到了最佳方案 show(); } else { //第j个数与后面的两两交换得到新的排列 //两两交换的简易方法不适合用Java写,因此提供三行代码 for(int j = start; j < n; j ++) { int t = A[step]; A[step] = A[j]; A[j] = t; arrange(step + 1, start + 1);//递归调用,因此最后得到的时间复杂度特别大(np问题) t = A[j]; A[j] = A[step]; A[step] = t; } } } static int print(int len) { for(int i = 0; i < n; i++) { //计算当前序列的路径长度 if(i < n-1) { len = len + dist[A[i]][A[i+1]]; } else { len = len + dist[A[i]][A[0]]; } System.out.print(A[i] + " ");//每次生成之后输出一次情况 } System.out.print("\n"); return len; } static void show() { System.out.println("最短路径的长度为:" + mindist); System.out.print("旅行路线为:"); for(int i = 0; i <= n; i++) { //最后会把顶点和路径的权值都输出 char[] city = {'A', 'B', 'C', 'D'}; if(i != 0 && path[i] == 0) { // System.out.print(city[path[i]]);//既然是终点,那肯定是最后输出 ,既然如此path[i]=0肯定是最后一个了呀???!? } else { System.out.print(city[path[i]] + "————" + dist[path[i]][path[i+1]] + "————"); } } /**测试代码**/ // for(int i = 0; i <= n; i++) { // System.out.print(path[i] + " "); // } } public static void main(String[] args) { // TODO Auto-generated method stub max = factorial(n);//初始化max arrange(step, start); } }