算法基础简介

目录

 

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: 增量, 每次都/2
	for (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);
	else
		left++;
	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]--;
		}

		//将桶中的数据取出来,赋值给arr
		for(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]]:xi
		i = 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/weight
    private 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--) {
            //列上存在queen
            if (result[i] == col) return false;
            //左上对角线存在queen
            if (leftup >= 0) {
                if (result[i] == leftup) return false;
            }
            //右下对角线存在queen
            if (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了,所以要减掉1
	return a[i-1];
}
public static void main(String[] args) {
	System.out.println(fib(9));
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【80天学习完《深入理解计算机系统》】第三天 2.3 整数运算【正负溢出】【运算的溢出】【类型转换的二进制扩展】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

数据库的约束 详解

一、约束的概述 1.概念:约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 2&#xff0e;目的:保证数据库中数据的正确、有效性和完整性。 3.分类: 约束描述关键字非空约束限制该字段的数据不能为nullNOT NULL唯一约束保证该字段的所有数据都是唯一、不…

全球飞机电磁阀总体规模分析

电磁阀是一种液压管路的电磁装置&#xff0c;通过使用电流产生磁场&#xff0c;从而驱动螺线管&#xff0c;控制阀中流体的流动。电磁阀作为流体控制自动化系统的执行器之一&#xff0c;有着结构紧凑、尺寸小、重量轻、密封良好、维修简便和可靠性高、节能降耗的特点&#xff0…

如何安全变更亚马逊收款账户?

有太多的卖家想知道如何安全变更亚马逊收款账户&#xff0c;因为更改了第三方收款账户可能会导致二次视频认证或者增强视频。真的是这样吗&#xff1f; 其实不推荐亚马逊店铺正常运营之后去变更信用卡&#xff0c;收款账户等重要资料的&#xff0c;因为玩黑科技的卖家也真的多…

[C++ 网络协议] 套接字

目录 1. 套接字 1.1 在Linux平台下构建套接字 1.1.1 用于接听的套接字(服务器端套接字) 1.1.2 用于发送请求的套接字(客户端套接字) 1.2 在Windows平台下构建套接字 1.2.1 Winsock的初始化 1.2.2 用于接听的套接字(服务器端套接字) 1.2.3 用于发送请求的套接字(客户端套…

vue+neo4j(neo4j desktop安装和使用)

vueneo4j&#xff08;neo4j desktop安装和使用&#xff09; 本文目录 vueneo4j&#xff08;neo4j desktop安装和使用&#xff09;官网下载安装基本使用创建项目新增数据库连接数据库 使用cypher构建简单知识图谱创建节点创建关系删除节点及关系查询节点和关系 数据导出为json文…

Stable Diffusion - 俯视 (from below) 拍摄的人物图像 LoRA 与配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132192139 图像来自 哥特风格 LoRA 俯视 LoRA&#xff0c;提升视觉冲击力&#xff0c;核心配置 <lora:view_from_below:0.6>,(from below,…

JVM工作的总体机制概述

JDK、JRE、JVM关系回顾 JVM&#xff1a;Java Virtual Machine&#xff0c;翻译过来是Java虚拟机JRE&#xff1a;Java Runtime Environment&#xff0c;翻译过来是Java运行时环境 JREJVMJava程序运行时所需要的类库JDK&#xff1a;Java Development Kits&#xff0c;翻译过来是…

LeetCode--HOT100题(25)

目录 题目描述&#xff1a;141. 环形链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;141. 环形链表&#xff08;简单&#xff09; 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连…

【笔试训练】统计回文

一、单选 1、以下代码结果是什么&#xff08;&#xff09; public class foo {public static void main(String sgf[]) {StringBuffer anew StringBuffer("A");StringBuffer bnew StringBuffer("B");operate(a,b);System.out.println(a"."b);}st…

Linux Maven 安装与配置

目录 Maven 下载 解压缩下载的文件 移动Maven文件夹 配置环境变量 验证安装 注意 Maven 下载 官方地址 Maven – Download Apache Maven&#xff0c;下载完成后&#xff0c;解压到合适的位置即可&#xff1b; 解压缩下载的文件 解压缩下载的文件&#xff1a; 使用以下命…

华为新版ENSP PRO模拟器测评:性能表现与功能扩展一览

一、引言 在网络领域不断涌现的新技术和复杂的网络拓扑要求&#xff0c;推动了网络设备模拟器的持续发展和创新。华为作为一家领先的通信技术解决方案提供商&#xff0c;不断致力于为网络工程师和技术从业人员提供更优秀的仿真环境。最近&#xff0c;华为推出了ensp pro模拟器的…

【Linux】从0到1实现一个进度条小程序

个人主页&#xff1a;&#x1f35d;在肯德基吃麻辣烫 我的gitee&#xff1a;gitee仓库 分享一句喜欢的话&#xff1a;热烈的火焰&#xff0c;冰封在最沉默的火山深处 文章目录 前言一、理解回车 \r 和换行 \n二、初步认识缓冲区1. 认识第一个函数&#xff1a;sleep2.观察缓冲区…

爬虫015_python异常_页面结构介绍_爬虫概念介绍---python工作笔记034

来看python中的异常 可以看到不做异常处理因为没有这个文件所以报错了 来看一下异常的写法

基于PyTorch的图像识别

前言 图像识别是计算机视觉领域的一个重要方向&#xff0c;具有广泛的应用场景&#xff0c;如医学影像诊断、智能驾驶、安防监控等。在本项目中&#xff0c;我们将使用PyTorch来开发一个基于卷积神经网络的图像识别模型&#xff0c;用来识别图像中的物体。下面是要识别的四种物…

FreeRTOS( 任务与中断优先级,临界保护)

资料来源于硬件家园&#xff1a;资料汇总 - FreeRTOS实时操作系统课程(多任务管理) 目录 一、中断优先级 1、NVIC基础知识 2、FreeRTOS配置NVIC 3、SVC、PendSV、Systick中断 4、不受FreeRTOS管理的中断 5、STM32CubeMX配置 二、任务优先级 1、任务优先级说明 2、任务…

记录--说一说css的font-size: 0

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 平常我们说的font-size&#xff1a;0&#xff1b;就是设置字体大小为0对吧&#xff0c;但是它的用处不仅仅如此哦&#xff0c;它还可以消除子行内元素间额外多余的空白&#xff01; 问题描述&#xff…

5. 服务发现

当主机较少时&#xff0c;在抓取配置中手动列出它们的IP地址和端口是常见的做法&#xff0c;但不适用于较大规模的集群。尤其不适用使用容器和基于云的实例的动态集群&#xff0c;这些实例经常会变化、创建或销毁的情况。 Prometheus通过使用服务发现解决了这个问题&#xff1…

Unity 打造游戏攻击技能架构与设计

一、技能系统的设计 在 MOBA 游戏中&#xff0c;每个英雄角色都会有多个技能&#xff0c;这些技能可以分为普通攻击和技能攻击两种。普通攻击是英雄角色的基本攻击方式&#xff0c;而技能攻击则需要消耗一定的资源&#xff08;如蓝量&#xff09;才能使用。在设计技能系统时&a…

基于Python 简易实现接口测试自动化

目录 实现思路 统筹脚本 请求封装 日志封装 结果比对 结果邮件 用例获取及数据格式化 请求url转换 测试用例excel结构 测试报告 邮件接收结果 资料获取方法 实现思路 使用excel管理用例用例信息&#xff0c;requests模块发送http请求&#xff0c;实现了记录日志&…
最新文章