算法基础简介

目录

 

1、递归

2、二分查找

3、排序算法

分类

3.1、冒泡排序

3.2、选择排序

3.3、插入排序

3.4、希尔排序(高级插入排序)

3.5、归并排序

3.6、快速排序

核心思想        

具体步骤

代码实现

3.7、堆排序

3.8、计数排序

3.9、桶排序

3.10、基数排序

4、字符串匹配算法

4.1、BF算法

4.2、RK算法

4.3、BM算法

4.3.1、一个例子理解一下

4.3.2、原理

4.3.2.1、坏字符规则

4.3.2.2、好后缀规则

4.4、Trie树

4.4.1、插入

4.4.2、查找

4.4.3、实现

5、算法思维

5.1、贪心算法

5.1.1、概念

5.1.2、部分背包问题

5.2、分治算法

5.3、回溯算法

5.4、动态规划

5.4.1、斐波那契数列:

1)递归分治+记忆搜索(备忘录)

2)递推(dp方程)


1、递归

  • 概念:在函数的定义中调用函数自身的方法。或者说,递归算法是一种直接或者间接调用自身函数/方法的算法。
  • 本质:是一种循环,在循环中调用自己。需要设置结束条件,避免函数无限递归导致OOM。
  • 优缺点:递归可以使代码变得更简洁,但如果递归太深,可能造成栈溢出。如果退出条件设置不合理,也很有可能造成OOM。
  • 应用:二分查找、快速排序、归并排序、树的遍历、回溯算法、分治算法、动态规划等。
  • 经典案例:斐波那契数列,普通递归时间复杂度为O(2^n)。以下方式会有大量重复计算,可利用动态规划进行优化。
<!--斐波那契数列:0、1、1、2、3、5、8、13、21、34、55.....规律:从第3个数开始,每个数等于前面两个数的和递归分析:函数的功能:返回n的前两个数的和递归结束条件:从第三个数开始,n<=2函数的等价关系式:fun(n)=fun(n-1)+fun(n-2)
-->
//递归实现
public static int fun2(int n) {if (n <= 1) return n;return fun2(n - 1) + fun2(n - 2);
}
public static void main(String[] args) {fun1(9);System.out.println("==================================");System.out.println(fun2(9));
}

2、二分查找

  • 概念:也叫做折半查找法。是针对有序数据集合的查找算法,不适用于无序数据集合。
  • 本质:查找时,每次与数据集合中间的数mid进行匹配。匹配成功则直接返回,如果大于mid,则舍弃小于mid那一半;如果小于mid,则舍弃大于mid那一半。这样,每一次比较都能排除掉一半的元素,收缩快。
  • 时间复杂度:O(logn)。
  • 优缺点:速度快,不占额外空间,但二分查找只能针对有序数据。数据量太小意义不大,但数据量太大会占用连续的空间,也不太合适。
  • 应用:所有有序数据集合的查找。
  • while循环实现
/**
* 在有序数组中找到某个数的位置
*/
public static int binarySearch(int[] nums, int t) {int low = 0;	//低位索引int high = nums.length - 1;	//高位索引int mid = 0;	//中间索引while (low <= high) {mid = (low + high) / 2;	if (t == nums[mid]){	//等于半数return mid;} else if(t < nums[mid]){	//半数以里high=mid-1;} else {low=mid+1;	//半数以外}}return -1;
}
  • 递归实现
	/*** 二分查找*title:recursionBinarySearch*@param arr 有序数组*@param key 待查找关键字*@return 找到的位置*/public static int recursionBinarySearch(int[] arr,int key,int low,int high){if(key < arr[low] || key > arr[high] || low > high){return -1;				}int middle = (low + high) / 2;			//初始中间位置if(arr[middle] > key){//比关键字大则关键字在左区域return recursionBinarySearch(arr, key, low, middle - 1);}else if(arr[middle] < key){//比关键字小则关键字在右区域return recursionBinarySearch(arr, key, middle + 1, high);}else {return middle;}	}

3、排序算法

分类

时间复杂度为O(n²):冒泡排序、选择排序、插入排序、希尔排序

时间复杂度为O(nlogn):快速排序、归并排序、堆排序

时间复杂度为线性:计数排序(O(m+n))、桶排序(O(n))、基数排序

3.1、冒泡排序

        依次比较相邻的元素,如果不符合排序条件则交换两个元素。这样每一轮比较后,最大(小)的值都会移动到数组尾部。N-1轮过后,排序完成。

public static int[] bubbleSort(int[] arr) {int length = arr.length;for (int i=0;i<length-1;i++) {for (int j=0;j<length-1-i;j++) {if (arr[j] > arr[j+1]) {int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}return arr;
}

3.2、选择排序

        每一轮都从未排序序列中找到最大(小)元素,存放到已排序序列的末尾。经过n-1轮过后,排序完成。

public static int[] selectionSort(int[] arr) {int length = arr.length;int min;for (int i=0;i<length-1;i++) {min = i;for (int j=i+1;j<length;j++) {if (arr[min] > arr[j]) {min = j;}}int temp = arr[min];arr[min] = arr[i];arr[i] = temp;}return arr;
}

3.3、插入排序

构建一个有序序列,将未排序的数据,一个一个地插入到有序序列中。

public static int[] insertSort(int[] arr) {for (int i=0;i<arr.length;i++) {int temp = arr[i];   //要插入的数据int j=i;// 从已经排序的序列最右边的开始比较,找到比其小的数while (j>0 && arr[j-1] > temp) {arr[j] = arr[j-1];j--;}if (j != i) {   // 存在比其小的数,插入arr[j] = temp;}}return arr;
}

3.4、希尔排序(高级插入排序)

核心:插入排序 + 分组。

        首次增量 = length/2,之后每一次循环,分组step = step/2。

        先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。

        举例说明:

        现有数组[12, 23, 25, 5, 32, 10, 13, 23, 27, 38],长度为10。

        1、首次排序增量step = 5,将数组分为[12, 10],[23, 13],[25, 23], [5, 27], [32, 38]。

             分别对三个数组进行插入排序,得到[10, 12], [13, 23], [23, 25], [5, 27], [32, 38]五个有序小数组,即[10, 13, 23, 5, 32, 12, 23, 25, 27, 38]。

        2、第二次排序增量为step = 5/2 = 2, 将数组分为[10, 23, 32,  23, 27]和[13, 5, 12, 25, 38]。

             分别对两个数组进行插入排序,得到[10, 23, 23 ,27, 32]和[5, 12, 13, 25, 38]两个有序小数组,即[10, 5, 23, 12, 23, 13, 27, 25, 32, 38],达到宏观有序。

        3、第三次排序增量为step = 2/2 =1,直接对上一步数组进行插入排序。

             得到[5, 10, 12, 13, 23, 23, 25, 27, 32, 38]。完成排序。

public static int[] shellSort(int[] arr) {int length = arr.length;// step: 增量, 每次都/2for (int step = length / 2; step >= 1; step /= 2) {// 从增量那组开始进行插入排序,直至完毕for (int i = step; i < length; i++) {int j = i;int temp = arr[j];// j - step 就是代表与它同组隔壁的元素while (j - step >= 0 && arr[j - step] > temp) {arr[j] = arr[j - step];j = j - step;}arr[j] = temp;}}return arr;
}

3.5、归并排序

        分治思想,将大问题拆解为小问题,递归将大数组拆分成小数组(只有一个元素的数组),分别对小数组进行排序,然后再合并小数组。

public int[] sort(int[] a, int low, int high) {int mid = (low + high) / 2;if (low < high) {sort(a, low, mid);sort(a, mid+1, high);merge(a, low, mid, high);}return a;}public void merge(int[] a, int low, int mid, int high) {int[] temp = new int[high - low + 1];int i = low;int j = mid + 1;int k = 0;while (i <= mid && j <= high) {if (a[i] < a[j]) {temp[k++] = a[i++];} else {temp[k++] = a[j++];}}while (i <= mid) {temp[k++] = a[i++];}while (j <= high) {temp[k++] = a[j++];}for (int x = 0; x < temp.length; x++) {a[x + low] = temp[x];}}

3.6、快速排序

核心思想        

        设置两个变量low和high,排序开始时,low = 0, high = size-1;

        从数组中找一个基准值,所有元素比基准值小的摆放在基准值前,所有元素比基准值大的摆放在基准值后。

具体步骤

        1、从数列中挑出一个元素,称为 “基准元素”;

        2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作;

        3、递归地把小于基准值元素的子数列和大于基准值元素的子数列分别进行快速排序。

代码实现

private void swap(int[] arr, int x, int y) {int temp = arr[x];arr[x] = arr[y];arr[y] = temp;
}
private void quick_sort_recursive(int[] arr, int start, int end) {if (start >= end)return;int mid = arr[end];int left = start, right = end - 1;while (left < right) {while (arr[left] <= mid && left < right)left++;while (arr[right] >= mid && left < right)right--;swap(arr, left, right);}if (arr[left] >= arr[end])swap(arr, left, end);elseleft++;quick_sort_recursive(arr, start, left - 1);quick_sort_recursive(arr, left + 1, end);
}

3.7、堆排序

        将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。 将其与末尾元素进行交换,此时末尾就为最大值。

        然后将剩余n-1个元素重新构造成一个堆,这样会得 到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

public static void sort(int[] array) {// 1. 把无序数组构建成最大堆// array.lentgh /2 - 1 : 第一个非叶子节点的左孩子for (int i = array.length / 2 - 1; i >= 0; i--) {adjustHeap(array, i, array.length);}// 2. 调整堆结构+交换堆顶元素与末尾元素,调整堆产生新的堆顶for (int i = array.length - 1; i > 0; i--) {// 最后1个元素和第1个元素进行交换int temp = array[i];array[i] = array[0];array[0] = temp;// “下沉”调整最大堆adjustHeap(array, 0, i);}
}public static void adjustHeap(int []array,int parentIndex,int length){// temp 保存父节点值,用于最后的赋值int temp = array[parentIndex];int childIndex = 2 * parentIndex + 1;while (childIndex < length){// 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子if (childIndex + 1 < length && array[childIndex + 1] >array[childIndex]){childIndex++;}// 如果父节点大于任何一个孩子的值,则直接跳出if (temp >= array[childIndex])break;//无须真正交换,单向赋值即可array[parentIndex] = array[childIndex];parentIndex = childIndex;//下一个左孩子childIndex = 2 * childIndex + 1;}array[parentIndex] = temp;
}

3.8、计数排序

使用一个额外的数组进行排序记录,其中第i个元素就是待排序数组A中值等于i的元素的个数。

适合于连续的取值范围不大的数组。

不连续和取值范围过大会造成数组过大。

比如:待排序元素集合为:9127813653。排序如下所示:

public static int[] countSort(int[] array, int offset) {int[] nums = new int[array.length];for (int i = 0; i < array.length; i++) {int n = (array[i] - offset);//数字自增nums[n]++;}int[] nums2 = new int[array.length];// i是计数数组下标,k是新数组下标for (int i = 0, k = 0; i < nums.length; i++) {for (int j = 0; j < nums[i]; j++) {nums2[k++] = i + offset;}}return nums2;
}

3.9、桶排序

        分治思想,将待排序数组元素,分到有限数量的桶里。每个桶再进行单独排序,最后按顺序将各个桶中的元素输出。

        每一个桶(bucket)代表一个区间范围,里面可以承载一个或多个元素。

        每个桶的区间跨度 = (最大值-最小值)/ (桶的数量 - 1)。

        比如,有数组[4.5、0.84、3.25、2.18、0.5],设置桶的数量为5,则可分为以下几个桶

public static double[] bucketSort(double[] array) {double max = 0;double min = 0;//获得最大值和最小值之间的差for (int i = 0; i < array.length; i++) {if (array[i] > max) {max = array[i];}if (array[i] < min) {min = array[i];}}double d = max - min;//桶初始化int bucketNum = array.length;ArrayList<LinkedList<Double>> bucketList =new ArrayList<LinkedList<Double>>(bucketNum);for (int i = 0; i < bucketNum; i++) {bucketList.add(new LinkedList<Double>());}//将每个元素放入桶中for (int i = 0; i < array.length; i++) {int num = (int) ((array[i] - min) * (bucketNum - 1) / d);bucketList.get(num).add(array[i]);}//对每个桶内部进行排序for (int i = 0; i < bucketList.size(); i++) {Collections.sort(bucketList.get(i));}//输出全部元素double[] sortedArray = new double[array.length];int index = 0;for (LinkedList<Double> list : bucketList) {for (double element : list) {sortedArray[index] = element;index++;}}return sortedArray;
}

3.10、基数排序

假设有待排序数组[53, 3, 542, 748, 14, 214, 154, 63, 616],排序图解如下:

        将整数按位数切割,然后对每个位数分别比较(依次比较个位、十位、百位、千位......)。比较方式有两种,为LSD和MSD。   

  • MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序。
  • LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序。
public static void lsd_RadixSort(int[] arr,int max) {//bucket用来当桶,放数据,取数据int[] bucket = new int[arr.length];//k表示第几位,1代表个位,2代表十位,3代表百位...for(int k=1;k<=max;k++) {int[] count = new int[arr.length];//分别统计第k位是0,1,2,3,4,5,6,7,8,9的数量//即此循环用来统计每个桶中的数据的数量for(int i=0;i<arr.length;i++) {count[getFigure(arr[i],k)]++;}//利用count[i]来确定放置数据的位置for(int i=1;i<arr.length;i++) {count[i] = count[i] + count[i-1];}//执行完此循环之后的count[i]就是第i个桶右边界的位置//利用循环把数据装入各个桶中,注意是从后往前装for(int i=arr.length-1;i>=0;i--) {int j = getFigure(arr[i],k);bucket[count[j]-1] = arr[i];count[j]--;}//将桶中的数据取出来,赋值给arrfor(int i=0,j=0;i<arr.length;i++,j++) {arr[i] = bucket[j];}}
}//此函数返回整型数i的第k位是什么
public static int getFigure(int i,int k) {int[] a = {1,10,100};return (i/a[k-1])%10;
}

4、字符串匹配算法

        从一个字符串中,判断是否包含一个子串。(indexOf()函数)。

4.1、BF算法

        Brute Force,暴力匹配算法。在字符串 A 中查找字符串 B,那字符串 A 就是主串,字符串 B 就是子串。在主串中,检查起始位置分别是 012…n-m 且长度为 m n-m+1 个子串,看有没有跟子串匹配的字符串。

        时间复杂度为O(m*n),m为子串长度,n为主串长度。

public boolean isMatch(String str, String sub) {for (int i = 0; i <= str.length() - sub.length(); i++) {for (int j = 0; j < sub.length(); j++) {if (str.charAt(i+j) != sub.charAt(j)) {break;}if (j == sub.length() - 1) {return true;}}}return false;
}

4.2、RK算法

        Rabin-Karp算法,通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与子

串的哈希值比较大小。如果有任意一个哈希值与子串的哈希值相等,则说明匹配成功。

存在哈希冲突,匹配结果可能有误。
时间复杂度为O(m+n),m为子串长度,n为主串长度。
public static boolean isMatch(String main, String sub) {//算出子串的hash值int hash_sub = strToHash(sub);for (int i = 0; i <= (main.length() - sub.length()); i++) {// 主串截串后与子串的hash值比较if (hash_sub == strToHash(main.substring(i, i + sub.length()))) {return true;}}return false;
}
/*** 支持 a-z 二十六进制* 获得字符串的hash值* 比如:字符串abc,a的ascii=97,b的ascii=98,c的ascii=99。* 那么,字符串abc的hash值 = 97 * 26² + 98 * 26¹ + 99 * 26º = 68219* @param src* @return*/
public static int strToHash(String src) {int hash = 0;for (int i = 0; i < src.length(); i++) {hash += src.charAt(i) * Math.pow(26, src.length() - i -1);}return hash;
}

4.3、BM算法

        Boyer-Moore算法,滑动算法。减少比较次数,避免一些肯定不会匹配字符串的比较。

        时间复杂度为O(n/m)。

4.3.1、一个例子理解一下

4.3.2、原理

        当不匹配的时候 一次性可以跳过不止一个字符 。即它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。

4.3.2.1、坏字符规则

字符串匹配失败时,移动的位数 = 坏字符在子串中的位置si - 坏字符在子串中最右出现的位置xi。

如果坏字符没有在子串中出现,那么xi = -1。

匹配顺序
什么是坏字符

滑动位数计算
private static final int SIZE = 256; // 全局变量或成员变量
/**
*
* @param b
* @param m
* @param dc
*/
private static void generateBC(char[] b, int m, int[] dc) {for (int i = 0; i < SIZE; ++i) {dc[i] = -1; // 初始化 bc 模式串中没有的字符值都是-1}//将模式串中的字符希写入到字典中for (int i = 0; i < m; ++i) {int ascii = (int)b[i]; // 计算 b[i] 的 ASCII 值dc[ascii] = i;}
}
/**
* 坏字符
* @param a 主串
* @param b 模式串
* @return
*/
public static int bad(char[] a, char[] b) {//主串长度int n=a.length;//模式串长度int m=b.length;//创建字典int[] bc = new int[SIZE];// 构建坏字符哈希表,记录模式串中每个字符最后出现的位置generateBC(b, m, bc);// i表示主串与模式串对齐的第一个字符int i = 0;while (i <= n - m) {int j;for (j = m - 1; j >= 0; --j) { // 模式串从后往前匹配//i+j : 不匹配的位置if (a[i+j] != b[j]) break; // 坏字符对应模式串中的下标是j}if (j < 0) {return i; // 匹配成功,返回主串与模式串第一个匹配的字符的位置}// 这里等同于将模式串往后滑动j-bc[(int)a[i+j]]位// j:si bc[(int)a[i+j]]:xii = i + (j - bc[(int)a[i+j]]);}return -1;
}

4.3.2.2、好后缀规则

字符串匹配失败时,后移位数 = 好后缀在子串中的位置 - 好后缀在子串上一次出现的位置,且如果好后缀在子串中没有再次出现,则为 -1。

4.4、Trie树

        也叫字典树,树形结构,专门处理字符串匹配的数据结构,用来解决在一组字符串中快速查找某个字符串的问题。

        本质就是利用字符串的公共前缀,将重复的前缀合并在一起。

        根节点不包含任何信息,从根节点到叶子结点的一条路径,构成一个字符串。

hello、her、hi、how、see、so

4.4.1、插入

4.4.2、查找

        将要查找的字符串分割成单 个的字符,然后从 Trie 树的根节点开始匹配。

4.4.3、实现

通过一个下标与字符一一映射的数组,来存储子节点的指针。
假设字符串中只有a-z共26个小写字母。
如下所示:下标为0的位置,存储指向子节点a的指针;下标为1的位置,存储指向子节点b的指针。
public class TrieNode {public char data;public TrieNode[] children = new TrieNode[26];public boolean isEndingChar = false;public TrieNode(char data) {this.data = data;}
}public class Trie {private TrieNode root = new TrieNode('/'); // 存储无意义字符// 往Trie树中插入一个字符串public void insert(char[] text) {TrieNode p = root;for (int i = 0; i < text.length; ++i) {int index = text[i] - 97;if (p.children[index] == null) {TrieNode newNode = new TrieNode(text[i]);p.children[index] = newNode;}p = p.children[index];}p.isEndingChar = true;}// 在Trie树中查找一个字符串public boolean find(char[] pattern) {TrieNode p = root;for (int i = 0; i < pattern.length; ++i) {int index = pattern[i] - 97;if (p.children[index] == null) {return false; // 不存在pattern}p = p.children[index];}if (p.isEndingChar == false) return false; // 不能完全匹配,只是前缀else return true; // 找到pattern}
}

5、算法思维

5.1、贪心算法

5.1.1、概念

        一种在每一个步骤中都采取在当前状态下最好或最后的选择,从而希望导致结果是全局最好或最优的算法。(在当前选择做最优选择,不能回退)。

        在不考虑排序的前提下,贪心算法只需要依次循环,所以时间复杂度为O(n)

        性能高,能用贪心算法解决的往往是最优解;但实际场景下应用不多。

贪心算法不是最优解
贪心算法为最优解

5.1.2、部分背包问题

  • 背包问题:背包有最大承重,物品有重量和价值。给定不同价值的物品,如何保证背包里的物品价值最大。
  • 部门背包(物品可分割):某件物品是一堆,可以带走这件物品的一部分。
  • 0-1背包(物品不可分割):对于某件物品,要么选中,要么不选中。

其中,部分背包问题可以使用贪心算法解决,并且能得到最优解。下面用一个例子举例说明:

        假设一共有N 件物品,第 i 件物品的价值为 Vi ,重量为 Wi ,一个小偷有一个最多只能装下重量为 W 的背 包,他希望带走的物品越有价值越好,可以带走某件物品的一部分,请问:他应该选择哪些物品?
        假设W = 50kg,物品信息如下所示:
  • 因为物品可分割,按物品单位重量价值排序;
  • 可得单位重量的物品价值A > B > C;
  • 所以取A = 10kg,B = 20kg,C = 20kg,可保证背包中价值最高。
/*** 贪心算法:部分背包*/
public class BagDemo {public void take(List<Goods> goodslist, double bag) {// 对物品按照价值排序从高到低List<Goods> goodslist2 = sort(goodslist);double sum_w = 0;// 取出价值最高的for (int i = 0; i < goodslist2.size(); i++) {sum_w += goodslist2.get(i).weight;if (sum_w <= bag) {System.out.println(goodslist2.get(i).name + "取" +goodslist2.get(i).weight + "kg");} else {System.out.println(goodslist2.get(i).name + "取" + (bag - (sum_w - goodslist2.get(i).weight)) + "kg");return;}}}// 按物品的每kg 价值排序 由高到低 price/weightprivate List<Goods> sort(List<Goods> goodslist) {goodslist.sort((o1, o2) -> (int) (o2.val - o1.val));return goodslist;}public static void main(String[] args) {BagDemo bd = new BagDemo();Goods goods1 = new Goods("A", 10, 60);Goods goods2 = new Goods("B", 20, 100);Goods goods3 = new Goods("C", 30, 120);List<Goods> goodslist = new ArrayList<>(){{add(goods2);add(goods3);add(goods1);}};bd.take(goodslist, 50);}
}class Goods {String name;double weight;double price;double val;public Goods(String name,double weight, double price) {this.name=name;this.weight = weight;this.price = price;val=price/weight;}
}

5.2、分治算法

        分而治之,将原问题划分为n个规模较小,结构与原问题相似的子问题,递归解决这些子问题,然后再合并结果。(快速排序就用到了分治算法)。

        当问题足够小时,可直接求解(递归结束条件)。

        分治算法要求分割成的子问题,不能有重复子问题。

举例说明:将小写abcde转换成大写ABCDE。

public static char[] toUpCase(char[] chs,int i){if (i >= chs.length) {return chs;}chs[i] = toUpCaseUnit(chs[i]);return toUpCase(chs,i+1);
}
public static char toUpCaseUnit(char c) {int n=c;if (n<97 || n>122){return ' ';}return (char)Integer.parseInt(String.valueOf(n-32));
}
public static void main(String[] args) {String ss="abcde";System.out.println(Test.toUpCase(ss.toCharArray(),0));
}

5.3、回溯算法

        类似枚举深度优先搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就"回溯"返回(递归返回),尝试别的路径。

        万金油算法,穷举所有的情况,找到满足期望的解。时间复杂度为O(n!),指数级别,性能非常低。

经典的八皇后问题:

        是如何将 8  个皇后放置在 8 × 8 的棋盘上,并且使皇后彼此之间不能相互攻击。

         把这个问题划分成 8 个阶段,依次将 8 个棋子放到第一行、第二行、第三行……第八行。在放置的 过程中,我们不停地检查当前放法,是否满足要求。如果满足,则跳到下一行继续放置棋子;如果不满 足,那就再换一种放法,继续尝试。

/*** 回溯算法:N皇后问题*/
public class NQueens {//皇后数static int QUEENS = 8;//下标表示行,值表示queen存储在哪一列int[] result = new int[QUEENS];/*** 在每行放置Queen** @param row*/public void setQueens(int row) {//递归中断if (row == QUEENS) {printQueens();return;}//在每行依次放置列 没有合适的则回到上一层for (int col = 0; col < QUEENS; col++) {if (isOk(row, col)) {//设置列result[row] = col;//开始下一行setQueens(row + 1);}}}/*** 打印输出*/private void printQueens() {for (int i = 0; i < QUEENS; i++) {for (int j = 0; j < QUEENS; j++) {if (result[i] == j) {System.out.print("Q| ");} else {System.out.print("*| ");}}System.out.println();}System.out.println("-----------------------");}/*** 判断是否可以放置** @param row 行* @param col 列* @return*/private boolean isOk(int row, int col) {int leftup = col - 1;int rightup = col + 1;// 逐行往上考察每一行for (int i = row - 1; i >= 0; i--) {//列上存在queenif (result[i] == col) return false;//左上对角线存在queenif (leftup >= 0) {if (result[i] == leftup) return false;}//右下对角线存在queenif (rightup < QUEENS) {if (result[i] == rightup) return false;}leftup--;rightup++;}return true;}public static void main(String[] args) {NQueens queens=new NQueens();queens.setQueens(0);}
}

5.4、动态规划

        分阶段求解,通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推/分治的方式去解决。

三个概念:最优子结构、边界(初始值)、dp方程。

动态规划常用于算法实现中存在大量的重复子问题正好相反,动态规划之所以高效,就是因为回溯算法实现中存在大量的重复子问题。

5.4.1、斐波那契数列:

递归实现(存在大量的重复计算)

1)递归分治+记忆搜索(备忘录)

利用一个数组,来保存已经计算过的函数,减少递归过程中的重复计算。

//用于存储每次的计算结果
static int[] sub=new int[10];public static int fib(int n){if(n<=1) return n;//没有计算过则计算if(sub[n]==0){sub[n]=fib(n-1)+fib(n-2);}//计算过直接返回return sub[n];
}public static void main(String[] args) {System.out.println(fib(9));
}

2)递推(dp方程)

dp方程:

if (i < 2):dp[0] = 0; dp[1] = 1;

if (i >= 2):dp[i] = dp[i-1] + dp[i-2]

  • 最优子结构:fib[9]=finb[8]+fib[7]
  • 边界:a[0]=0; a[1]=1;
  • dp方程:fib[n]=fib[n-1]+fib[n-2]
public static int fib(int n){int a[]=new int[n+1];a[0]=0;a[1]=1;int i;// a[n]= a[n-1]+a[n-2]for(i=2;i<=n;i++){a[i]=a[i-1]+a[i-2];}// i已经加1了,所以要减掉1return a[i-1];
}
public static void main(String[] args) {System.out.println(fib(9));
}

以上内容为个人学习理解,如有问题,欢迎在评论区指出。

部分内容截取自网络,如有侵权,联系作者删除。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/79044.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT自带PDF库的使用

QT自带PDF库可以方便的打开PDF文件&#xff0c;并将文件解析为QImage&#xff0c;相比网上提供的开源库&#xff0c;QT自带PDF库使用更方便&#xff0c;也更加可靠&#xff0c;然而&#xff0c;QT自带PDF库的使用却不同于其他通用库的使用&#xff0c;具备一定的技巧。 1. 安装…

【深度学习】Transformer,Self-Attention,Multi-Head Attention

必读文章&#xff1a; https://blog.csdn.net/qq_37541097/article/details/117691873 论文名&#xff1a;Attention Is All You Need 文章目录 1、Self-Attention 自注意力机制2、Multi-Head Attention 1、Self-Attention 自注意力机制 Query&#xff08;Q&#xff09;表示当…

Docker Compose构建lnmp

目录 Compose的优点 编排和部署 Compose原理 Compose应用案例 安装docker-ce 阿里云镜像加速器 安装docker-compose docker-compose用法 Yaml简介 验证LNMP环境 Compose的优点 先来了解一下我们平时是怎么样使用docker的&#xff1f;把它进行拆分一下&#xff1a; 1…

全志F1C200S嵌入式驱动开发(soc系统集成)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 任何一个嵌入式设备都是由很多的子系统组成的。这里面有硬件、有软件,还可能有机械,并不一定就是大家看到的消费电子那样,即一个soc构成了所有的系统。现实情况是,要构建一个系…

网关 GateWay 的使用详解、路由、过滤器、跨域配置

一、网关的基本概念 SpringCloudGateway网关是所有微服务的统一入口。 1.1 它的主要作用是&#xff1a; 反向代理&#xff08;请求的转发&#xff09; 路由和负载均衡 身份认证和权限控制 对请求限流 1.2 相比于Zuul的优势&#xff1a; SpringCloudGateway基于Spring5中…

【JavaSE】面向对象编程思想之继承

【本节目标】 1. 继承 2. 组合 目录 1. 为什么需要继承 2. 继承概念 3. 继承的语法 4. 父类成员访问 4.1 子类中访问父类的成员变量 4.2 子类中访问父类的成员方法 5. super关键字 6. 子类构造方法 7. super和this 8. 再谈初始化 9. protected 关键字 10. 继承方式…

【C++基础(六)】类和对象(下)--初始化列表,友元,匿名对象

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C初阶之路⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 类和对象 1. 前言2. 初始化列表2.1初始化列表的作用…

岩土工程监测仪器多通道振弦传感器信号转换器应用于铁路监测

岩土工程监测仪器多通道振弦传感器信号转换器应用于铁路监测 岩土工程监测是工程建设和运营过程中必不可少的环节&#xff0c;它主要是通过对地下水位、土体应力、变形、固结沉降等参数进行实时监测&#xff0c;以保证工程施工和运营的安全性和稳定性。而多通道振弦传感器信号…

【Segment Anything Model】四:预处理自己的数据集接入SAM

文章目录 1️⃣预备知识2️⃣实现思路&#x1f538;脚本预处理得到包含embedd和GT的npz&#x1f538;编写Dataset类3️⃣代码&#x1f538;实现脚本预处理得到包含embedd和GT的npz代码&#x1f538;实现Dataset的代码 1️⃣预备知识 欢迎订阅本专栏&#xff08;为爱发电&#…

Idea添加mybatis的mapper文件模版

针对Java开发人员&#xff0c;各种框架的配置模版的确是需要随时保留一份&#xff0c;在使用的时候&#xff0c;方便复制粘贴&#xff0c;但是也依然不方便&#xff0c;我们可以给开发工具&#xff08;IDE&#xff09;中添加配置模版&#xff0c;这里我介绍下使用idea开发工具&…

ad+硬件每日学习十个知识点(18)23.7.29 (LDO原理、LDO的补偿引脚)

文章目录 1.LDO名字介绍2.LDO的应用范围3.LDO的原理4.LDO输出端和输入端的差值至少满足多少V&#xff1f;怎么计算的&#xff1f;5.输出的误差和输出电流&#x1f446;&#xff08;右下角图像&#xff09;6.LDO一般会有个引脚是做补偿之用&#xff0c;datasheet会说明一个器件的…

Packet Tracer - 检验 IPv4 和 IPv6 编址

Packet Tracer - 检验 IPv4 和 IPv6 编址 地址分配表 设备 接口 IPv4 地址 子网掩码 默认网关 IPv6 地址/前缀 R1 G0/0 10.10.1.97 255.255.255.224 N/A 2001:DB8:1:1::1/64 N/A S0/0/1 10.10.1.6 255.255.255.252 N/A 2001:DB8:1:2::2/64 N/A 本地链路 F…

Linux 信号signal处理机制

Signal机制在Linux中是一个非常常用的进程间通信机制&#xff0c;很多人在使用的时候不会考虑该机制是具体如何实现的。signal机制可以被理解成进程的软中断&#xff0c;因此&#xff0c;在实时性方面还是相对比较高的。Linux中signal机制的模型可以采用下图进行描述。 每个进程…

电力巡检无人机助力迎峰度夏,保障夏季电力供应

夏季是电力需求量较高的时期&#xff0c;随着高温天气的来临&#xff0c;风扇、空调和冰箱等电器的使用量也大大增加&#xff0c;从而迎来夏季用电高峰期&#xff0c;电网用电负荷不断攀升。为了保障夏季电网供电稳定&#xff0c;供电公司会加强对电力设施设备的巡检&#xff0…

spring — Spring Security 5.7与6.0差异性对比

1. spring security Spring Security 是一个提供身份验证、授权和针对常见攻击保护的框架。 凭借对保护命令式和反应式应用程序的一流支持&#xff0c;它成为基于Spring的标准安全框架。 Spring Security 在最近几个版本中配置的写法都有一些变化&#xff0c;很多常见的方法都…

【力扣每日一题】2023.8.7 反转字符串

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一个字符数组形式的字符串&#xff0c;让我们直接原地修改反转字符串&#xff0c;不必返回。 给出的条件是使用O(1)的额外空间…

24届近5年重庆邮电大学自动化考研院校分析

今天给大家带来的是重庆邮电大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、重庆邮电大学 学校简介 重庆邮电大学简称"重邮"&#xff0c;坐落于直辖市-重庆市&#xff0c;入选国家"中西部高校基础能力建设工程”、国家“卓越工程师教育培养计划…

【ES】笔记-let 声明及其特性

let 声明及其特性 声明变量 变量赋值、也可以批量赋值 let a;let b,c,d;let e100;let f521,giloveyou,h[];变量不能重复声明 let star罗志祥;let star小猪;块级作用域&#xff0c;let声明的变量只在块级作用域内有效 {let girl周杨青;}console.log(girl)注意&#xff1a;在 i…

SpringIOC注入的两种方式讲解以及代码示例

Ioc是Spring全家桶各个功能模块的基础&#xff0c;创建对象的容器。 AOP也是以IoC为基础&#xff0c;AOP是面向切面编程&#xff0c;抽象化的面向对象 AOP功能&#xff1a;打印日志&#xff0c;事务&#xff0c;权限处理 AOP的使用会在下一篇文章进行介绍 IoC 翻译为控制反…

配置Hive远程服务详细步骤

HiveServer2支持多客户端的并发和认证&#xff0c;为开放API客户端如JDBC、ODBC提供了更好的支持。 &#xff08;1&#xff09;修改hive-site.xml&#xff0c;在文件中添加以下内容&#xff1a; <property><name>hive.metastore.event.db.notification.api.auth&l…