算法期末考试大程序详解
算法期末考试程序详解
马上就期末考试了,自己梳理一下后面大题的思路和代码,理解好各个算法的理念和不同
写这篇文章也是为了以后复习方便
最短路径问题
问题:求两个城市之间最短距离(距离为边的权值之和,下图各边权值为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);
}
}
上一篇: SQL Server期末复习要点(二)
下一篇: oracle分组group by