Java算法篇-查找算法&字符串匹配算法&图论算法
📢📢📢📣📣📣
哈喽!大家好,我是「Leen」。刚工作几年,想和大家一同进步🤝🤝
一位上进心十足的Java博主!😜😜😜
喜欢尝试一些新鲜的东西,平时比较喜欢研究一些新鲜技术和一些自己没有掌握的技术领域。能用程序解决的坚决不手动解决😜😜😜目前已涉足Java、Python、数据库(MySQL、pgsql、MongoDB、Oracle...)、Linux、HTML、VUE、PHP、C(了解不多,主要是嵌入式编程方向做了一些)...(还在不断地学习,扩展自己的见识和技术领域中),希望可以和各位大佬们一起进步,共同学习🤝🤝
✨ 如果有对【Java】,或者喜欢看一些实操笔记感兴趣的【小可爱】,欢迎关注我
❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️
————————————————如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。
前言
Java语言常用的算法包括:
排序算法:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序等。查找算法:顺序查找、二分查找、哈希查找等。
字符串匹配算法:暴力匹配、KMP算法、Boyer-Moore算法等。
图论算法:最短路径算法、最小生成树算法、拓扑排序等。
动态规划算法:背包问题、最长公共子序列、最长上升子序列等。
贪心算法:最小生成树、单源最短路径等。
分治算法:快速排序、归并排序等。
网络流算法:最大流问题、最小割问题等。
数学算法:欧几里得算法、素数判断、大数计算等。
本次文章内容主要展示查找算法、字符串匹配算法、图论算法中的几个算法demo
一、查找算法-二分查找
1、算法原理
二分查找,也称为折半查找,它要求数据集合必须是有序的,它的基本思想是将数据集合分成两半,如果目标元素比中间元素小,就在前半部分继续查找,否则在后半部分继续查找,直到找到目标元素或数据集合为空。二分查找的时间复杂度为O(logn)。
2、原理图讲解二分查找流程
3、算法代码demo
public static int binarySearch(int[] arr, int target) {
// 初始化左边界为数组的第一个索引
int left = 0;
// 初始化右边界为数组的最后一个索引
int right = arr.length - 1;
// 当左边界不超过右边界时,继续循环
while (left <= right) {
// 计算中间索引,避免整数溢出
int mid = left + (right - left) / 2;
// 如果中间元素等于目标值,返回中间索引
if (arr[mid] == target) {
return mid;
// 如果中间元素小于目标值,移动左边界到中间索引的右侧
} else if (arr[mid] < target) {
left = mid + 1;
// 如果中间元素大于目标值,移动右边界到中间索引的左侧
} else {
right = mid - 1;
}
}
// 如果目标值不存在于数组中,返回 -1
return -1;
}
二、查找算法-线性查找
1、算法原理
初始化:首先,从集合的第一个元素开始搜索。通常,你会使用一个索引或指针来跟踪当前查找的位置,将其初始化为0(对于数组或列表)或集合的起始位置。
遍历:在遍历过程中,算法将当前位置的元素与目标元素进行比较。如果当前元素与目标元素相匹配,算法会返回当前位置的索引(或其他位置信息),表示找到了目标元素。
比较:如果当前元素不与目标元素匹配,算法将继续移动到下一个元素,更新当前位置的索引,然后重复比较的步骤,直到找到目标元素或者遍历整个集合。
结束条件:线性查找算法将一直进行直到发生以下两种情况之一:
找到目标元素,返回目标元素的位置。
遍历整个集合,没有找到目标元素,此时算法返回一个指示未找到的值,通常是一个特殊值
2、原理图讲解线性查找流程
如图所示,我们查找数字6在数组中的位置
从数组的最左边开始查找,将其与6进行比较,如果结果一致,查找便结束,不一致则向右检查下一个数字。
此处不一致,所以向右继续和下一个数字进行比较。
重复上述操作直到找到数字6为止
找到6了,查找结束
线性查找需要从头开始不断地按顺序检查数据,因此在数据量大且目标数据靠后,或者目标数据不存在时,比较的次数就会更多,也更为好使。若数据量为n,线性查找的时间复杂便为O(n)。
3、算法代码demo
public static int linearSearch(int[] arr, int target) {
// 遍历整个数组
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
// 找到目标元素,返回索引
return i;
}
}
// 没有找到目标元素,返回-1
return -1;
}
三、字符串匹配算法-kmp算法
1、算法原理
KMP算法,即Knuth-Morris-Pratt算法,是一种高效的字符串匹配算法。它的基本思想是,利用已知信息,尽量减少模式串与文本串的匹配次数。具体实现中,KMP算法利用了前缀和后缀的概念,对模式串进行预处理,以便在匹配过程中快速调整模式串的位置。KMP算法的时间复杂度是O(m+n),其中m和n分别为模式串和文本串的长度。
2、原理图讲解插入排序流程
3、算法代码demo
public static int kmp(String s, String p) {
// 获取模式字符串的部分匹配表(next数组)
int[] next = getNext(p);
int i = 0, j = 0;
// 遍历主字符串和模式字符串
while (i < s.length() && j < p.length()) {
// 如果当前字符匹配,或者j为-1(表示模式串的开始),则 i 和 j 同时向前移动。
if (j == -1 || s.charAt(i) == p.charAt(j)) {
i++;
j++;
} else {
// 如果当前字符不匹配,根据next数组更新j的位置
j = next[j];
}
}
// 如果模式串匹配完毕,返回匹配的起始位置
if (j == p.length()) {
return i - j;
} else {
// 如果没有匹配,返回-1
return -1;
}
}
/**
* 生成部分匹配表(next数组)
*
* @param p
* @return
*/
private static int[] getNext(String p) {
//初始化 next 数组,长度为模式字符串的长度
int[] next = new int[p.length()];
//设置 next 数组的第一个元素为 -1
next[0] = -1;
int i = 0, j = -1;
// 遍历模式字符串,生成next数组
while (i < p.length() - 1) {
// 如果当前字符匹配,或者j为-1(表示模式串的开始),则 i 和 j 同时向前移动。
if (j == -1 || p.charAt(i) == p.charAt(j)) {
i++;
j++;
// 如果i位置的字符不等于j位置的字符,设置next[i]为j
if (p.charAt(i) != p.charAt(j)) {
next[i] = j;
} else {
// 如果i位置的字符等于j位置的字符,继承next[j]的值
next[i] = next[j];
}
} else {
// 如果当前字符不匹配,根据next数组更新j的位置,避免回退过多字符
j = next[j];
}
}
return next;
}
四、图论算法-dijkstra算法
1、算法原理
Dijkstra算法是一种高效地找出图中从一个给定起点到所有其他顶点最短路径的方法。它按照距离起点的远近顺序,逐步确定到各个顶点的最短路径。具体来说,算法首先找到距离起点最近的顶点,并确定它们之间的最短路径;然后,它接着寻找下一个最近的顶点,依此类推。在第 i ii 次迭代之前,算法已经确定了到达最近的 i − 1 i-1i−1个顶点的最短路径,这些顶点及其路径构成了原图的一个子图,形成一棵以起点为根的树。
2、原理图讲解dijkstra算法流程
3、算法代码demo
public static void dijkstra(int[][] graph, int start) {
// 获取图的顶点数
int n = graph.length;
// visited数组记录顶点是否已访问
boolean[] visited = new boolean[n];
// dist数组记录从起点到各顶点的最短距离
int[] dist = new int[n];
// 将所有顶点的初始距离设置为无穷大(表示不可达)
Arrays.fill(dist, Integer.MAX_VALUE);
// 起点到自身的距离为0
dist[start] = 0;
// 遍历所有顶点
for (int i = 0; i < n; i++) {
int u = -1;
// 在未访问的顶点中找到距离起点最近的顶点u
for (int j = 0; j < n; j++) {
if (!visited[j] && (u == -1 || dist[j] < dist[u])) {
u = j;
}
}
// 标记顶点u为已访问
visited[u] = true;
// 更新顶点u的所有邻接顶点v的距离
for (int v = 0; v < n; v++) {
// 如果顶点v未访问,且u到v有边相连
if (graph[u][v] != 0 && !visited[v]) {
// 更新v的距离为当前距离和u到v距离之和的最小值
dist[v] = Math.min(dist[v], dist[u] + graph[u][v]);
}
}
}
}
五、参考代码(可以自己跑一下,debug看看数据变化的原理)
1、查找算法
package com.leen.test.test2024.test521;
import java.util.Arrays;
import java.util.LinkedList;
/**
* Author:Leen
* Date: 2024/5/21 10:28
*/
public class SearchAlgorithm {
/**
* 查找算法,也称为搜索算法,是计算机科学中的一种基本算法,它用于在数据集合中查找特定元素的位置或存在性。
* 常见的查找算法有线性查找、二分查找、哈希查找等。
*
* @param args
*/
public static void main(String[] args) {
//定义数组
int[] arr = new int[10];
arr[0] = 767;
arr[1] = 67468;
arr[2] = 496;
arr[3] = 3463;
arr[4] = 3213;
arr[5] = 7676;
arr[6] = 5876;
arr[7] = 245;
arr[8] = 425;
arr[9] = 5158;
System.out.println(Arrays.toString(arr));
int target = 425;
/**
* 无序情况下
*/
//二分
// int index = binarySearch(arr, target);
//线性
int index = linearSearch(arr, target);
//数据元素
if (index == -1) {
System.out.println("无序情况下,目标元素不存在");
} else {
System.out.println("无序情况下,目标元素在数组中的位置为:" + index);
}
/**
* 有序情况下
*/
int[] array = Arrays.stream(arr).sorted().toArray();
System.out.println(Arrays.toString(array));
// 二分
// int soterIndex = binarySearch(array, target);
// 线性
int soterIndex = linearSearch(arr, target);
// 数据元素
if (soterIndex == -1) {
System.out.println("目标元素不存在");
} else {
System.out.println("目标元素在数组中的位置为:" + soterIndex);
}
System.out.println(Arrays.toString(arr));
}
/**
* 二分查找法,也称为折半查找,
* 它要求数据集合必须是有序的,
* 它的基本思想是将数据集合分成两半,如果目标元素比中间元素小,就在前半部分继续查找,
* 否则在后半部分继续查找,直到找到目标元素或数据集合为空。二分查找的时间复杂度为O(logn)。
* 通过逐步缩小搜索区间来查找目标值。
* 时间复杂度为 O(log n),适用于查找大规模排序数组中的元素。
*
* @param arr
* @param target
* @return
*/
public static int binarySearch(int[] arr, int target) {
// 初始化左边界为数组的第一个索引
int left = 0;
// 初始化右边界为数组的最后一个索引
int right = arr.length - 1;
// 当左边界不超过右边界时,继续循环
while (left <= right) {
// 计算中间索引,避免整数溢出
int mid = left + (right - left) / 2;
// 如果中间元素等于目标值,返回中间索引
if (arr[mid] == target) {
return mid;
// 如果中间元素小于目标值,移动左边界到中间索引的右侧
} else if (arr[mid] < target) {
left = mid + 1;
// 如果中间元素大于目标值,移动右边界到中间索引的左侧
} else {
right = mid - 1;
}
}
// 如果目标值不存在于数组中,返回 -1
return -1;
}
/**
* 线性查找是最简单的查找算法之一,
* 它的数据不要求有序
* 它的基本思想是从数据集合的第一个元素开始逐个比较,直到找到目标元素或遍历完整个数据集合。
* 线性查找算法的时间复杂度是O(n),其中n是数据集中的元素数量,而空间复杂度通常为O(1)。
* 虽然它在小型数据集中适用,但在大型数据集或需要快速查找的情况下,更高效的算法可能更合适。
* @param arr
* @param target
* @return
*/
public static int linearSearch(int[] arr, int target) {
// 遍历整个数组
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
// 找到目标元素,返回索引
return i;
}
}
// 没有找到目标元素,返回-1
return -1;
}
}
2、字符串匹配算法
package com.leen.test.test2024.test521;
/**
* Author:Leen
* Date: 2024/5/21 16:24
*/
public class StringMatchingAlgorithm {
/**
* 字符串匹配算法是计算机科学中的一种算法,用于在一个字符串中查找一个子串的出现位置。
* 字符串匹配算法可以应用于文本搜索、数据处理、图形处理等领域。
* 在实际应用中,常用的字符串匹配算法包括暴力匹配算法、KMP算法、Boyer-Moore算法、Rabin-Karp算法等
*/
public static void main(String[] args) {
String A = "afndkisabfjsbfjsbhfjhbfj";
String B = "f";
int kmp = kmp(A, B);
if (kmp != -1) {
System.out.println("B字符串位置在A的索引第'" + kmp + "'位");
} else {
System.out.println("B字符串不存在A中");
}
}
/**
* KMP算法,即Knuth-Morris-Pratt算法,是一种高效的字符串匹配算法。
* 它的基本思想是,利用已知信息,尽量减少模式串与文本串的匹配次数。
* 具体实现中,KMP算法利用了前缀和后缀的概念,对模式串进行预处理,以便在匹配过程中快速调整模式串的位置。
* KMP算法的时间复杂度是O(m+n),其中m和n分别为模式串和文本串的长度
* @param s
* @param p
* @return
*/
/**
* KMP主算法
* KMP算法用于在主字符串中查找模式字符串,具有线性时间复杂度 O(n+m),
* 其中n 是主字符串的长度,m 是模式字符串的长度。
* KMP算法通过预处理模式字符串,生成部分匹配表(next数组),避免在匹配过程中重复检查已匹配的部分,从而提高查找效率。
* 该算法的关键在于合理利用next数组,减少主字符串的遍历次数
*/
public static int kmp(String s, String p) {
// 获取模式字符串的部分匹配表(next数组)
int[] next = getNext(p);
int i = 0, j = 0;
// 遍历主字符串和模式字符串
while (i < s.length() && j < p.length()) {
// 如果当前字符匹配,或者j为-1(表示模式串的开始),则 i 和 j 同时向前移动。
if (j == -1 || s.charAt(i) == p.charAt(j)) {
i++;
j++;
} else {
// 如果当前字符不匹配,根据next数组更新j的位置
j = next[j];
}
}
// 如果模式串匹配完毕,返回匹配的起始位置
if (j == p.length()) {
return i - j;
} else {
// 如果没有匹配,返回-1
return -1;
}
}
/**
* 生成部分匹配表(next数组)
*
* @param p
* @return
*/
private static int[] getNext(String p) {
//初始化 next 数组,长度为模式字符串的长度
int[] next = new int[p.length()];
//设置 next 数组的第一个元素为 -1
next[0] = -1;
int i = 0, j = -1;
// 遍历模式字符串,生成next数组
while (i < p.length() - 1) {
// 如果当前字符匹配,或者j为-1(表示模式串的开始),则 i 和 j 同时向前移动。
if (j == -1 || p.charAt(i) == p.charAt(j)) {
i++;
j++;
// 如果i位置的字符不等于j位置的字符,设置next[i]为j
if (p.charAt(i) != p.charAt(j)) {
next[i] = j;
} else {
// 如果i位置的字符等于j位置的字符,继承next[j]的值
next[i] = next[j];
}
} else {
// 如果当前字符不匹配,根据next数组更新j的位置,避免回退过多字符
j = next[j];
}
}
return next;
}
}
3、图论算法
package com.leen.test.test2024.test521;
import java.util.Arrays;
/**
* Author:Leen
* Date: 2024/5/21 16:59
*/
public class GraphTheoryAlgorithm {
/**
* 图论算法主要是用来处理图结构的算法,图结构是由节点(顶点)和边(连接节点的线)构成的。
*/
public static void main(String[] args) {
int V = 5; // 顶点数
// 图的邻接矩阵表示
int[][] graph = new int[][]{
{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}
};
System.out.println(Arrays.toString(graph));
// 调用Dijkstra算法
dijkstra(graph, 0);
System.out.println(Arrays.toString(graph));
}
/**
* 图论算法
* 用于计算从给定起点到图中所有其他顶点的最短路径。该算法通过不断选择未访问顶点中距离起点最近的顶点,并更新其邻接顶点的距离来工作。
* @param graph
* @param start
*/
public static void dijkstra(int[][] graph, int start) {
// 获取图的顶点数
int n = graph.length;
// visited数组记录顶点是否已访问
boolean[] visited = new boolean[n];
// dist数组记录从起点到各顶点的最短距离
int[] dist = new int[n];
// 将所有顶点的初始距离设置为无穷大(表示不可达)
Arrays.fill(dist, Integer.MAX_VALUE);
// 起点到自身的距离为0
dist[start] = 0;
// 遍历所有顶点
for (int i = 0; i < n; i++) {
int u = -1;
// 在未访问的顶点中找到距离起点最近的顶点u
for (int j = 0; j < n; j++) {
if (!visited[j] && (u == -1 || dist[j] < dist[u])) {
u = j;
}
}
// 标记顶点u为已访问
visited[u] = true;
// 更新顶点u的所有邻接顶点v的距离
for (int v = 0; v < n; v++) {
// 如果顶点v未访问,且u到v有边相连
if (graph[u][v] != 0 && !visited[v]) {
// 更新v的距离为当前距离和u到v距离之和的最小值
dist[v] = Math.min(dist[v], dist[u] + graph[u][v]);
}
}
}
}
}
欢迎大家在评论区讨论,今天的干货分享就到此结束了,如果觉得对您有帮助,麻烦给个三连!
以上内容为本人的经验总结和平时操作的笔记。若有错误和重复请联系作者删除!!感谢支持!!