比较排序是小白最容易想到的排序方法,从第一个元素开始,和后面所有元素依次进行比较,比第一个元素小,就将这两个元素交换,最后比完第一轮,第一个元素肯定是最小的了,然后从第二个元素开始,和后面所有元素比较,一轮比完,第二个元素是第二小的,同理,比完n-1(n为数组元素个数)轮,数组全部变得有序。比较排序是稳定的,也就是遇到值一样大的情况下不会发生交换,保持原来位置,其时间复杂度为O(n^2),代码如下:
public class Test比较 {
public static void main(String[] args) {
int [] arr= {1,3,2,5,4};
//从小到大排序
//先确定第一项是最小的
//i-1回和可以排除i个数的循序,比如排两个数,只需要1次
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(arr[i]>arr[j]) {
// 确保第i个元素是最小的
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
冒泡排序是很经典的一个排序算法,顾名思义,冒泡排序,就像水里冒水泡一样,从最底下慢慢升到最上面。冒泡排序是相邻元素进行比较,这样使最大值慢慢换到最后面去,所以,冒泡排序也得进行n-1趟,是稳定的,其时间复杂度为O(N^2),代码如下:
public class Test冒泡排序 {
public static void main(String[] args) {
int [] arr= {1,3,2,5,4};
//从小到大排序
//相邻两个元素比较
for (int i = 0; i < arr.length-1; i++) {
//排除最大的,从未确定的元素中进行排序
for (int j = 0; j < arr.length-1-i; j++) {
//大的元素向后走
if(arr[j]>arr[j+1]) {
flag = 1;//要进行排序
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
如果它已经有序,就没必要进行排序了
public class Test冒泡排序 {
public static void main(String[] args) {
int [] arr= {1,3,2,5,4};
//从小到大排序
//相邻两个元素比较
for (int i = 0; i < arr.length-1; i++) {
int flag = 0;//标记是否排过序
//排除最大的,从未确定的元素中进行排序
for (int j = 0; j < arr.length-1-i; j++) {
//大的元素向后走
if(arr[j]>arr[j+1]) {
flag = 1;//要进行排序
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
if (flag == 0) {
break;
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
快速排序简称快排,是最常用的排序方法。理解快速排序你首先得了解分治思想,即将一个大的复杂的问题分成几个小的容易解决的问题,当你把所有小问题解决后,这个大问题也就被解决了。那么说回快排,快排的思想就是:在待排序数组找到一个基准值,将待排序数组分成两段,第一段的值全小于这个基准值,第二段的值全大于这个基准值。然后重复这个操作将这直到整个数组都变得有序。即 “整体有序,局部无序”。其中,基准值得选取关系到快排的效率,快排的平均复杂度为 O(n*logn) 当数组完全逆序时,快排的复杂度会退化成 O(n^2)。关于基准值的选取(快排的优化)这里不做展开,一般我们都选取区间最最左端的元素作为基准值。
那么我们的问题转化成,如何将区间分段?这也是快排的核心: 交叉扫描,将元素交换位置
举个例子:待排序数组为:8 6 9 7 3 0 2 5 1 4
此时,基准值flag=8,区间最左端下标left=0,最右端下标right=9;从右端right-9开始扫描(因为我们基准值选取的是最左边的元素值),4<8,代表4应该在8的左边,所以我们将其交换位置,数组变成:4 6 9 7 3 0 2 5 1 8,left=0,right=9然后从左边left=0开始扫,,4<=8,代表4应该在8的左边,位置正确,那么接着扫描,6<=8,代表6应该在8的左边,位置正确,那么接着扫描,9=>8,9应该在8的右边,j进行交换,数组变成:4 6 8 7 3 0 2 5 1 9,left=2,right=9,然后重复此操作(没交换,就继续向前(后)扫描,交换了就从另一边开始扫描),直到left>=right,就代表数组已经被分好段。分好段的数组为:4 6 1 7 3 0 2 5 8 9,分成了4 6 1 7 3 0 2 5和9这两个待排序区间,重复上面的操作,直至所有区间都排好序(每个元素的位置都正确),即排序完成。需要注意的是: 快排是不稳定的。根据上面的思路,我们可以写出下面的代码:
算法描述:
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
public class Tast快速排序 {
private static int partition(int[] arr, int low, int high) {
//指定左指针i和右指针j
int i = low;
int j= high;
//将第一个数作为基准值。挖坑
int x = arr[low];
//使用循环实现分区操作
while(i<j){//5 8
//1.从右向左移动j,找到第一个小于基准值的值 arr[j]
while(arr[j]>=x && i<j){
j--;
}
//2.将右侧找到小于基准数的值加入到左边的(坑)位置, 左指针向中间移动一个位置i++
//i
if(i<j){
arr[i] = arr[j];
System.out.println(Arrays.toString(arr));
i++;
}
//3.从左向右移动i,找到第一个大于等于基准值的值 arr[i]
while(arr[i]<x && i<j){
i++;
}
//4.将左侧找到的大于等于基准值的值加入到右边的坑中,右指针向中间移动一个位置 j--
if(i<j){
arr[j] = arr[i];
System.out.println(Arrays.toString(arr));
j--;
}
}
//使用基准值填坑,这就是基准值的最终位置
arr[i] = x;//arr[j] = y;
//返回基准值的位置索引
return i; //return j;
}
private static void quickSort(int[] arr, int low, int high) {//???递归何时结束
if(low < high){
//分区操作,将一个数组分成两个分区,返回分区界限索引
int index = partition(arr,low,high);
//对左分区进行快排
quickSort(arr,low,index-1);
//对右分区进行快排
quickSort(arr,index+1,high);
}
}
public static void quickSort(int[] arr) {
int low = 0;
int high = arr.length-1;
quickSort(arr,low,high);
}
public static void main(String[] args) {
//给出无序数组
int arr[] = {72,6,57,88,60,42,83,73,48,85};
//输出无序数组
System.out.println(Arrays.toString(arr));
//快速排序
quickSort(arr);
//partition(arr,0,arr.length-1);
//输出有序数组
System.out.println(Arrays.toString(arr));
}
}
#5.
https://www.cnblogs.com/kyoner/p/11080078.html
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
//取出中间的坐标
int mid = (right + left) / 2;
//如果正好是目标值,就返回坐标
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
比如说给你有序数组 nums = [1,2,2,2,3],target = 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的。
这样的需求很常见。你也许会说,找到一个 target 索引,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的时间复杂度了。
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
return left;
}
int right_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1; // 注意
https://www.cnblogs.com/xiaozhang2014/p/7783795.html
定义
贪心算法,又称贪婪算法(Greedy Algorithm),是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优解出发来考虑,它所做出的仅是在某种意义上的局部最优解。
贪心算法的基本思路:
具体案例
假设1元、2元、5元、10元、20元、50元、100元的纸币,张数不限制,现在要用来支付K元,至少要多少张纸币?
public static void greedyGiveMoney(int money) {
System.out.println("需要找零: " + money);
// 数组中放着可选的钱数
int[] moneyLevel = {1, 5, 10, 20, 50, 100};
for (int i = moneyLevel.length - 1; i >= 0; i--) {
//算出需要的张数
int num = money/ moneyLevel[i];
//找到剩余的钱
int mod = money % moneyLevel[i];
//剩余的钱重新赋值
money = mod;
//如果可以取出来
if (num > 0) {
System.out.println("需要" + num + "张" + moneyLevel[i] + "块的");
}
}
}
比如,纸币1元,5元,6元,要支付10元的话,按照上面的算法,至少需要1张6元的,4张1元的,而实际上最优的应该是2张5元的。
有一个背包,背包容量是W=150。有7个物品,每个物品有各自的重量和价值,每个物品有一件。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品 A B C D E F G
重量 35 30 60 50 40 10 25
价值 10 40 30 50 35 40 30
我们很容易想到使用贪心算法来解决这个问题,那我们考虑一下贪心策略:
(1)每次挑选价值最大的物品放入背包,得到的结果是否最优?
(2)每次挑选所占重量最小的物品放入背包,得到的结果是否最优?
(3)每次选取单位重量价值最大的物品,得到的结果是否最优?
值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。但可惜的是,它需要证明后才能真正运用到题目的算法中。
而上面的3中贪心策略,都是无法成立的,即无法被证明的:
https://www.cnblogs.com/cainiao-chuanqi/p/11320972.html#autoid-0-0-0
func( mode){
if(endCondition){ //递归出口
end;
}else{
func(mode_small) //调用本身,递归
}
}
public static void main(String[] args) {
File file = new File("f:\\123");
listAllFile(file);
}
public static void listAllFile(File file) {
// listFiles()方法是返回某个目录下所有文件
// 和目录的绝对路径,返回的是File数组
File[] strs = file.listFiles();
for (int i = 0; i < strs.length; i++) {
// 判断strs[i]是不是目录
if (strs[i].isDirectory()) {
listAllFile(strs[i]);//递归调用自己
System.out.println("目录="+strs[i].getName());
} else {
System.out.println("文件名="+strs[i].getName());
}
}
}
}
public static void recursion_display(int n) {
int temp=n;//保证前后打印的值一样
System.out.println("递进:" + temp);
//if语句里全是递进,在不断的压栈
if (n > 0) {
recursion_display(--n);
}
//到了这里,不在进栈,于是开始出栈
System.out.println("回归:" + temp);
}
public static long factorial(int n) throws Exception {
if (n < 0)
throw new Exception("参数不能为负!");
else if (n == 1 || n == 0)
return 1;
else
return n * factorial(n - 1);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1HpZ0a9f-1622898270671)(蓝桥杯比赛.assets/776887-20160505194608060-952305852.png)]
斐波那契数列的递推公式:Fib(n)=Fib(n-1)+Fib(n-2),指的是如下所示的数列:
1、1、2、3、5、8、13、21…
public static int fib(int n) throws Exception {
if (n < 0)
throw new Exception("参数不能为负!");
else if (n == 0 || n == 1)
return n;
else
return fib(n - 1) + fib(n - 2);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFJOfEU3-1622898270679)(蓝桥杯比赛.assets/1340783103_6523.png)]
归并排序也是递归的典型应用,其思想:将序列分为若干有序序列(开始为单个记录),两个相邻有序的序列合并成一个有序的序列,以此类推,直到整个序列有序。
https://blog.csdn.net/zw6161080123/article/details/80639932
https://www.jianshu.com/p/e8da6139f84b
https://blog.csdn.net/hrn1216/article/details/51534607
public static int solutionFibonacci(int n){
if(n==0){
return 0;
}else if(n == 1){
return 1;
}else{
//上面的和递归一样
int result[] = new int[n+1];
result[0] = 0;
result[1] = 1;
//从小到大逐渐求得最终的值
//递归是把问题从大化小,直到小问题解决了,然后再往上返回
for(int i=2;i<=n;i++){
result[i] = result[i-1] + result[i-2];
}
return result[n];
}
}
arr[] = {3,1,4,1,5,9,2,6,5}的最长递增子序列长度为4。即为:1,4,5,9
public static int MaxChildArrayOrder(int a[]) {
int n = a.length;
//表示数组的增长序列
int temp[] = new int[n];//temp[i]代表0...i上最长递增子序列
for(int i=0;i<n;i++){
temp[i] = 1;//初始值都为1
}
for(int i=1;i<n;i++){
//i从第二个数开始
for(int j=0;j<i;j++){
//首先,先找到0--i-1前所有小于i的数的集合ss,
//然后,ss的最后一项值的子序列+1就是第i项的子序列
if(a[i]>a[j]&&temp[j]+1>temp[i]){
//如果有a[i]比它前面所有的数都大,
//则temp[i]为它前面的比它小的数的那一个temp+1取得的最大值
temp[i] = temp[j]+1;
}
}
}
int max = temp[0];
//从temp数组里取出最大的值
for(int i=1;i<n;i++){
if(temp[i]>max){
max = temp[i];
}
}
return max;
}
如arr[] = {6,-1,3,-4,-6,9,2,-2,5}的最大连续子序列和为14。即为:9,2,-2,5
创建一个数组a,长度为原数组长度,不同位置数字a[i]代表0…i上最大连续子序列和,a[0]=arr[0]设置一个最大值max,初始值为数组中的第一个数字。当进来一个新的数字arr[i+1]时,判断到他前面数字子序列和a[i]+arr[i+1]跟arr[i+1]哪个大,前者大就保留前者,后者大就说明前面连续数字加起来都不如后者一个新进来的数字大,前面数字就可以舍弃,从arr[i+1]开始,每次比较完都跟max比较一下,最后的max就是最大值。
public static int MaxContinueArraySum(int a[]) {
int n = a.length;
int max = a[0];
int sum = a[0];
for(int i=1;i<n;i++){
//判断当前项和以往的项的和谁大,前者大就保留前者,
// 后者大就说明前面连续数字加起来都不如后者一个新进来的数字大,前面数字就可以舍弃
sum = Math.max(sum+a[i], a[i]);
//最后判断
if(sum>=max){
max = sum;
}
}
return max;
}
如图所示,给定一个正整数构成的三角形,如下所示:
在下面的数字三角形中寻找一条从顶部到底边的路径,
使得路径上所经过的数字之和最大。
路径上的每一步都只能往左下或者右下走。
只需要求出这个最大和即可,不必给出路径。
三角形的行数大于1小于等于100,整数为0~99
public static int minNumberInRotateArray(int yuan[][]) {
int max = 0;
int dp[][] = new int[yuan.length][yuan.length];
dp[0][0] = yuan[0][0];
//以原数组为基准
for(int i=1;i<yuan.length;i++){
for(int j=0;j<=i;j++){
if(j==0){
//如果是第一列,那么原数组的这一项加上动态数组的上一项
dp[i][j] = dp[i-1][j] + yuan[i][j];
}else{
//如果不是第一列,那么原数组这一项加上动态数组数组的上一项与左上项的的最大值
//动态数组的每一项就是所走所走路径之和,可以和元数组的下一项相加
dp[i][j] = Math.max(dp[i-1][j-1], dp[i-1][j]) + yuan[i][j];
}
max = Math.max(dp[i][j], max);
}
}
return max;
}
优化:动态规划中每一个需要创建一个二维数组的解法,都可以换成只创建一个一维数组的滚动数组解法,依据的规则是一般二维数组中存放的是所有的结果,但是一般我们需要的结果实在二维数组的最后一行的某个值,前面几行的值都是为了得到最后一行的值而需要的,所以可以开始就创建跟二维数组最后一行一样大的一维数组,每次存放某一行的值,下一次根据这一行的值算出下一行的值,在存入这个数组,也就是把这个数组滚动了,最后数组存储的结果就是原二维数组中最后一行的值。
public static int minNumberInRotateArray2(int yuna[][]) {
// 从一个二维数组变成一位数组,极大的节省空间
int[] temp = new int[yuna.length];
//初始值
temp[0] = yuna[0][0];
for(int i=1;i<yuna.length;i++){
//这里也是不同的
for(int j=i;j>=0;j--){
// 这里是对二维数组的简化,需要找到思路,然后转化成代码
if(j==i){
//在一行的末尾时
//元数组的这一项加上动态数组的i-1项
temp[i]=temp[i-1]+yuna[i][j];
}else if(j==0){
//当第一列时,动态数组第一项加上原数组的该项
temp[0]+=yuna[i][0];
}else{
//处于中间项时,动态数组的j项与动态数组的j-1项的最大值加上原数组的该项
temp[j]=Math.max(temp[j], temp[j-1])+yuna[i][j];
}
}
}
int max = temp[0];
//从temp数组里取出最大的值
for(int i=1;i<temp.length;i++){
if(temp[i]>max){
max = temp[i];
}
}
return max;
}
具体方法也是建立一个二维数组,最下面一行数据添到二维数组最后一行,从下往上填数字
public static int minNumberInRotateArray3(int yuan[][]) {
int max = 0;
int dp[][] = new int[yuan.length][yuan.length];
//这里是起始点,也就是不一样的地方,这里是末赋值
dp[yuan.length-1] = yuan[yuan.length-1];
//以原数组为基准
//从倒数第二列出发
for(int i=yuan.length-2;i>=0;i--){
for(int j=0;j<=i;j++){
//动态数组的这个下标的值等于
//原数组这个下标的值加上动态数组的下一项值与左下项值的最大值
dp[i][j] = yuan[i][j]+Math.max(dp[i+1][j], dp[i+1][j+1]);
}
}
return dp[0][0];
}
比如字符串1:BDCABA;字符串2:ABCBDAB,则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA
具体思想:设 X=(x1,x2,…xn)和 Y={y1,y2,…ym} 是两个序列,将 X 和 Y 的最长公共子序列记为LCS(X,Y),如果 xn=ym,即X的最后一个元素与Y的最后一个元素相同,这说明该元素一定位于公共子序列中。因此,现在只需要找:LCS(Xn-1,Ym-1)就好,LCS(X,Y)=LCS(Xn-1,Ym-1)+1;如果xn != ym,这下要麻烦一点,因为它产生了两个子问题:LCS(Xn-1,Ym) 和 LCS(Xn,Ym-1)。
动态规划解法:先创建一个解空间即数组,因为给定的是两个字符串即两个一维数组存储的数据,所以要创建一个二维数组,设字符串X有n个值,字符串Y有m个值,需要创建一个m+1*n+1的二维数组,二维数组每个位置(i,j)代表当长度为i的X子串与长度为j的Y的子串他们的最长公共子串,之所以要多创建一个是为了将边界值填入进去,边界值就是第一行跟第一列,指X长度为0或者Y长度为0时,自然需要填0,其他位置填数字时,当这两个位置数字相同,dp[i][j] = dp[i-1][j-1]+1;当这两个位置数字不相同时,dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j])。最后二维数组最右下角的值就是最大子串。
public static int MaxTwoArraySameOrderMethod(String str1,String str2) {
int m = str1.length();
int n = str2.length();
/*
* 定义一个二维数组保存公共子序列长度
* dp[i][j]表示字符串1从头开始长度是i,字符串2从头开始长度是j,这两个字符串的最长公共子序列的长度
* 设置数组行列比他们长度大一,往二维数组中填写数字时,
* 每个位置的数字跟他上方或者左方或者左上方数字有关系,
* 这样处理边界数字时不用处理这种情况,方便接下来的循环
*/
int dp[][] = new int[m+1][n+1];
//动态数组的每个值代表对应下标的最大子序列长度
//取字符串第i个的字母依次和字符串2比较
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
//相等时
// 动态数组的左上角值+1赋给动态数组的这个坐标
/*
* 如果当c[i][j]时,字符串1从头开始长度是i,字符串2从头开始长度是j时他们最后一个字符相同
* 就同时把他们向前移动一位,找c[i-1][j-1]时长度最大的再加一
* 表现在二维数组中就是c[i][j]左上方的点
*/
if(str1.charAt(i-1) == str2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+1;
/*
* 如果当c[i][j]时,他们最后一个字符不相同
* 要将str1往前移动一位的c[i-1][j]的lcs长度,或者将str2往前移动一位的c[i][j-1]的lcs长度
* 哪个长,将它赋给c[i][j]
* 表现在二维数组中就是c[i][j]上方的点或者左方的点
*/
}else{
//不相等时
//动态数组的左面与上面的最大值赋值在这个坐标上
dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
}
}
}
return dp[m][n];
}
import java.util.Scanner;
public class 动态规划 {
/*
规律:
假如S1的最后一个元素 与 S2的最后一个元素相等,
那么S1和S2的LCS就等于 {S1减去最后一个元素} 与 {S2减去最后一个元素} 的 LCS
再加上 S1和S2相等的最后一个元素。
假如S1的最后一个元素 与 S2的最后一个元素不等,
那么S1和S2的LCS就等于 : {S1减去最后一个元素} 与 S2 的LCS,
{S2减去最后一个元素} 与 S1 的LCS 中的最大的那个序列。
算法描述:
两个字符串:s1,s2
依次取出s2的每个元素依次和s1比较,
如果元素相同,就是s2这个元素左上角元素+1,
如果元素不相同,就是s2左面和上面的元素的最大值
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 把字符串变为数组
String str1=scanner.nextLine();
String str2=scanner.nextLine();
int len1=str1.length();
int len2=str2.length();
int [][]dp=new int[len1+1][len2+1];
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
// 这里是从左到右,从上到下一行行的比较
//相等时
// 动态数组的左上角值+1赋给动态数组的这个坐标
if(str1.charAt(i-1) == str2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+1;
/*
* 如果当c[i][j]时,他们最后一个字符不相同
* 要将str1往前移动一位的c[i-1][j]的lcs长度,he将str2往前移动一位的c[i][j-1]的lcs长度
* 哪个长,将它赋给c[i][j]
* 表现在二维数组中就是c[i][j]上方的点或者左方的点
*/
}else{
//不相等时
//动态数组的左面与上面的最大值赋值在这个坐标上
dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
}
}
}
System.out.println(dp[len1][len2]);
}
}
在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数),求背包能够容纳的最大价值。
https://blog.csdn.net/seagal890/article/details/79826353
给定一个M*N的矩阵(二维数组),分别用0和1表示通路和障碍物。即 0 表示 通路;1 表示 障碍物。从矩阵的左上角开始,每次只能向右,下,左,上移动位置,不能斜着走。请给出从入口到出口的路线。
public class Testdfs {
/**
* DFS算法解决走迷宫问题
* 0: 表示通路
* 1: 表示死路
*
*/
static String path = "";
static String shortestPath = "";
public static void dfsMaze(int x, int y, int[][] maze) {
//x,y代表要去的点,maze就代表这个迷宫
/*
* 获得矩阵的大小
* */
int m=maze.length;
int n=maze[0].length;
//设置结束条件,边界越线
//如果要去的点坐标越界,或者 maze[x][y]==1 表示遇到障碍,
if (x < 0 || y < 0 ||x >= m || y >= n || maze[x][y] ==1)
return;
// 判断是否抵达出口
if (x == m - 1 && y == n - 1) {
//路径等于以前的路径+这点的坐标
path = path + "(" + x + "," + y + ")";
//如果没有最短路径,就直接把当前路径赋给最短路径
//若果有最短的路径,就比较当前路径和最短的路路径谁短
if (shortestPath.length() == 0 || path.length() > shortestPath.length())
shortestPath = path;
System.out.println("找到路线:" + path);
return;
}
//没有到达出口的情况下,把路径记录下来
String temp = path;
path = path + "(" + x + "," + y + ")" + "-"; // 记录路线
maze[x][y] = 1; // 将走过的路标记标记为障碍物,这样它就不能再走了
// 向四个方向搜索
dfsMaze(x + 1, y, maze); //向右搜索
dfsMaze(x, y + 1, maze); //向下搜索
dfsMaze(x - 1, y, maze); //向左搜索
dfsMaze(x, y - 1, maze); //向上搜索
// 将路线和标记恢复成上一次的状态
maze[x][y] = 0;
//清除
path = temp;
}
/*
我们一一个三乘三的二维数组举例说明
开始节点(0,0):
1.往右走(0,1):
1.往右走(0,2):障碍物,回退
----------------------
2.往下走:(1,1):
------------------------------
1.往右走:(1,2):
1.往右走(1,3):越界,回退
2.往下走:(2,2),找到位置,记录路径,回退
3.往左走:(1,1):已走过,回退
4.往上走:(0,2):障碍物,回退
一轮递归结束,路径变为父节点路径
------------------------------
2.往下走(2,1):
1.往右走(2,2):找到位置,记录路径,比较最短路径,回退
2.往下走(3,1):越界,回退
3.往左走(2,0):障碍物,回退
4.往上走(1,1):走过,回退
一轮递归结束,路径变为父节点路径
---------------------------------
3.往左走(1,0):障碍物,回退
---------------------------------
4.往上走(0,1):走过,回退
一轮递归结束,路径变为父节点路径
----------------------------
3.往左走(0,0):走过,回退
----------------------------
4.往上走(-1,1):越界,回退
一轮递归结束,路径变为父节点路径
--------------------------------------------
2.往下走(1,0):障碍物,回退
------------------------------------------
3.往左走(0,-1):越界,回退
-------------------------------------------
4.往上走(-1,0):越界,回退
*/
public static void main(String[] args) {
// 初始化一个迷宫地图
// 0: 表示通路
// 1:表示死路
int[][] maze = {
{0, 0, 1},
{1, 0, 0},
{1, 0, 0},
};
/*
* 从矩阵的左上角位置开始搜索
* */
dfsMaze(0, 0, maze);
if (shortestPath.length() != 0)
System.out.println("最短路线为:" + shortestPath);
else
System.out.println("没有找到路线!");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0SYWRFq-1622898270686)(蓝桥杯比赛.assets/1.png)]
由图可知它相当于后续遍历(左右根),它会把所有节点的值给遍历完,然后返回结果
当每次递归时,以它父节点s的视角进行判断,如果这次递归可行,拿着它就变成了父节点,否则就会舍弃它,从它父节点s的角度,重新选择一个子节点进行递归,当节点s的所有子节点全部舍弃后,它自己也会被舍弃,如此往复。
假设给定3个数:1,2,3,求出其所有的排列组合情况。
例如:
1,1,1
1,1,2
1,1,3
1,2,1
1,2,2
1,2,3
……
3,3,3
static int[] num = new int[3];
public static void dfsExample(int index){
// 边界条件,递归出口
if (index == 3){
for (int i = 0; i < 3; i++){
System.out.print(num[i]+" ");
}
System.out.println();
//走不下去了就 return了
return;
}
for (int i = 1; i <= 3; i++){
num[index] = i;
// index+1 枚举下一种情况,把num[0] = 1,然后dfsExample(1)
dfsExample(index+1);
}
}
public static void main(String[] args) {
//参数的含义是从目标数组中依次取出第几个元素。
//index代表数组元素的下标
//递归入口
dfsExample(0);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgFkHWb9-1622898270690)(蓝桥杯比赛.assets/2.jpg)]
https://blog.csdn.net/qq_37482202/article/details/89513877
第一类问题:从节点A出发,有前往节点B的路径吗?(在你的人际关系网中,有芒果销售商吗?)
第二类问题:从节点A出发,前往节点B的哪条路径最短?(哪个芒果销售商与你的关系最近?)
刚才你看到了如何回答第一类问题,下面来尝试回答第二类问题——谁是关系最近的芒果销售商。例如,朋友是一度关系,朋友的朋友是二度关系。
我们要从“你”出发找到“ANUJ”,关系表示为下图,使用广度优先搜索算法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YggBkE7o-1622898270695)(蓝桥杯比赛.assets/20190425131729132.png)]
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
//代表着某个人的结点
class Node{
//这个人的id
public String id;
//这个人的上层朋友,即父节点
public Node parent;
public Node(String id,Node parent) {
this.id = id;
this.parent = parent;
}
}
public class Testbfs {
public static void main(String[] args) {
// 用hashmap表示图,表示要走的方向
HashMap<String,String[]> hashMap=new HashMap<>();
//键表示人,而值表示关系
hashMap.put("YOU",new String[]{"CLAIRE","ALICE","BOB"});
hashMap.put("CLAIRE",new String[]{"YOU","JONNY","THON"});
hashMap.put("JONNY",new String[]{"CLAIRE"});
hashMap.put("THOH",new String[]{"CLAIRE"});
hashMap.put("ALICE",new String[]{"YOU","PEGGY"});
hashMap.put("BOB",new String[]{"YOU","PEGGY","ANUJ"});
hashMap.put("PEGGY",new String[]{"BOB","ALICE"});
hashMap.put("ANUJ",new String[]{"BOB"});
//开始寻找节点
Node target = findTarget("YOU","ANUJ",hashMap);
//打印出最短路径的各个节点信息
printSearPath(target);
}
/**
* 打印出到达节点target所经过的各个节点信息
* @param target
*/
static void printSearPath(Node target) {
//当所找到的结点不为空时
if (target != null) {
System.out.print("找到了目标节点:" + target.id + "\n");
//新建一个集合
List<Node> searchPath = new ArrayList<Node>();
//把目标结点添加到集合中
searchPath.add(target);
Node node = target.parent;
//不断的目标的父节点往里添加
while(node!=null) {
searchPath.add(node);
node = node.parent;
}
String path = "";
//对这个集合进行倒数打印
for(int i=searchPath.size()-1;i>=0;i--) {
path += searchPath.get(i).id;
if(i!=0) {
path += "-->";
}
}
System.out.print("步数最短:"+path);
} else {
System.out.print("未找到了目标节点");
}
}
static Node findTarget(String startId,String targetId,HashMap<String,String[]> map) {
//建立一个集合,判断是否应经检查过某个人
List<String> hasSearchList = new ArrayList<String>();
//建立一个队列
LinkedBlockingQueue<Node> queue=new LinkedBlockingQueue<>();
//往队列中添加开始节点,它是没有父结点的
queue.offer(new Node(startId,null));
//当队列不为空时
while(!queue.isEmpty()) {
//弹出队列元素
Node node = queue.poll();
//如果这个人已经检查过了,就换个人去查找
if(hasSearchList.contains(node.id)) {
continue;
}
System.out.print("判断节点:" + node.id +"\n");
//如果这个人就是想寻找的人,结束查找
if (targetId.equals(node.id)) {
return node;
}
//这个人不是想找的人,于是就标记他已经检查过
hasSearchList.add(node.id);
//从图中找到这个人,然后把他的关系按顺序加入到队列中
if (map.get(node.id) != null && map.get(node.id).length > 0) {
for (String childId : map.get(node.id)) {
//新加入的结点都是当前节点的子节点
queue.offer(new Node(childId,node));
}
}
}
//如果没找到,就返回空·
return null;
}
}
https://www.bilibili.com/video/BV1c7411d7NC?from=search&seid=13382806908817995434
import java.util.Arrays;
//找起始点到其他顶点的最短距离
public class 最短路径test {
// private static final int MAX = 0;
public static void main(String[] args) {
int Max = 2021;
//这里放着各个顶点
char[] vertexts = {'1','2','3','4','5'};
//这里的行代表各个顶点,列代表当前顶点到其他顶点的距离(权值)
int[][] matrix = {
{0,3,2000,7,Max},
{3,0,4,2,Max},
{Max,4,0,5,4},
{7,2,5,0,6},
{Max,Max,4,6,0}
};
//开始寻找
Dijkstra(vertexts,matrix,0);
}
/**
* 功能:查找最短路径
* @param vertexts 顶点集合
* @param matrix 边距离集合
* @param start 原点的起始位置
*/
private static void Dijkstra(char[] vertexts,int[][] matrix,int start) {
//顶点个数
int n = matrix.length;
//保存起始点(原点)到各个顶点的最短距离
int[] dis = new int[n];
//标记当前节点是否被访问
int[] isVisted = new int[n];
//记录最短路径
//存放从start点到各点的最短路径的字符串表示
String[] path = new String[n];
//初始化标记路径,我们假设一开始原点可以走完所有路径
for (int i = 0; i < path.length; i++) {
path[i] = vertexts[start]+"---->"+vertexts[i];
}
for (int i = 0; i < path.length; i++) {
System.out.println(path[i]);
}
//第一个顶点已经被访问,且路径为0
dis[start] = 0;
isVisted[start] = 1;
//我们要执行n-1次,因为传进来的原点,自己访问自己为0,就排除在外了,这里只是控制循环次数而已
for (int i = 1; i < n; i++) {
//s:起点---->u:中间点--->v:终点
int u = -1;//初始化中间点的坐标
//寻找最小的权值,默认无穷大
int minDis = Integer.MAX_VALUE;
//找到原点到所有顶点的最短路径(权值),并记录下标,这里的下标就是中间点的下标
for (int j = 0; j < n; j++) {
//要到的结点没有被访问,而且原点到顶点的距离是最小的
if (isVisted[j] == 0 && matrix[start][j] < minDis) {
//记录这个原点到它所有可到达顶点的最小的距离
minDis = matrix[start][j];
//记下中间点的坐标
u = j;
}
}
System.out.println("minDis:"+minDis);
System.out.println("u="+u);
//标记下最短的距离
dis[u] = minDis;
//该中间点已被访问
isVisted[u] = 1;
//现在我们以u中间点的视角进行选择,找到最短路径(u,v),此时就能得到我们的w(s,v)最短路径
for (int k = 0; k < n; k++) {
//当前顶点没有被访问,而且s-->u + u-->v < s-->v:
if(isVisted[k] == 0 && matrix[start][u]+matrix[u][k] < matrix[start][k] ) {
//更新原点到各个顶点的距离
matrix[start][k] = matrix[start][u]+matrix[u][k];
//记录最短距离的路径,最短路径是原点到终点的路径
//它是把中间点到目标点的最短路径进行赋值
//k代表目标结点的下标,u代表中间点的下标
//由原来的原点-->终点,换了路径:从中间点到终点
path[k] = path[u]+"---->"+vertexts[k];
}
}
}
for (int i = 0; i < path.length; i++) {
System.out.println(path[i]+"最短路径为"+dis[i]);
}
}
}
/*
找取n-1次,n代表顶点个数,
找到原定(s)到其他其他顶点(u)的最小距离,然后以这个顶点(k)的视角选择,
看中间点(u)到终点(v)的距离+原点(s)到中间点(k)的距离,是否小于原点直接到终点的距离。
是就跟新原点到其他点的距离,然后进行循环,类似贪心算法
*/
最短路径是把两点之间路径最短的问题,应用如导航,两个地方怎么走距离最短。可以存在到不了的情况。
这个问题是说,如何找到从某个特定的节点出发,通向其他节点的最短路径。它只着眼于点与点之间的路径问题,并不关注整个图,也就意味着对一个节点运行算法的结果与另一个节点的结果之间没有多少关系。
比如说,可以把城市的路口看作图的节点,把公路看做边,综合长度、拥堵程度等指标作为边的权重,就可以通过Dijkstra算法计算出从城市一点到另一点的最短路线。
最小生成树是把连通的图的所有顶点连起来路径之和最小的问题,即生成树总权值之和最小。
即在一个连通的图里,如何去除图里的边,使得剩余的边仍能连接所有的节点,且这些边的权重之和最小。显然,满足这个要求的图不可能存在环,也就是一棵树,因此叫做生成树。这种算法与上面的相反,着眼于整个图的结构,并不关心某两个节点之间的路径是不是最短的。
这种算法的应用也很广泛,比如说有一个快递公司,每天都要开车把快递送到城市里的不同地点,怎样走才能不重复地经过每个节点,还能让总时间最短呢?我们就可以用Kruskal这样的最小生成树算法,找到一个最小生成树,只需要按这个路线走就行了。
https://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html
https://blog.csdn.net/a2392008643/article/details/81781766
https://blog.csdn.net/xushiyu1996818/article/details/90475757
https://www.cnblogs.com/haimishasha/p/5363390.html
https://blog.csdn.net/a2392008643/article/details/81781766
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
加点法
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
public class Prime算法 {
/**
* 最小生成树的prim算法
* @author liuy
*/
/*
在prim算法中,通过加入最小邻接边的方法来建立最小生成树算法。
首先构造一个零图,在选一个初始顶点加入到新集合中,
然后分别在原先的顶点集合中抽取一个顶点,使得构成的边为权值最小,
然后将该笔边加入到图中,并将抽出的顶点加入到新集合中,
重复这个过程,知道新集合等于原先的集合。
最短路径:起点到目标点的最短距离
最小生成树:整个路径的和的最小值
*/
public static void prim(int num, float[][] weight) {
//num为顶点数,weight为权
float[] lowcost = new float[num + 1]; //到新集合的最小权
int[] closest = new int[num + 1]; //代表与s集合相连的最小权边的点
//代表顶点是否被连接
boolean[] s = new boolean[num + 1]; //s[i] == true代表i点在s集合中
//起点被连接
s[1] = true; //将第一个点放入s集合
for(int i = 2; i <= num; i++) { //初始化辅助数组
//把起点初始的连接关系赋值给lowcost
lowcost[i] = weight[1][i];
//初始都是起点
closest[i] = 1;
//初始都未被访问
s[i] = false;
}
//每个定点依次遍历
for(int i = 1; i < num; i++) {
float min = Float.MAX_VALUE;
//初始化新节点地坐标
int j = 1;
for(int k = 2; k <= num; k++) {
//!s[k],没有连接过的结点
//lowcost[k] < min,这个点到目标点的最小权值
if((lowcost[k] < min) && (!s[k])) {//根据最小权加入新点
min = lowcost[k];
//找到新加入结点的下标
j = k;
}
}
//新加入点的j,和j与哪个点相连接的,或者说是由谁找到的
System.out.println("加入点" + j + " : " + j + "---" + closest[j]);
//这个结点已被加入
s[j] = true;//加入新点j
//以新加入结点的视角,来比较起点到终点的距离
for(int k = 2; k <= num; k++) {
if((weight[j][k] < lowcost[k]) && !s[k]) {//根据新加入的点j,求得最小权
lowcost[k] = weight[j][k];
//这个更新的点是和这个中间点相连的,而不是和起点相连的
closest[k] = j;
}
}
}
}
public static void main(String[] args) {
// ①
// / | /
// 6 1 5
// / | /
// ②-5--③--5--④
// / // /
// 3 6 4 2
// // //
// ⑤--6-⑥
//最小生成树为:
// ①
// |
// 1
// |
// ②-5--③ ④
// / / /
// 3 4 2
// / //
// ⑤ ⑥
//
float m = Float.MAX_VALUE;
// 到达自己也是不可以的
float[][] weight = {{0, 0, 0, 0, 0, 0, 0},
{0, m, 6, 1, 5, m, m},
{0, 6, m, 5, m, 3, m},
{0, 1, 5, m, 5, 6, 4},
{0, 5, m, 5, m, m, 2},
{0, m, 3, 6, m, m, 6},
{0, m, m, 4, 2, 6, m}};//上图的矩阵
prim(weight.length - 1, weight);
//加入点3. 3---1
//加入点6. 6---3
//加入点4. 4---6
//加入点2. 2---3
//加入点5. 5---2
}
}
https://blog.csdn.net/weiyuefei/article/details/79316653
https://zhuanlan.zhihu.com/p/98406740
https://blog.csdn.net/weixin_37818081/article/details/78633187
https://blog.csdn.net/u013027996/article/details/17112325
https://blog.csdn.net/Morandas/article/details/69946530
import java.util.Scanner;
public class 分考场3 {
static int[] pre ;
static int ans;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n1 = scanner.nextInt();
int n2 = scanner.nextInt();
int n = n1*n2;
int m = scanner.nextInt();
// 记录每个结点的父节点
pre = new int[n+1];
// 刚开始的时候每个结点的父节点是自己,先初始化一下
for (int i = 1; i <= n; i++) {
pre[i] = i;
}
for (int i = 0; i < m; i++) {
int a = scanner.nextInt();
int b = scanner.nextInt();
int fa = find2(a);
int fb = find2(b);
// 合并父节点,就是把第二个赋给第一个
if (fa != fb) {
pre[fb] = fa;
}
}
for (int i = 1; i <= n; i++) {
// 等下标和元素值相等时,代表它是一个集合
if (pre[i] ==i ) {
ans++;
System.out.print(i+":"+pre[i]+"\t");
}
System.out.print(i+":"+pre[i]+"\t");
}
}
private static int find(int x) {
// 查找这个人(a)的父节点,数组的下标就代表这个人
// 当下标等于元素值时,就找了父节点
while (pre[x] != x) {
// pre[x]:x这个人的父节点,这样就可以一直循环,直到找到父节点
x = pre[x];
}
return x;
}
// 这是经过压缩的算法
private static int find2(int x) {
// 查找这个人(a)的父节点,数组的下标就代表这个人
// 当下标等于元素值时,就找了父节点,最终可以找到集合结点
int temx = x;
while (pre[x] != x) {
// pre[x]:x这个人的父节点,这样就可以一直循环,直到找到父节点
x = pre[x];
}
// // 压缩路径
int temp;
// 当前结点不是父节点的时候,就一直开始遍历
while (temx != x) {
// 获取节点的父节点
temp = pre[temx];
// 把当前结点的父节点设为集合节点
pre[temx]= x;
// 把父节点赋给当前结点,进行跟新
temx = temp;
}
return x;
}
}
https://zhuanlan.zhihu.com/p/132702321/
https://www.cnblogs.com/duzhentong/p/7816594.html