网页右边,向下滑有目录索引,可以根据标题跳转到你想看的内容 |
---|
如果右边没有就找找左边 |
1.认识时间复杂度
如果一个操作的执行时间不以具体样本量为转移,每次执行时间都是固定时间。称这样的操作为常数时间的操作
- 执行时间固定的操作都是常数时间的操作(比如数组取值,操作时间,不会因为取下标为1或者取下标为10000的值,而发生变化,都是拿到地址直接取)
- 执行时间不固定的操作,都不是常数时间操作(比如一组数排序,你的算法就是要一个一个比较一遍,才能得到正确结果,那么数越多,操作时间越长,它不是固定的)
- 常见常数时间操作
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210718160017796.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dyZF9qYXZh,size_16,color_FFFFFF,t_70)
- 时间复杂度(由流程决定)
- 额外空间复杂度(流程决定)
- 常数项时间(实现细节决定)
就是研究整个算法执行完,常数时间操作的执行次数,以此生成表达式
- 当完成表达式建立,只要把最高阶项留下即可,低阶项都去掉,高阶项的系数也去掉,最终记为O(忽略掉系数的最高阶项)
- 对于为什么要去掉系数,因为我们计算的是一个算法,在任何数据量下,所需时间复杂度,系数的大小没有什么意义
- 一定要按最差情况来,比如插入排序算法,123456,排序,无需交换位置,复杂度为O(N),而最坏情况654321,每次都要排满,为O(N ^ 2),那么我们只考虑最坏的情况,插入排序时间复杂度就是O(N ^ 2)
- 但是如果你是科班出身,时间复杂度计算,其实有3种,最好的情况(符号为Θ),平均情况(符号为Ω),最差情况(符号为O),但是面试问,日常计算等,都是只估计最差情况O
public class Main {
public static void main(String[] args) {
int arr[] = {10,4,5,2,3,7,11,26};
int minIndex;
for(int i = 0;i<arr.length-1;i++){
minIndex = i;
for (int j = i+1;j<arr.length;j++){
if(arr[minIndex]>arr[j]){
minIndex = j;
}
}
if(i != minIndex){
arr[i] = arr[i]^arr[minIndex];
arr[minIndex] = arr[i]^arr[minIndex];
arr[i] = arr[i]^arr[minIndex];
}
}
for(int i = 0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
}
public class Main {
public static void main(String[] args) {
int arr[] = {10,4,5,2,3,7,11,26};
System.out.print("原序列为:"+arr[0]+" ");
for(int i = 1;i<=arr.length-1;i++){
System.out.print(arr[i]+" ");
for (int j = i-1;j >=0 && arr[j]>arr[j+1];j--){
arr[j] = arr[j]^arr[j+1];
arr[j+1] = arr[j]^arr[j+1];
arr[j] = arr[j]^arr[j+1];
}
}
System.out.print("\n排序后为:");
for(int i = 0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
}
- 你要实现一个算法流程,在实现算法流程过程中,你需要开辟一些空间来支持你的算法流程,这就是额外空间复杂度
- 一些必要,或者用户要求的,和实现目标有关,但不是算法流程中需要的不算额外空间,比如:
- 作为输入参数的空间
- 作为输出结果的空间
- 如果你的流程还需要开辟空间才能让你的流程继续下去,这部分也是额外空间
- 如果你的流程只需要开辟有限的几个变量(比如一共就需要3个变量,如果你不确定几个,那就是无限)额外空间复杂度就是O(1)
- 因为时间复杂度这个指标,忽略低阶项和所有常数系数,那么同样时间复杂度的流程,比如选择排序,冒泡排序,插入排序,实际运行时,一样好吗?当然不是
- 如果两个相同时间复杂度的算法,要在时间上拼一个孰高孰低,拼一个优劣,就进入拼常数时间的阶段,简称拼常数项
- 直接申请大样本,实际去跑,然后观察运行时间,不要去理论分析,因为浪费精力,还没什么必要
- 先再时间复杂度上,尽可能低
- 满足时间复杂度后,使用最少的空间,即为最优解
- 常数项比较无关紧要,因为这个因素只决定实现层次的优化和考虑,而和怎么解决整个问题的思想无关
常见时间复杂度,排名由小到大,最好的是第一个,最坏的是最后一个, |
---|
- O(1):常数级别算法
- O(logN):log以2为底的N
- O(N)
- O(N*logN)
- O(N ^ 2),O(N ^ 3)…,O(N ^ K)
- O(2 ^ N),O(3 ^ N)…,O(K ^ N)
- O(N!):N的阶乘
2. 回溯
2.1 迷宫问题
- 小球初始在迷宫左上角,求出小球走到右下角的路径
![在这里插入图片描述](https://img-blog.csdnimg.cn/58c33f9682ce408286256875c93fd590.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 运行效果(1:代表墙,0:代表空地,2:代表小球走的路线)
![在这里插入图片描述](https://img-blog.csdnimg.cn/ac6ec9740ea44e18bfca83bf6ea7726e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_18,color_FFFFFF,t_70,g_se,x_16)
- 代码
public class Test {
public static void main(String[] args) {
int[][] map = new int[8][7];
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
System.out.println("地图的情况");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
setWay(map, 1, 1);
System.out.println("小球走过,并标识过的 地图的情况");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
public static boolean setWay(int[][] map, int i, int j) {
if(map[6][5] == 2) {
return true;
} else {
if(map[i][j] == 0) {
map[i][j] = 2;
if(setWay(map, i+1, j)) {
2.2 n皇后问题
- 我们以8皇后举例(8皇后就是8*8的棋盘摆8个皇后,n皇后就是n * n的棋牌摆n个皇后)
![在这里插入图片描述](https://img-blog.csdnimg.cn/9234ff41a98a489099dfbbc0253a929a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 核心思路
- n代表皇后个数,有n个皇后,就代表棋盘有几行几列,8皇后,就8行8列
- 通过一维数组抽象皇后在棋盘位置,用数组下标代表皇后所在行,用下标对应元素值,代表皇后所在列,比如在下标从0开始的情况下。arr[0]=0 就代表第1个皇后,在第一行第1列,arr[4]=3 代表当前皇后在第5行,第4列
- 判断皇后是否在同一行因为我们按行摆,每个皇后不可能在同一行,无需考虑行
- 判断是否在同一列,我们通过数组元素对应值表示列,所以只需要判断当前皇后和已有皇后的元素值是否相同,就可以知道是否冲突,比如a[0]=1 和 a[3]=1,说明在第一行的皇后和在第4行的皇后都在第一列,冲突
- 判断是否在对角线,根据数学知识,如果两个坐标的横坐标相减的绝对值,等于纵坐标相减的绝对值,说明两个点,构成对角线,比如a[0]=0和a[1]=1,|0-0| = 0 = |1-1|
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/b2035f67e6ea49f9a46e833ad02918fc.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 代码
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
private int arr[];
private ArrayList<List<String>> result = new ArrayList<>();
public List<List<String>> solveQueues(int n){
arr = new int[n];
check(n,0);
return result;
}
public void check(int n,int index){
if(index==n){
ArrayList<String> list = new ArrayList<>();
for(int i = 0;i < n;i++){
StringBuilder stringBuilder = new StringBuilder();
for(int j = 0;j < n;j++){
if(arr[i]==j){
stringBuilder.append("Q");
}else{
stringBuilder.append(".");
}
}
list.add(new String(stringBuilder));
}
result.add(list);
}else{
for(int i = 0;i < n;i++){
arr[index] = i;
if(judge(index)){
check(n,index+1);
}
}
}
}
public boolean judge(int index){
for(int i = 0;i<index;i++){
if(arr[i]==arr[index]||Math.abs(i-index)==Math.abs(arr[i]-arr[index])){
return false;
}
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Main main = new Main();
while (sc.hasNext()) {
int n = sc.nextInt();
List<List<String>> lists;
lists=main.solveQueues(n);
for(List<String> list1 : lists){
System.out.println(list1);
}
}
sc.close();
}
}
3. 排序
![在这里插入图片描述](https://img-blog.csdnimg.cn/7e112c675efb40899d6a14f65d9db5e5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
1. 插入排序
- 就是依次遍历要排序的序列元素,将每个元素插入到合适的位置,就是遍历第一个元素时,保证这个元素有序(因为一个元素本来就有序,所以不用排),遍历第二个元素时,保证前两个元素有序,遍历第三个元素时,保证前三个元素有序,保证有序的过程,其实就是将元素插入到合适位置的过程,所以称之为插入排序。(
注意:每个元素,都是从后往前依次比较,这个问题很大,我们后面说
)
运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/746ddb52aaeb4546bc9aaf5e8947781d.png)
代码
public static void main(String[] args) {
int arr[] = {10,4,5,2,3,7,11,26};
System.out.println("原序列为:"+ Arrays.toString(arr));
for(int i = 1;i<=arr.length-1;i++){
for (int j = i-1;j >=0 && arr[j]>arr[j+1];j--){
arr[j] = arr[j]^arr[j+1];
arr[j+1] = arr[j]^arr[j+1];
arr[j] = arr[j]^arr[j+1];
}
}
System.out.print("排序后为:");
for(int i = 0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
2. 希尔排序
- 插入排序的劣势
- 因为插入排序每个元素都是从后往前比较,那么当插入的数较小,排序(比较)次数明显增多,比如10000个数排序,偏偏最后一个数是最小的,那么单单最后一个数就需要比较9999次,才能完成最后一个数的排序,效率将会十分低下
- 希尔排序
- 希尔与1959年提出的算法,插入排序的改进版,更加高效,少许人称缩小增量排序
- 采用了分治思想,就是分而治之,基本就是按照下标的一定增量分组(比如增量是5,那么下标为0的元素和下标为0+5=5的元素分组,有过有下标为5+5=10的元素,那么0、5、10。3个下标元素分为一组,依次类推),对每组分别使用插入排序,然后增量就会越来越少,当增量为1时,整个文件正好成为一组,最后再执行一次插入排序,即可完成整体排序(增量缩小规则,增量 = 增量/2,初始增量为序列长度,第二次为初始增量算出的增量结果,第三次为第二次算出的增量结果)
![在这里插入图片描述](https://img-blog.csdnimg.cn/5fb64f3cb8c64ea0ae7ff53e11ec2f4c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/460cbb3becf047399c91e4ae277dc0ff.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
希尔排序有两种实现方法,交换法实现希尔排序,思路简单,但速度非常慢,移动法实现希尔排序,思路不容易理解,但速度快 |
---|
- 移动法,就是杜绝没用的交换操作,比如4、5、3、1这4个数,1要进行插入排序,那么我们就先将1这个数保存,然后让3移动到1的位置,5移动到3的位置,4移动到5的位置,最后,让1存储到4这个位置,这样就杜绝了交换操作,效率大大提高
- 80000组数据测试发现(
不使用位运算的前提下
),使用插入排序排80000个数据,花费4秒,使用交换法实现的希尔排序花费了14秒,使用移动法实现的希尔排序花费了4毫秒(1秒=1000毫秒)(下图是使用位运算的消耗时间
)
![在这里插入图片描述](https://img-blog.csdnimg.cn/3f5caf2e02c1474ba11b524579ed09dc.png)
- 运行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/ba0ebcca164e478fb6c6a563206d68b4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 代码
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
public class Test {
public static void main(String[] args) {
int arr[]= {8,9,1,7,2,3,5,4,6,0};
int arr1[]={10,4,5,2,3,7,11,26};
System.out.println("原序列为:"+ Arrays.toString(arr));
shellSort1(arr);
print(arr);
System.out.println("原序列为:"+ Arrays.toString(arr1));
shellSort1(arr1);
print(arr1);
System.out.println("=============================移动法============================");
int arr2[]= {8,9,1,7,2,3,5,4,6,0};
int arr3[]={10,4,5,2,3,7,11,26};
System.out.println("原序列为:"+ Arrays.toString(arr));
shellSort2(arr2);
print(arr2);
System.out.println("原序列为:"+ Arrays.toString(arr1));
shellSort2(arr3);
print(arr3);
}
public static void insertSort(int arr[]){
for(int i = 1;i<=arr.length-1;i++){
for (int j = i-1;j >=0 && arr[j]>arr[j+1];j--){
arr[j] = arr[j]^arr[j+1];
arr[j+1] = arr[j]^arr[j+1];
arr[j] = arr[j]^arr[j+1];
}
}
}
public static void print(int arr[]){
System.out.print("排序后为:");
for(int i = 0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void shellSort1(int arr[]){
int gap = arr.length/2;
int index = 1;
while(gap>1){
for(int i = gap;i < arr.length;i++){
for(int j = i-gap;j >= 0 && arr[j] > arr[j+gap];j-=gap){
arr[j]=arr[j]^arr[j+gap];
arr[j+gap] = arr[j]^arr[j+gap];
arr[j] = arr[j]^arr[j+gap];
}
}
gap /= 2;
System.out.print("第"+index+"轮希尔排序结果为:");
index++;
print(arr);
}
insertSort(arr);
}
public static void shellSort2(int arr[]){
int gap = arr.length/2;
int index = 1;
while(gap>1){
for(int i = gap;i < arr.length;i++){
if(arr[i-gap] > arr[i]){
int temp = arr[i];
int j ;
for(j = i -gap;j >= 0 && temp < arr[j];j-=gap){
arr[j+gap] = arr[j];
}
arr[j+gap] = temp;
}
}
gap /= 2;
System.out.print("第"+index+"轮希尔排序结果为:");
index++;
print(arr);
}
insertSort(arr);
}
}
3. 快速排序
- 快速排序是冒泡排序的改进版。依然采用分治思想,先通过一趟排序将数据分割成两部分,其中一部分的所有数据要比另一部分的所有数据要小,然后再按此方法,对这两部分数据分别进行快速排序,整个过程可以用递归实现。
- 就是拿到一组数据,先找一个数,作为中间数(你可以直接拿序列中间的数,或者拿最后一个数,或者第一个数),然后依据此数,先排成两组,不用有序,就是比中间数大的,分为一组,比中间数小的,分为一组,中间数最后归哪组,随意,不归任何一组也可以。然后分别对这两组再进行快速排序(就像冒泡一样,每次把中间数冒出去)
![在这里插入图片描述](https://img-blog.csdnimg.cn/16e2e834f3c141069a063785b2e96525.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 实现思路与运行效果
- 首先,我们选择中间数(基准数),我们这里指定序列的第一个数是基准数,将基准数保存。然后创建两个下标,分别位于序列的最左面和最右面,依次从右边找大于中间数的值,将其放在左边,左边找小于中间数的值,放在右边,最后,左边下标最后的位置,就是中间,将中间数放进去即可
![在这里插入图片描述](https://img-blog.csdnimg.cn/08222fda826c41d9a626173323d928eb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 代码
import java.util.Arrays;
import java.util.Date;
public class Test {
public static void main(String[] args) {
int arr[]= {8,9,1,7,2,3,5,4,6,0};
int arr1[]={-9,78,0,23,-567,70, -1,900, 4561};
System.out.println("原序列为:"+ Arrays.toString(arr));
quickSort(arr,0,arr.length-1);
print(arr);
System.out.println("原序列为:"+ Arrays.toString(arr1));
quickSort(arr1,0,arr1.length-1);
print(arr1);
System.out.println("=================================80000组数据测试======================");
int array[] = new int[80000];
for (int i = 0 ;i<80000;i++){
array[i] = (int) (Math.random()*800000);
}
long start = new Date().getTime();
quickSort(array,0,array.length-1);
long end = new Date().getTime();
System.out.println("消耗时间"+(end-start));
}
public static void quickSort(int[] a, int left, int right) {
if (left < right) {
int l = left;
int r = right;
int temp = a[l];
while (l < r) {
while(l < r && a[r] > temp)
r--;
if(l < r)
a[l++] = a[r];
while(l < r && a[l] < temp)
l++;
if(l < r)
a[r--] = a[l];
}
a[l] = temp;
quickSort(a, left, l-1);
quickSort(a, l+1,right);
}
}
public static void print(int arr[]){
System.out.print("排序后为:");
for(int i = 0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
}
4. 归并排序
- 利用归并思想实现的排序方式,采用经典分治策略,将问题分成多个小问题,依次求解,最后将小问题的结果修补到一起。也就是分而治之
![在这里插入图片描述](https://img-blog.csdnimg.cn/28034f5a2a694f8db64e6e496761a792.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/4b2c440871424b0fbb9c8f381941143f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/8cda97a9ee594fc99840b9d410d16a3b.png)
- 代码
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int arr[] = {8,4,5,7,1,3,6,2};
int temp[] = new int[arr.length];
System.out.println("原数组为:"+Arrays.toString(arr));
mergeSort(arr,0,arr.length-1,temp);
System.out.println("排序过后:"+Arrays.toString(arr));
}
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
}
public static void merge(int arr[],int left,int mid,int right,int[] temp){
int i = left;
int j = mid + 1;
int t = 0;
while(i <= mid && j <= right){
if(arr[i] <= arr[j]){
temp[t] = arr[i];
t += 1;
i += 1;
}else{
temp[t] = arr[j];
t += 1;
j += 1;
}
}
while(i <= mid){
temp[t] = arr[i];
i++;
t++;
}
while(j <= right){
temp[t] = arr[j];
j++;
t++;
}
t = 0;
int tempLeft = left;
while(tempLeft <= right){
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
5. 基数排序(桶排序)
- 属于分配时排序,又称桶子法,顾名思义,它通过键值的各个位的值,将要排序的元素分配至某些桶中,达到排序的作用
- 基数排序法是属于稳定的排序,并且效率很高
- 桶排序的扩展
- 1887年赫尔曼·何乐礼发明的。将整数按位数切割成不同数字,然后按每个位数分别比较
- 基本思想
- 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后从低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成后,数列就有序了
![在这里插入图片描述](https://img-blog.csdnimg.cn/85e8de765b5743eab3425037ddb6fd56.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/e1e41f6614b748adb2cb2cff07286177.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/1323878249ff4c849f44c7437ae4811c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 实现思路
- 首先确定数组数据最大位数,比如最大数是1900,那么最大位数就是4位
- 创建10个桶,就是一个二维数组,里面有10个一维数组,每个一维数组代表一个桶,分别代表0-9
- 创建桶的有效数据记录,就是一个一维数组,记录每个桶的有效数据个数
- 根据最大位数,进行遍历,如果要从小到大排序,那么就需要先比较个位,根据个位的数字,放到对应桶中,然后让桶有效数据记录自增
- 然后依次遍历桶,将桶中数据,放到原数组,然后将桶有效数据记录清零,然后比较十位,依次类推
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/ee3c245a2cde4be4bc90c3dd804e61fc.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 代码(没有做负数的处理)
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int arr[] = {53,3,542,748,14,214};
radixSort(arr);
int arr1[] = {9,7,13,0,5,0,14,21,197};
radixSort(arr1);
}
public static void radixSort(int arr[]){
int maxLength = (Arrays.stream(arr).max().getAsInt()+"").length();
int bucket[][] = new int[10][arr.length];
int bucketEleCount[] = new int[10];
for(int i = 0;i < maxLength;i++){
for( int j = 0;j<arr.length;j++){
int num = arr[j] / (int)Math.pow(10,i) % 10;
bucket[num][bucketEleCount[num]] = arr[j];
bucketEleCount[num]++;
}
int index = 0;
for(int j = 0;j<bucketEleCount.length;j++){
if(bucketEleCount[j]!=0){
for(int z = 0;z < bucketEleCount[j];z++){
arr[index++] = bucket[j][z];
}
}
bucketEleCount[j]=0;
}
System.out.println("第"+(i+1)+"轮排序结果"+Arrays.toString(arr));
}
}
}
6. 堆排序
- 堆排序是利用堆(数据结构)设计的排序算法,属于选择排序,最坏,最好,平均时间复杂度均为O(n logn),不稳定排序
- 堆是具有以下性质的完全二叉树:
- 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆,
- 没有要求结点的左孩子的值和右孩子的值的大小关系。
- 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
![在这里插入图片描述](https://img-blog.csdnimg.cn/fe2f1d368f0340f483835bc0a6568ca0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/b9a400aba82242fab421a30297a71086.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 将待排序序列构成一个大顶堆
- 因为大顶堆最大值就是根节点,此时将其与末尾元素交换,末尾值就是最大值
- 然后将剩下的n-1个元素构成大顶堆,重复上面操作,每次构建都可以获取一个最大值,最终得到有序序列
- 用到的公式(二叉树的基本公式)
- arr[i]<=arr[2i+1] && arr[i] <= arr[2i+2] 小顶堆条件,当前非叶子节点arr[i],左节点和右节点,都小于它,大顶堆正好相反,左右都大于它本身
- 第一个非叶子节点arr.length/2-1
- 当前节点的左子节点,i2+1,当前节点右子节点i2+2
- 基本实现思路
- 构建大顶堆,从二叉树下面往上构建
- 每构建一次,交换堆顶元素到堆最后面,这样整个数组依次的就形成了,最后一个是最大值,倒数第二个是倒数第二大的值
- 然后去掉数组后面内些排好的,再次构建堆,再交换,依次类推
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int arr[] = {4,6,8,5,9};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void heapSort(int arr[]){
for(int i = arr.length /2 -1;i >= 0 ;i--){
adjustHeap(arr,i,arr.length);
}
for(int i = arr.length-1;i>0;i--){
arr[i]=arr[i]^arr[0];
arr[0]=arr[i]^arr[0];
arr[i]=arr[i]^arr[0];
adjustHeap(arr,0,i);
}
}
public static void adjustHeap(int arr[],int i ,int length){
int temp = arr[i];
for(int k = i*2+1;k<length;k=k*2+1){
if(k+1 < length && arr[k] < arr[k+1]){
k++;
}
if(arr[k]>temp){
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;
}
}
4. 查找
- java中常用查找有:顺序(线性)查找、二分/折半查找、插值查找、斐波那契查找
1. 二分查找(递归)
- 二分查找的前提是,序列有序
- 首先我们要查找一个数,先找到序列中间的数,进行比较,如果比中间数大,那么我们从右边继续找,如果比中间数小,那么左边找
- 比如比中间数大,那么去右边找,此时,需要重新找右边这一半序列的中间数,然后继续判断,依次类推
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/dcc13ef07e244479949a1599f2982ea0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
int arr[] = { 1, 8, 10, 89,1000,1000, 1234 };
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11, 12, 13,14,15,16,17,18,19,20 };
System.out.println("序列为:"+ Arrays.toString(arr1));
int resIndex = binarySearch(arr1, 0, arr1.length - 1, 8);
System.out.println("元素8的下标为resIndex=" + resIndex);
System.out.println("序列为:"+Arrays.toString(arr));
List<Integer> resIndexList = binarySearch2(arr, 0, arr.length - 1, 1000);
System.out.println("元素1000的下标为resIndexList=" + resIndexList);
}
public static int binarySearch(int[] arr, int left, int right, int findVal) {
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
System.out.println("hello~");
if (left > right) {
return new ArrayList<Integer>();
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearch2(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch2(arr, left, mid - 1, findVal);
} else {
List<Integer> resIndexlist = new ArrayList<Integer>();
int temp = mid - 1;
while(true) {
if (temp < 0 || arr[temp] != findVal) {
break;
}
resIndexlist.add(temp);
temp -= 1;
}
resIndexlist.add(mid);
temp = mid + 1;
while(true) {
if (temp > arr.length - 1 || arr[temp] != findVal) {
break;
}
resIndexlist.add(temp);
temp += 1;
}
return resIndexlist;
}
}
}
2. 插值查找
- 二分查找改进版,因为如果我们要查找一个有序序列的第一个元素,那么它需要从中间一次一次折半,反而耗费大量时间,而插值查找与二分查找不同的是,每次从自适应mid处开始查
- 二分查找公式为mid = (left+right)/2,而插值查找公式为mid = left + (key - a[left])/(a[right]-a[left])*(high-low)
![在这里插入图片描述](https://img-blog.csdnimg.cn/cad790172a0e425d8512e04b548fb8ee.png)
- 代码(和二分查找一样,就换了个公式,然后退出递归的条件变一下就可以了)
public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
System.out.println("插值查找次数~~");
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
int midVal = arr[mid];
if (findVal > midVal) {
return insertValueSearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return insertValueSearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
3. 斐波那契查找
- 利用斐波那契数列查找,查找的时候,需要先确定数组最大下标,如果比相应斐波那契数列的小,那么需要创建一个新的数组,来填充数据,和斐波那契数列配合
- 实际上查就是在新数组中查找,不断的通过斐波那契数列来查
- 核心思路,就是如何和斐波那契配合,所以也无需过多解释,直接看代码理解
import java.util.Arrays;
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int [] arr = {1,8, 10, 89, 1000, 1234};
System.out.println("index=" + fibSearch(arr, 189));
}
public static int[] fib() {
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < maxSize; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
public static int fibSearch(int[] a, int key) {
int low = 0;
int high = a.length - 1;
int k = 0;
int mid = 0;
int f[] = fib();
while(high > f[k] - 1) {
k++;
}
int[] temp = Arrays.copyOf(a, f[k]);
for(int i = high + 1; i < temp.length; i++) {
temp[i] = a[high];
}
while (low <= high) {
mid = low + f[k - 1] - 1;
if(key < temp[mid]) {
high = mid - 1;
k--;
} else if ( key > temp[mid]) {
low = mid + 1;
k -= 2;
} else {
if(mid <= high) {
return mid;
} else {
return high;
}
}
}
return -1;
}
}
4. 二分查找(非递归版本)
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/925c36a584ab420793414bb19606473f.png)
- 代码
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] arr = {1,3,8,10,11,67,100};
System.out.println("数组元素为:"+Arrays.toString(arr));
int index = binarySearch(arr,10);
System.out.println("查找元素10的下标为"+index);
}
public static int binarySearch(int[] arr,int target){
int left = 0;
int right = arr.length - 1;
while(left <= right){
int mid = (left + right)/2;
if(arr[mid]==target){
return mid;
}else if(arr[mid]>target){
right = mid-1;
}else{
left = mid+1;
}
}
return -1;
}
}
5. 分治算法
1. 汉诺塔
![在这里插入图片描述](https://img-blog.csdnimg.cn/2c3cb01044b2435ea71e10033cf7e822.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/eea96426a79046ed86df853fe62a2446.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_10,color_FFFFFF,t_70,g_se,x_16)
- 代码
public class Test {
public static void main(String[] args) {
System.out.println("5个盘子的移动步骤");
hanoiTower(5,'A','B','C');
}
public static void hanoiTower(int num,char a,char b,char c){
if(num == 1)
System.out.println("第1个盘从 "+a+"->"+c);
else{
hanoiTower(num-1,a,c,b);
System.out.println("第"+num+"个盘从 "+a+"->"+c);
hanoiTower(num-1,b,a,c);
}
}
}
6. 动态规划
![在这里插入图片描述](https://img-blog.csdnimg.cn/c7dbe0f344ea4568aac1315757e9dc16.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
任何动态规划的动态转移方程,都是暴力递归过程的推导 |
---|
只要暴力递归有重复计算,那么我们就需要动态规划,避免掉这些重复计算 |
---|
所有暴力递归均为思路,不代表可以解题,因为暴力递归及其消耗内存,很可能内存溢出,只是为了让大家了解,如果通过思考起来更简单的暴力递归,推导动态规划 |
---|
1. 背包问题
![在这里插入图片描述](https://img-blog.csdnimg.cn/62a91d8551be492ca37822bff55d24a7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 有一堆物品,每件物品都有自己的
重量w
和价值v
- 我们有一个背包,
背包最大装载重量为rest
- 求背包如何装载物品,
让背包中物品价值最大
public class Main {
public static int process(int[] w,int[] v,int index,int rest){
if(rest < 0) return -1;
if(index == w.length) return 0;
int p1 = process(w,v,index + 1,rest);
int p2 = -1;
int p2Next = process(w,v,index + 1,rest - w[index]);
if(p2Next != -1){
p2 = v[index] + p2Next;
}
return Math.max(p1,p2);
}
public static void main(String[] args) {
int[] w = {12,13,15,27,36,48};
int[] v = {10,5,27,58,100,100};
System.out.println(process(w,v,0,50));
}
}
- 首先,我们发现,暴力递归过程中,始终发生变化的值只有两个,当前物品下标index和背包剩余空间rest
- 假设,我们再递归中,多次遇到当前物品下标是5,背包剩余空间是10的情况,那么我们就在第一次遇见这个情况时,将这个过程f(5,10)=x,保存起来,当第二次遇到,发现f(5,10)=x,已经有做过缓存,那么我们直接获取x结果,无需再次进入递归
- 所以我们用一个二维表来存储这些结果,想象用index当x轴坐标,用rest当y轴坐标(如果用数组,就是arr[ index ] [ rest ])
- 那么我们需要规定二维表的大小,我们发现如果想要完全存储结果,只需要将二维表定义成arr[ 物品数量 ] [背包最大空间],就完全可以存储所有结果
- 但是我们保留递归中,递归退出条件为index == w.length;这说明我们除了物品数量外,还用到了多于物品数量的一个值,来当做我们退出递归的条件,所以我们也要在二维表中存储,那么定义为arr[ 物品数量+1 ][背包最大空间]
- 同理,我们需要为背包最大空间这里,考虑0的情况,所以我们要定义为arr[ 物品数量+1 ] [ 背包最大空间 + 1]
- 好的动态规划,就是用已知信息,绘制二维表(当然根据不同问题,可能是一维表,那就更简单了),然后将结果返回,也就是不需要递归了,最常见的就是用两个for循环直接构建二维表,
- 我们已知,最终背包容量为0的情况,和index最后一行是0的情况,所以我们可以自下而上,利用已知的为0情况,构建出2维表
下面的代码,细心点你就会发现,暴力递归中,所有return的值,就是你动态规划时要往表结构中添加的值 ,而我们暴力递归从上到下,从右到左 ,生成的结构,恰好最下面和最左面是最终的固定值0 .所以我们动态规划时,就从下到上,从左到右利用已知的条件0生成二维表即可 |
---|
public class Main {
public static int process(int[] w,int[] v,int bag){
int N = w.length;
int[][] dp = new int[N+1][bag+1];
for(int index = N - 1;index >= 0 ;index--){
for(int rest = 1;rest <= bag;rest++){
dp[index][rest] = dp[index + 1][rest];
if(rest >= w[index]){
dp[index][rest] = Math.max(dp[index][rest],v[index]+dp[index+1][rest-w[index]]);
}
}
}
return dp[0][bag];
}
public static void main(String[] args) {
int[] w = {12,13,15,27,36,48};
int[] v = {10,5,27,58,100,100};
System.out.println(process(w,v,50));
}
}
2. 找钱问题
- 给定arr数组,所有值都为正数且不重复,代表一种面值的货币,不规定货币的数量
- 用变量aim,代表要找的钱数
- 求共有多少种找钱方式,利用不同货币数量,找同样的钱
- 一看题就可以猜出
二维表纵坐标是硬币面值,横坐标是张数,值是钱的数量
- 具体分析
- 首先这道题,我们知道要组成钱(假设为rest)的大小,但是货币又没有要求
- 那么最好的办法就是,我们每次递归都用rest-当前面值,当rest==0时,表示达到要求
下面列出如何一步一步从暴力递归->记忆化搜索->精细化动态规划。以及3种算法所消耗时间 |
---|
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021061317215056.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dyZF9qYXZh,size_16,color_FFFFFF,t_70)
package com.company;
import java.util.Date;
public class Main {
public static int dfs(int arr[],int index,int rest){
if(rest < 0) return 0;
if(index == arr.length) return rest==0?1:0;
int ways = 0;
for(int i =0;(arr[index]*i)<=rest;i++){
ways+=dfs(arr,index+1,rest-(arr[index]*i));
}
return ways;
}
public static int dp1(int arr[],int index,int rest,int dp[][]){
if(dp[index][rest]!=-1) return dp[index][rest];
if(index == arr.length) {
dp[index][rest]=(rest==0?1:0);
return dp[index][rest];
}
int ways = 0;
for(int i =0;(arr[index]*i)<=rest;i++){
ways+=dp1(arr,index+1,rest-(arr[index]*i),dp);
}
dp[index][rest]=ways;
return dp[index][rest];
}
public static int dp2(int arr[],int aim){
int N = arr.length;
int[][] dp2 = new int[N+1][aim+1];
dp2[N][0]=1;
for(int index=N-1;index>=0;index--){
for(int rest = 0;rest<=aim;rest++){
dp2[index][rest] = dp2[index+1][rest];
if(rest - arr[index]>=0){
dp2[index][rest] += dp2[index][rest-arr[index]];
}
}
}
return dp2[0][aim];
}
public static void main(String[] args) {
int arr[] = {5,10,50,100};
int aim = 9900;
long start = new Date().getTime();
System.out.println(dfs(arr,0,aim));
long end = new Date().getTime();
System.out.println("所用时间(单位毫秒)"+(end-start));
int dp[][] = new int[arr.length+1][aim+1];
for(int i = 0 ;i<dp.length;i++){
for (int j =0;j<dp[0].length;j++){
dp[i][j]=-1;
}
}
long start1 = new Date().getTime();
System.out.println(dp1(arr,0,aim,dp));
long end1 = new Date().getTime();
System.out.println("所用时间(单位毫秒)"+(end1-start1));
long start2 = new Date().getTime();
System.out.println(dp2(arr,aim));
long end2 = new Date().getTime();
System.out.println("所用时间(单位毫秒)"+(end2-start2));
}
}
7. KMP算法
- 解决模式串在文本串是否出现过,如果出现过,获取最早出现的位置的经典算法
- 常用于在一个文本串S内查找一个模式串P出现的位置,此算法由Donald Knuth、Vaughan Pratt、James H.Morris三人于1977年联合发表,故取三人姓氏命名此算法为Knuth-Morris-Pratt,简称KMP算法
- 利用之前判断过的信息,通过一个next数组,保存模式串中前后最常公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去大量计算时间
![在这里插入图片描述](https://img-blog.csdnimg.cn/f731d7f3eaee4901856d59df5c43c3e9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/1cb39c8ed6454d3490f10048965aac49.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/634b62e564e14418bda17031a48fc891.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/f6a97d5443af4636ad925b43da450343.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/f02960c91e2d48b6958ba33b23983de0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/8a67c0bdfec049a98ddbe917481bf7fa.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/58cc8ab6fa104dee8197e457a41b3f7b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/6057abb9885642e6b3b7a8a612d9284a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/865e6880cf3b4e8d89f7af25a6197b96.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/fb028efe7bc242218f4dbeb54b52d7c1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/23c48c0329174d45b31b93c2abc2f22d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/7f3123464b5b485ea8c572369fc328b1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/d8c3a268e57e4c36ad4a8d952ec8b334.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/1405e4f92cb74d998392b00d5f22df9f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/9a23c2450636480b96e019cef9e071e1.png)
- 代码
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int[] next = kmpNext("ABCDABD");
System.out.println("next=" + Arrays.toString(next));
int index = kmpSearch(str1, str2, next);
System.out.println("index=" + index);
}
public static int[] kmpNext(String dest) {
int[] next = new int[dest.length()];
next[0] = 0;
for(int i = 1, j = 0; i < dest.length(); i++) {
while(j > 0 && dest.charAt(i) != dest.charAt(j)) {
j = next[j-1];
}
if(dest.charAt(i) == dest.charAt(j)) {
j++;
}
next[i] = j;
}
return next;
}
public static int kmpSearch(String str1, String str2, int[] next) {
for(int i = 0, j = 0; i < str1.length(); i++) {
while( j > 0 && str1.charAt(i) != str2.charAt(j)) {
j = next[j-1];
}
if(str1.charAt(i) == str2.charAt(j)) {
j++;
}
if(j == str2.length()) {
return i - j + 1;
}
}
return -1;
}
}
8. 贪心算法
![·](https://img-blog.csdnimg.cn/5210852170dc4f4ab9713662db9160c9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 贪心算法,就是找最优解,没有最优,只有更优
- 一般都是将数组、序列,按规则排序,然后递归或循环遍历,找到最优解
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/f3f3fc47cf5249cf967ead60da860f0a.png)
- 代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
HashMap<String,HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();
HashSet<String> hashSet1 = new HashSet<String>();
hashSet1.add("北京");hashSet1.add("上海");hashSet1.add("天津");
HashSet<String> hashSet2 = new HashSet<String>();
hashSet2.add("广州");hashSet2.add("北京");hashSet2.add("深圳");
HashSet<String> hashSet3 = new HashSet<String>();
hashSet3.add("成都");hashSet3.add("上海");hashSet3.add("杭州");
HashSet<String> hashSet4 = new HashSet<String>();
hashSet4.add("上海");hashSet4.add("天津");
HashSet<String> hashSet5 = new HashSet<String>();
hashSet5.add("杭州");hashSet5.add("大连");
broadcasts.put("K1", hashSet1);broadcasts.put("K2", hashSet2);broadcasts.put("K3", hashSet3);broadcasts.put("K4", hashSet4);broadcasts.put("K5", hashSet5);
HashSet<String> allAreas = new HashSet<String>();
allAreas.add("北京");allAreas.add("上海");allAreas.add("天津");allAreas.add("广州");allAreas.add("深圳");allAreas.add("成都");allAreas.add("杭州");allAreas.add("大连");
ArrayList<String> selects = new ArrayList<String>();
HashSet<String> tempSet = new HashSet<String>();
String maxKey = null;
while(allAreas.size() != 0) {
maxKey = null;
for(String key : broadcasts.keySet()) {
tempSet.clear();
HashSet<String> areas = broadcasts.get(key);
tempSet.addAll(areas);
tempSet.retainAll(allAreas);
if(tempSet.size() > 0 &&
(maxKey == null || tempSet.size() >broadcasts.get(maxKey).size())){
maxKey = key;
}
}
if(maxKey != null) {
selects.add(maxKey);
allAreas.removeAll(broadcasts.get(maxKey));
}
}
System.out.println("得到的选择结果是" + selects);
}
}
9.最小生成树算法
1. 普里姆(Prim)算法
![在这里插入图片描述](https://img-blog.csdnimg.cn/99c002a93e6248c7bd74b9345bffcd5b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/ead8c01b466f471391a5b979a8c449f2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 修路问题的本质就是最小生成树(Minimum Cost Spanning Tree)问题,简称MST。
- 给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这就叫最小生成树
- N个顶点,一定有N-1条边
- 并且连通了全部顶点
- 这N-1条边,都在完全图中
- 求最小生成树的算法主要有普里姆算法和克鲁斯卡尔算法
- 求最小生成树,也就是在包含n个顶点的连通图中,找出只有n-1条边就连通了所有顶点的连通子图,也就是极小连通子图
- 算法思路如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/8b5502bb898c4d469944362465dea2d3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 设G=(V,E)是连通图,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合
- 若从顶点a开始构建最小生成树,则从集合V中取出顶点a放入集合U中,标记顶点a的visited[a]=1表示已经访问过,如果不是第一次构建,那么选择一个没有访问过的,然后找权值最小的边
- 若集合V中的顶点b与集合U中顶点a之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点a加入集合U中,将边(a,b)加入集合D中,标记visited[b]=1表示b这个顶点也访问过了
- 重复步骤2,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边
package com.yzpnb.test;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
char[] data = new char[]{'A','B','C','D','E','F','G'};
int verxs = data.length;
int [][]weight=new int[][]{
{10000,5,7,10000,10000,10000,2},
{5,10000,10000,9,10000,10000,3},
{7,10000,10000,10000,8,10000,10000},
{10000,9,10000,10000,10000,4,10000},
{10000,10000,8,10000,10000,5,4},
{10000,10000,10000,4,5,10000,6},
{2,3,10000,10000,4,6,10000},};
MGraph graph = new MGraph(verxs);
MinTree minTree = new MinTree();
minTree.createGraph(graph, verxs, data, weight);
minTree.showGraph(graph);
minTree.prim(graph, 1);
}
}
class MinTree {
public void createGraph(MGraph graph, int verxs, char data[], int[][] weight) {
int i, j;
for(i = 0; i < verxs; i++) {
graph.data[i] = data[i];
for(j = 0; j < verxs; j++) {
graph.weight[i][j] = weight[i][j];
}
}
}
public void showGraph(MGraph graph) {
for(int[] link: graph.weight) {
System.out.println(Arrays.toString(link));
}
}
public void prim(MGraph graph, int v) {
int visited[] = new int[graph.verxs];
visited[v] = 1;
int h1 = -1;
int h2 = -1;
int minWeight = 10000;
for(int k = 1; k < graph.verxs; k++) {
for(int i = 0; i < graph.verxs; i++) {
for(int j = 0; j< graph.verxs;j++) {
if(visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);
visited[h2] = 1;
minWeight = 10000;
}
}
}
class MGraph{
int verxs;
char[] data;
int[][] weight;
public MGraph(int verxs) {
this.verxs = verxs;
data = new char[verxs];
weight = new int[verxs][verxs];
}
}
2. 克鲁斯卡尔(Kruskal)算法
![在这里插入图片描述](https://img-blog.csdnimg.cn/c474f2c7c1b849d4a93f805f74708acb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 克鲁斯卡尔(Kruskal)算法,就是用来求加权连通图的最小生成树的算法
- 按照权值从小到大的顺序选择n-1条边,保证n-1条边不构成回路
- 首先构造一个只含n个顶点的森林,然后依权值从小到大从连通图中选择边加入到森林中,并使森林中不产生回路,直到森林变成一课树为止
![在这里插入图片描述](https://img-blog.csdnimg.cn/dd35f0f2b2fb49e5887735c184c9fe87.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/6966b065c6024909a9d71e3b1f220b7a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/3d48f094232c45899aed1baec1406a8e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 在将<E,F> <C,D> <D,E>加入到最小生成树R中之后,这几条边的顶点就都有了终点
- C的终点是F。
- D的终点是F。
- E的终点是F。
- F的终点是F。
- 终点
- 就是将加入到最小生成树R中的所有顶点按照从小到大的顺序排列好之后;某个顶点的终点就是"与它连通的最大顶点"
- 因此,接下来,虽然<C,E>是权值最小的边。但是C和E的终点都是F,即它们的终点相同,因此,将<C,E>加入最小生成树的话,会形成回路。这就是判断回路的方式
- 我们加入的边的两个顶点不能都指向同一个终点,否则将构成回路
import java.util.Arrays;
public class Test {
private static final int INF = Integer.MAX_VALUE;
public static void main(String[] args) {
char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int matrix[][] = {
/*B*
{ 0, 12, INF, INF, INF, 16, 14},
{ 12, 0, 10, INF, INF, 7, INF},
{ INF, 10, 0, 3, 5, 6, INF},
{ INF, INF, 3, 0, 4, INF, INF},
{ INF, INF, 5, 4, 0, 2, 8},
{ 16, 7, 6, INF, 2, 0, 9},
{ 14, INF, INF, INF, 8, 9, 0}};
KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
kruskalCase.print();
kruskalCase.kruskal();
}
}
class KruskalCase{
private int edgeNum;
private char[] vertexs;
private int[][] matrix;
private static final int INF = Integer.MAX_VALUE;
public KruskalCase(char[] vertexs, int[][] matrix) {
int vlen = vertexs.length;
this.vertexs = new char[vlen];
for(int i = 0; i < vertexs.length; i++) {
this.vertexs[i] = vertexs[i];
}
this.matrix = new int[vlen][vlen];
for(int i = 0; i < vlen; i++) {
for(int j= 0; j < vlen; j++) {
this.matrix[i][j] = matrix[i][j];
}
}
for(int i =0; i < vlen; i++) {
for(int j = i+1; j < vlen; j++) {
if(this.matrix[i][j] != INF) {
edgeNum++;
}
}
}
}
public void kruskal() {
int index = 0;
int[] ends = new int[edgeNum];
EData[] rets = new EData[edgeNum];
EData[] edges = getEdges();
System.out.println("图的边的集合=" + Arrays.toString(edges) + " 共"+ edges.length);
sortEdges(edges);
for(int i=0; i < edgeNum; i++) {
int p1 = getPosition(edges[i].start);
int p2 = getPosition(edges[i].end);
int m = getEnd(ends, p1);
int n = getEnd(ends, p2);
if(m != n) {
ends[m] = n;
rets[index++] = edges[i];
}
}
System.out.println("最小生成树为");
for(int i = 0; i < index; i++) {
System.out.println(rets[i]);
}
}
public void print() {
System.out.println("邻接矩阵为: \n");
for(int i = 0; i < vertexs.length; i++) {
for(int j=0; j < vertexs.length; j++) {
System.out.printf("%12d", matrix[i][j]);
}
System.out.println();
}
}
private void sortEdges(EData[] edges) {
for(int i = 0; i < edges.length - 1; i++) {
for(int j = 0; j < edges.length - 1 - i; j++) {
if(edges[j].weight > edges[j+1].weight) {
EData tmp = edges[j];
edges[j] = edges[j+1];
edges[j+1] = tmp;
}
}
}
}
private int getPosition(char ch) {
for(int i = 0; i < vertexs.length; i++) {
if(vertexs[i] == ch) {
return i;
}
}
return -1;
}
private EData[] getEdges() {
int index = 0;
EData[] edges = new EData[edgeNum];
for(int i = 0; i < vertexs.length; i++) {
for(int j=i+1; j <vertexs.length; j++) {
if(matrix[i][j] != INF) {
edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);
}
}
}
return edges;
}
private int getEnd(int[] ends, int i) {
while(ends[i] != 0) {
i = ends[i];
}
return i;
}
}
class EData {
char start;
char end;
int weight;
public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return "EData [<" + start + ", " + end + ">= " + weight + "]";
}
}
10. 迪杰斯特拉算法(图数据结构的算法)
![在这里插入图片描述](https://img-blog.csdnimg.cn/d5de15a20a0b44b480359ac980535a5f.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_15,color_FFFFFF,t_70,g_se,x_16)
- 假设A、B、C、D、E、F、G,代表6个村庄,现在有6个邮差,从G出发,分别到ABCDEF六个村庄
- 各个村庄距离用边线表示权,例如A-B距离为5公里
- 如何计算出G村庄到其它各个村庄的最短距离?从其它点出发到各个点最短距离又是多少?
- 用于计算一个结点到其它结点的最短路径,主要特点是以起始点为中心向外层层扩展(
广度优先
搜索思想),直到扩展到终点为止 - 首先我们需要依次遍历所有顶点,第一个是必须是出发顶点,依次将可直接到达的顶点距离记录下来。不可达设置为65535
- 然后遍历剩下的顶点(贪心思想,每次拿到没访问过的,距离出发顶点最短的顶点),将前驱设置好,然后进行判断,如果它到达某个顶点,比记录的距离小,则更新(前驱的距离+自己到目标顶点的距离)
- 指定出发顶点G,定义
顶点集合V
{A,B,C,D,E,F,G},定义G到V中各顶点距离构成的距离集合Dis
{d1,d2,d3,d4,di…},Dis集合中记录G点到各顶点距离,自身记作0 - 从Dis中选择最小di,移出Dis集合,同时移出V集合中对应顶点vi,比如A。此时G到A为最短路径
- 更新Dis集合,比较G到V集合中顶点的距离值,与G点通过某个点(假设vi)到目标点(假设P)的距离值(经过某个顶点,到达目标点),保留值小的,同时更新
顶点的前驱结点
为它经过的点(P的前驱为vi),保存在一个数组或集合中
- 重复2-3步骤,直到最短路径顶点为目标顶点结束
![在这里插入图片描述](https://img-blog.csdnimg.cn/1e18ac61930648f697d024dc2577e2a9.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 运行效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/5f98e3a446084e9cbe98fa51c1313489.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q635Li_Z3JkX-W_l-m5jw==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 代码
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
int[][] matrix = new int[vertex.length][vertex.length];
final int N = 65535;
matrix[0]=new int[]{N,5,7,N,N,N,2};
matrix[1]=new int[]{5,N,N,9,N,N,3};
matrix[2]=new int[]{7,N,N,N,8,N,N};
matrix[3]=new int[]{N,9,N,N,N,4,N};
matrix[4]=new int[]{N,N,8,N,N,5,4};
matrix[5]=new int[]{N,N,N,4,5,N,6};
matrix[6]=new int[]{2,3,N,N,4,6,N};
Graph graph = new Graph(vertex, matrix);
graph.showGraph();
graph.digkstra(6);
graph.showDijkstra();
}
}
class Graph{
private char[] vertex;
private int[][] matrix;
private VisitedVertex visitedVertex;
public Graph(char[] vertex,int[][] matrix){
this.vertex = vertex;
this.matrix = matrix;
}
public void showGraph(){
for (int[] link: matrix){
System.out.println(Arrays.toString(link));
}
}
public void digkstra(int index){
this.visitedVertex = new VisitedVertex(vertex.length, index);
update(index);
for(int j = 1;j < vertex.length;j++){
index = visitedVertex.updateArr();
update(index);
}
}
private void update(int index){
int len = 0;
for(int j = 0;j<matrix[index].length;j++){
len = visitedVertex.getDis(index) + matrix[index][j];
if(!visitedVertex.isVisited(j) && len < visitedVertex.getDis(j)){
visitedVertex.updatePre(j,index);
visitedVertex.updateDis(j,len);
}
}
}
public void showDijkstra(){
visitedVertex.show();
}
}
class VisitedVertex{
public int[] already_arr;
public int[] pre_visited;
public int[] dis;
public VisitedVertex(int length,int index){
this.already_arr = new int[length];
this.pre_visited = new int[length];
this.dis = new int[length];
Arrays.fill(dis,65535);
this.dis[index] = 0;
this.already_arr[index] = 1;
}
public boolean isVisited(int index){
return already_arr[index] == 1;
}
public void updateDis(int index,int len){
dis[index] = len;
}
public void updatePre(int index,int pre){
pre_visited[index] = pre;
}
public int getDis(int index){
return dis[index];
}
public int updateArr(){
int min = 65535,index = 0;
for (int i = 0;i < already_arr.length;i++){
if(already_arr[i] == 0 && dis[i] < min){
min = dis[i];
index = i;
}
}
already_arr[index]=1;
return index;
}
public void show(){
System.out.println("============================");
for (int i : already_arr){
System.out.print(i + " ");
}
System.out.println();
for (int i : pre_visited){
System.out.print(i + " ");
}
System.out.println();
for (int i : dis){
System.out.print(i + " ");
}
System.out.println();
char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
int count = 0;
for (int i : dis) {
if (i != 65535) {
System.out.print(vertex[count] + "("+i+") ");
} else {
System.out.println("N ");
}
count++;
}
System.out.println();
}
}