欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

算法期末考试大程序详解

程序员文章站 2022-04-20 21:46:53
...

算法期末考试程序详解

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

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


最短路径问题

 

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

}


相关标签: 算法 期末