Java作为一种广泛使用的编程语言,支持实现多种算法。这些算法可以根据其用途、复杂度、数据结构和应用领域进行分类。以下是一些Java中常见的算法示例:
通过重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
public class BubbleSort {
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
// swap arr[j+1] and arr[j]
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
bubbleSort(arr);
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++)
System.out.print(arr[i] + " ");
System.out.println();
}
}
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
public class SelectionSort {
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n-1; i++) {
// Find the minimum element in unsorted array
int min_idx = i;
for (int j = i+1; j < n; j++)
if (arr[j] < arr[min_idx])
min_idx = j;
// Swap the found minimum element with the first element
int temp = arr[min_idx];
arr[min_idx] = arr[i];
arr[i] = temp;
}
}
public static void main(String[] args) {
int[] arr = {64, 25, 12, 22, 11};
selectionSort(arr);
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++)
System.out.print(arr[i] + " ");
System.out.println();
}
}
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
public class InsertionSort {
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; ++i) {
int key = arr[i];
int j = i - 1;
/* Move elements of arr[0..i-1], that are
greater than key, to one position ahead
of their current position */
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6};
insertionSort(arr);
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++)
System.out.print(arr[i] + " ");
System.out.println();
}
}
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
public class QuickSort {
public static void sort(int[] array) {
quickSort(array, 0, array.length - 1);
}
private static void quickSort(int[] array, int low, int high) {
if (low < high) {
int pivotIndex = partition(array, low, high);
quickSort(array, low, pivotIndex - 1);
quickSort(array, pivotIndex + 1, high);
}
}
private static int partition(int[] array, int low, int high) {
int pivot = array[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (array[j] < pivot) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, high);
return i + 1;
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] array = {10, 7, 8, 9, 1, 5};
sort(array);
for (int num : array) {
System.out.print(num + " ");
}
}
}
快速排序的关键在于选择一个“基准”元素,并通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
归并排序是一种高效的排序算法,它采用分治法的策略,将一个大问题分解成小问题来解决,然后将小问题的解合并以解决整个大问题。在归并排序中,我们将数组分成两半,对每一半递归地应用归并排序,然后将排序后的两半合并在一起。
public class MergeSort {
// 主函数,用于调用归并排序
public static void sort(int[] array) {
if (array.length < 2) {
return; // 基本情形:如果数组只有一个元素,就不需要排序
}
int middle = array.length / 2;
int[] left = new int[middle];
int[] right = new int[array.length - middle];
// 将数据复制到左右两个数组
for (int i = 0; i < middle; i++) {
left[i] = array[i];
}
for (int i = middle; i < array.length; i++) {
right[i - middle] = array[i];
}
// 对左右两个数组递归地进行归并排序
sort(left);
sort(right);
// 合并排序后的左右两个数组
merge(array, left, right);
}
// 合并两个已排序的数组
private static void merge(int[] result, int[] left, int[] right) {
int i = 0, j = 0, k = 0;
// 当左右两个数组都未遍历完时,比较并合并
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result[k++] = left[i++];
} else {
result[k++] = right[j++];
}
}
// 将剩余的元素复制到结果数组中
while (i < left.length) {
result[k++] = left[i++];
}
while (j < right.length) {
result[k++] = right[j++];
}
}
public static void main(String[] args) {
int[] array = {34, 7, 23, 32, 5, 62};
sort(array);
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
逐个检查每个元素,直到找到所需的特定元素为止。
public class LinearSearch {
public static int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target)
return i; // 返回目标的索引
}
return -1; // 如果没有找到目标,返回-1
}
public static void main(String[] args) {
int[] arr = {2, 3, 4, 10, 40};
int target = 10;
int result = linear
Search.linearSearch(arr, target);
if (result == -1)
System.out.println("Element not present in array");
else
System.out.println("Element found at index " + result);
}
}
在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。
注意,二分搜索需要数组是有序的。
public class BinarySearch {
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;
if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1; // 如果没有找到目标,返回-1
}
public static void main(String[] args) {
int[] arr = {-10, -5, 0, 3, 7, 9, 12};
int target = 7;
int result = binarySearch(arr, target);
if (result == -1)
System.out.println("Element not present in array");
else
System.out.println("Element found at index " + result);
}
}
在二分搜索的实现中,我们首先定义了左边界
left
和右边界right
,然后在每次迭代中计算中间索引mid
。如果arr[mid]
等于目标值target
,则搜索成功并返回索引。如果arr[mid]
小于目标值,则搜索区间变为mid + 1
到right
(因为mid
及其左边的元素都不可能是目标值)。如果arr[mid]
大于目标值,则搜索区间变为left
到mid - 1
。如果循环结束时仍未找到目标值,则返回-1。注意,二分搜索的时间复杂度为O(log n),比线性搜索的O(n)要快得多,但它要求数组已经是有序的。如果数组未排序,则需要先对其进行排序,这会增加额外的计算成本。
栈是一种后进先出(LIFO)的数据结构。Java中可以使用Stack类(尽管它不是Java集合框架的一部分,并且通常建议使用Deque实现,如ArrayDeque)或自己实现栈。
import java.util.ArrayDeque;
import java.util.Deque;
public class StackExample {
private Deque stack;
public StackExample() {
stack = new ArrayDeque<>();
}
public void push(int value) {
stack.push(value);
}
public int pop() {
return stack.pop();
}
public boolean isEmpty() {
return stack.isEmpty();
}
// 可以添加更多方法来支持栈的操作
}
队列是一种先进先出(FIFO)的数据结构。Java中Queue接口是Java集合框架的一部分,并且有多种实现,如LinkedList、PriorityQueue等。
import java.util.LinkedList;
import java.util.Queue;
public class QueueExample {
private Queue queue;
public QueueExample() {
queue = new LinkedList<>();
}
public void enqueue(int value) {
queue.offer(value);
}
public int dequeue() {
return queue.poll();
}
public boolean isEmpty() {
return queue.isEmpty();
}
// 可以添加更多方法来支持队列的操作
}
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。
贪心算法解决问题的效率很高,但它不保证总能得到最优解。对于很多问题而言,贪心算法能够产生令人满意的解,尤其是在解决一些最优化问题时。
贪心算法的基本思路是:
- 建立数学模型来描述问题。
- 把求解的问题分成若干个子问题。
- 对每一子问题求解,得到子问题的局部最优解。
- 把子问题的解局部最优解合成原来解问题的一个解。
假设你有无限数量的面额为1元、5元和10元的硬币,要找出给定金额的最少硬币数量。
public class CoinChange {
public int minCoins(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, Integer.MAX_VALUE - 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] == Integer.MAX_VALUE - 1 ? -1 : dp[amount];
}
public static void main(String[] args) {
CoinChange cc = new CoinChange();
int[] coins = {1, 5, 10};
int amount = 11;
System.out.println("Minimum coins required: " + cc.minCoins(coins, amount));
}
}
分治算法通过将大问题分解为小问题来解决,通常这些小问题与原问题相同,但规模更小。递归地解决这些小问题,然后将结果合并以得到原始问题的答案。
public class MergeSort {
// 主函数,用于调用归并排序
public static void sort(int[] array) {
if (array.length < 2) {
return; // 基本情形:如果数组只有一个元素,就不需要排序
}
int middle = array.length / 2;
int[] left = new int[middle];
int[] right = new int[array.length - middle];
// 将数据复制到左右两个数组
for (int i = 0; i < middle; i++) {
left[i] = array[i];
}
for (int i = middle; i < array.length; i++) {
right[i - middle] = array[i];
}
// 对左右两个数组递归地进行归并排序
sort(left);
sort(right);
// 合并排序后的左右两个数组
merge(array, left, right);
}
// 合并两个已排序的数组
private static void merge(int[] result, int[] left, int[] right) {
int i = 0, j = 0, k = 0;
// 当左右两个数组都未遍历完时,比较并合并
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result[k++] = left[i++];
} else {
result[k++] = right[j++];
}
}
// 将剩余的元素复制到结果数组中
while (i < left.length) {
result[k++] = left[i++];
}
while (j < right.length) {
result[k++] = right[j++];
}
}
public static void main(String[] args) {
int[] array = { 34, 7, 23, 32, 5, 62 };
sort(array);
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
深度优先搜索( DFS, Depth-First Search) 是一种用于遍历或搜索树或图的算法。在这个算法中,我们从根节点开始,尽可能深地搜索树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这个过程一直进行到已发现从原始节点可达的所有节点为止。对于树来说,深度优先搜索可以简单地看作是沿着树的左子树一直向下搜索,直到到达叶子节点,然后回溯并搜索右子树。这个过程会递归地进行,直到搜索完所有的节点。
深度优先搜索算法的核心思想是:
1. 访问 : 首先访问指定的起始节点, 并将其标记为已访问。
2. 深入 : 然后寻找与当前节点相邻且未被访问的节点, 并递归地进行深度优先搜索。
3. 回溯 : 如果当前节点没有相邻的未被访问的节点, 则回溯到上一个节点, 并继续搜索其他相邻的未被访问的节点。
4. 重复 : 重复上述过程, 直到所有节点都被访问过。
import java.util.*;
public class DFS {
private LinkedList[] adjLists; // 邻接列表
private boolean[] visited; // 访问标记数组
// 构造函数,初始化图的邻接列表和访问标记数组
public DFS(int vertices) {
adjLists = new LinkedList[vertices];
visited = new boolean[vertices];
for (int i = 0; i < vertices; i++) {
adjLists[i] = new LinkedList();
}
}
// 添加边
void addEdge(int src, int dest) {
adjLists[src].add(dest);
}
// 递归实现的深度优先搜索
void DFSUtil(int vertex) {
visited[vertex] = true;
System.out.print(vertex + " ");
Iterator it = adjLists[vertex].listIterator();
while (it.hasNext()) {
int adj = it.next();
if (!visited[adj]) {
DFSUtil(adj);
}
}
}
// 对外提供的DFS方法,从指定的节点开始搜索
void DFS(int vertex) {
// 初始化所有节点为未访问
for (int i = 0; i < visited.length; i++) {
visited[i] = false;
}
// 从指定的节点开始递归搜索
DFSUtil(vertex);
}
public static void main(String args[]) {
DFS g = new DFS(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
System.out.println("深度优先遍历(从顶点2开始):");
g.DFS(2);
}
}
广度优先搜索(BFS)是一种遍历或搜索树或图的算法,从一个节点开始,访问此节点的所有邻接节点,然后对这些邻接节点逐一进行同样的访问,以此类推,直到访问完图中所有被访问的节点。以下是一个简单的广度优先搜索的示例,用于遍历一个无向图的所有节点。
import java.util.*;
public class BFSExample {
// 使用邻接表来表示图
private Map> graph;
// 构造函数,初始化图
public BFSExample() {
graph = new HashMap<>();
}
// 添加边到图中
public void addEdge(int source, int destination) {
this.graph.computeIfAbsent(source, k -> new ArrayList<>()).add(destination);
this.graph.computeIfAbsent(destination, k -> new ArrayList<>()).add(source);
}
// 广度优先搜索函数
public void bfs(int start) {
Queue queue = new LinkedList<>();
Set visited = new HashSet<>();
queue.add(start);
visited.add(start);
while (!queue.isEmpty()) {
int current = queue.poll();
System.out.print(current + " ");
for (int neighbor : graph.getOrDefault(current, Collections.emptyList())) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
queue.add(neighbor);
}
}
}
}
public static void main(String[] args) {
BFSExample graph = new BFSExample();
graph.addEdge(1, 2);
graph.addEdge(1, 3);
graph.addEdge(2, 4);
graph.addEdge(2, 5);
graph.addEdge(3, 6);
System.out.println("广度优先搜索(BFS):");
graph.bfs(1);
}
}
除了DFS和BFS外,还有如Dijkstra算法(用于找到图中节点之间的最短
用于字符串匹配的算法,可以在一个文本字符串S内查找一个词W的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表。
下面是KMP算法的一个Java示例实现。这个实现包含了预处理阶段,用于生成部分匹配表(也称为“前缀表”或“最长公共前后缀数组”),以及实际的字符串匹配阶段。
public class KMPAlgorithm {
// 获取模式字符串的部分匹配表
public static int[] getPartialMatchTable(String pattern) {
int[] pmt = new int[pattern.length()];
int index = 0; // pattern中前缀的单个字符最长相等前后缀长度
int len = 0; // 前缀长度
pmt = 0; // 初始化部分匹配表第一位为0
// 计算部分匹配表
while (index < pattern.length() - 1) {
if (pattern.charAt(index) == pattern.charAt(len)) {
len++;
pmt[index + 1] = len;
index++;
} else {
if (len != 0) {
len = pmt[len - 1];
} else {
pmt[index + 1] = 0;
index++;
}
}
}
return pmt;
}
// KMP算法实现
public static int KMPSearch(String txt, String pattern) {
int[] pmt = getPartialMatchTable(pattern);
int i = 0; // txt的索引
int j = 0; // pattern的索引
while (i < txt.length() && j < pattern.length()) {
if (txt.charAt(i) == pattern.charAt(j)) {
i++;
j++;
} else {
if (j != 0) {
j = pmt[j - 1];
} else {
i++;
}
}
}
if (j == pattern.length()) {
return i - j; // 返回模式字符串在文本中的起始索引
} else {
return -1; // 没有找到匹配
}
}
public static void main(String[] args) {
String txt = "ABABDABACDABABCABAB";
String pattern = "ABABCABAB";
int index = KMPSearch(txt, pattern);
if (index != -1) {
System.out.println("找到模式字符串,起始索引为:" + index);
} else {
System.out.println("未找到模式字符串");
}
}
}
Boyer-Moore算法是一种高效的字符串匹配算法, 特别适用于在较长的文本中查找较短的模式字符串。该算法的核心在于,当字符不匹配时,能够利用已经匹配过的部分信息,将模式字符串向右滑动更远的距离。
以下是Boyer-Moore算法的Java示例实现:
public class BoyerMooreAlgorithm {
// 坏字符规则表,记录每个字符最后出现的位置
private int[] badCharRule;
// 构造函数,初始化坏字符规则表
public BoyerMooreAlgorithm(String pattern) {
int patternLength = pattern.length();
badCharRule = new int; // 假设字符集是ASCII
// 初始化坏字符规则表,默认值为-1
for (int i = 0; i < 256; i++) {
badCharRule[i] = -1;
}
// 更新坏字符规则表
for (int i = 0; i < patternLength; i++) {
badCharRule[pattern.charAt(i)] = i;
}
}
// Boyer-Moore算法实现
public int search(String txt, String pattern) {
int txtLength = txt.length();
int patternLength = pattern.length();
int shift = 0;
while (shift <= (txtLength - patternLength)) {
int patternIndex = patternLength - 1;
// 从模式字符串的末尾开始匹配
while (patternIndex >= 0 && pattern.charAt(patternIndex) == txt.charAt(shift + patternIndex)) {
patternIndex--;
}
if (patternIndex < 0) {
// 找到匹配,返回模式字符串在文本中的起始索引
return shift;
} else {
// 不匹配,根据坏字符规则计算滑动距离
int badChar = txt.charAt(shift + patternLength - 1);
shift += Math.max(1, patternLength - badCharRule[badChar] - 1);
}
}
return -1; // 未找到匹配
}
public static void main(String[] args) {
String txt = "HERE IS A SIMPLE EXAMPLE";
String pattern = "EXAMPLE";
BoyerMooreAlgorithm algorithm = new BoyerMooreAlgorithm(pattern);
int index = algorithm.search(txt, pattern);
if (index != -1) {
System.out.println("找到模式字符串,起始索引为:" + index);
} else {
System.out.println("未找到模式字符串");
}
}
}
除了之前提到的KMP算法和Boyer-Moore算法外,还有其他如Rabin-Karp算法(用于字符串搜索)、Levenshtein距离(用于测量两个序列之间的差异)等。
动态规划通过将原问题分解为相对简单的子问题,并保存子问题的解来避免重复计算,从而解决复杂问题。
斐波那契数列:用动态规划求解斐波那契数列,避免重复计算。
public class Fibonacci {
public static void main(String[] args) {
int n = 10; // 要计算的斐波那契数列的长度
for (int i = 0; i < n; i++) {
System.out.print(fibonacci(i) + " ");
}
}
public static int fibonacci(int n) {
// 创建一个数组来保存已经计算过的斐波那契数
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
if (n <= 1) {
return n;
}
// 使用动态规划思想,从底向上计算斐波那契数
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
在这段代码中,
fibonacci
方法使用了一个dp
数组来保存已经计算过的斐波那契数。dp
数组的长度是n + 1
,其中dp[0]
和dp[1]
分别初始化为0和1。然后,使用一个for循环从2开始迭代到n
,在每次迭代中计算当前的斐波那契数并将其保存在数组中。最后,返回dp[n]
作为结果。这种方法避免了重复计算,因为每个斐波那契数只计算一次并保存在数组中。当需要计算相同的斐波那契数时,可以直接从数组中获取结果,而不需要重新计算。
最长公共子序列(LCS):在两个序列中找到最长公共子序列的问题。
public class LCS {
public static void main(String[] args) {
String X = "AGGTAB";
String Y = "GXTXAYB";
System.out.println("Length of LCS is " + lcs(X, Y));
}
public static int lcs(String X, String Y) {
int m = X.length();
int n = Y.length();
int[][] L = new int[m + 1][n + 1];
// 构建LCS长度表L[][]
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
if (X == 0 || Y == 0) {
L[i][j] = 0;
} else if (X.charAt(i - 1) == Y.charAt(j - 1)) {
L[i][j] = L[i - 1][j - 1] + 1;
} else {
L[i][j] = Math.max(L[i - 1][j], L[i][j - 1]);
}
}
}
// L[m][n]包含LCS的长度
return L[m][n];
}
}
这段代码定义了一个lcs
方法,它接受两个字符串X
和Y
作为输入,并返回它们的最长公共子序列L[][]
的长度。我们使用一个二维数组L[i][j]
来存储LCS的长度信息,其中表示X
的前i
个字符和Y
的前j
个字符之间的LCS的长度。
在lcs
方法中,我们首先初始化L[][]
数组的第一行和第一列,它们都为0,因为空序列和任何序列的LCS长度都是0。然后,我们使用两个嵌套的for循环来填充数组L[][]
。如果X的第i个字符和Y的第j个字符相等,那么L[i][j]
就等于L[i-1][j-1]
加1,表示我们在LCS中添加了一个新字符。否则,L[i][j]
就等于L[i-1][j]
和L[i][j-1]
中的最大值,表示我们不考虑的第X个i字符或Y的第j个字符。
最后,L[m][n]
就包含了X和Y之间的LCS的长度,我们将其返回作为结果。
回溯算法通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化来撤销上一步或上几步的计算,即“回溯”并尝试另一种可能的候选解。
在Java中实现回溯算法,我们通常会定义一个递归函数,该函数会尝试所有可能的候选解,并在不满足条件时进行回溯。
import java.util.ArrayList;
import java.util.List;
public class Permutation {
public List> permute(int[] nums) {
List> result = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrack(nums, new ArrayList<>(), used, result);
return result;
}
private void backtrack(int[] nums, List tempList, boolean[] used, List> result) {
if (tempList.size() == nums.length) {
result.add(new ArrayList<>(tempList));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
tempList.add(nums[i]);
used[i] = true;
backtrack(nums, tempList, used, result);
used[i] = false;
tempList.remove(tempList.size() - 1);
}
}
public static void main(String[] args) {
Permutation permutation = new Permutation();
int[] nums = {1, 2, 3};
List> permutations = permutation.permute(nums);
for (List permutationList : permutations) {
System.out.println(permutationList);
}
}
}
在这个例子中,
permute
方法是入口点,它初始化结果列表、布尔数组(用于跟踪哪些数字已经被使用)并调用backtrack
方法进行回溯。backtrack
方法尝试将每个数字添加到临时列表中,如果临时列表的长度达到了输入数组的长度,就将该临时列表添加到结果列表中。否则,它会继续尝试添加其他数字,并在添加之前和之后分别标记和取消标记该数字为已使用。如果某个数字已经被使用,就跳过它。最后,在main
方法中,我们创建一个对象,传入一个Permutation
整数数组,并打印出所有可能的排列。
在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击(即任意两个皇后都不能处于同一行、同一列或同一斜线上),问有多少种摆法。
public class EightQueens {
private static final int SIZE = 8;
private int[] queens = new int[SIZE]; // 数组下标表示行,值表示列
private int solutionsCount = 0;
public void solve() {
placeQueen(0);
System.out.println("共有 " + solutionsCount + " 种解法");
}
private void placeQueen(int row) {
if (row == SIZE) {
solutionsCount++;
printSolution();
return;
}
for (int col = 0; col < SIZE; col++) {
queens[row] = col;
if (isSafe(row, col)) {
placeQueen(row + 1);
}
}
}
private boolean isSafe(int row, int col) {
for (int i = 0; i < row; i++) {
int otherCol = queens[i];
if (otherCol == col || Math.abs(otherCol - col) == Math.abs(i - row)) {
return false;
}
}
return true;
}
private void printSolution() {
for (int row = 0; row < SIZE; row++) {
for (int col = 0; col < SIZE; col++) {
System.out.print(queens[row] == col ? "Q " : ". ");
}
System.out.println();
}
System.out.println();
}
public static void main(String[] args) {
EightQueens eq = new EightQueens();
eq.solve();
}
}
- 回溯算法是一种通过遍历所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个正确的解(或者至少不是最后一个正确的解),回溯算法会通过在上一步进行一些变化来丢弃该解,即“回溯”并尝试另一个可能的候选解。
- 回溯算法通常用于解决组合问题、分割问题、子集问题、排列问题、棋盘问题等。其核心思想是“试错”,即尝试每一种可能的情况,一旦发现当前情况不能得到正确的解,就退回上一步(或几步),然后尝试另一种可能的情况。
- 在实现回溯算法时,通常需要定义一个递归函数,该函数会尝试所有可能的候选解,并递归地调用自身来处理每一种情况。当递归函数找到一个正确的解时,它会将这个解保存下来,并继续尝试其他可能的解。
- 回溯算法的时间复杂度通常很高,因为它需要遍历所有可能的解。然而,在某些情况下,可以通过剪枝来减少搜索空间,从而提高算法的效率。剪枝是指在搜索过程中,根据某些条件提前排除一些不可能得到正确解的候选解,从而减少搜索的时间。