算法期末考试大程序详解

算法期末考试程序详解

 马上就期末考试了,自己梳理一下后面大题的思路和代码,理解好各个算法的理念和不同

写这篇文章也是为了以后复习方便


最短路径问题

问题:求两个城市之间最短距离(距离为边的权值之和,下图各边权值为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);
	}

}


猜你喜欢

转载自blog.csdn.net/legendary_idiot/article/details/80727349