1. 程式人生 > 其它 >演算法基礎入門——漢諾塔問題、字串的子序列、字串的全排列、紙牌贏家問題、遞迴逆序棧、數字轉化字串問題、揹包問題、N皇后問題

演算法基礎入門——漢諾塔問題、字串的子序列、字串的全排列、紙牌贏家問題、遞迴逆序棧、數字轉化字串問題、揹包問題、N皇后問題

package com.zuoshen.jichurumen.class08;

import java.util.ArrayList;
import java.util.Stack;

/**
 * @author ShiZhe
 * @create 2022-03-12 13:16
 */
public class code01 {

    /**
     * 漢諾塔問題
     * 列印n層漢諾塔從最左邊移動到最右邊的全部過程
     * @param n
     */
    public static void hanoi(int n) {
        if (n > 0) {
            func1(n, n, 
"left", "mid", "right"); } } /** * 漢諾塔問題的遞迴函式 * 不要去糾結每一個怎麼移動,要思考抽象的i怎麼移動 * @param i 移動個數 * @param down 當前位置,為了列印輸出 * @param from 出發 * @param help 輔助 * @param to 目的 */ public static void func1(int i, int down, String from, String help, String to) {
if (i == 1) { System.out.println("Move " + down + " from " + from + " to " + to); } else { // 1————i-1的圓盤從from移到help上 func1(i - 1, down - 1, from, to, help); // 將i圓盤從from移到to上,並列印 func1(1, down, from, help, to); // 將1————i-1的圓盤從help移到to上
func1(i - 1, down - 1, help, from, to); } } /** * 列印一個字串的全部子序列,包括空字串 * 輸出格式規範化可以新增一個list<char>型別的列表為遞迴函式的變數 * @param string */ public static void printAllSubsquence(String string) { char[] chars = string.toCharArray(); func2(chars, 0); } /** * 字串子序列的遞迴函式 * @param chars * @param i chars陣列的i位置 */ public static void func2(char[] chars, int i) { if (i == chars.length) { System.out.println(String.valueOf(chars)); return; } // i位置要 func2(chars, i + 1); char tmp = chars[i]; chars[i] = 0; // i位置不要 func2(chars, i + 1); chars[i] = tmp; } /** * 列印一個字串的全部排列 * 全排列的時間複雜度大於O(n!)小於O(n*n!) * @param string * @return */ public static ArrayList<String> printAllPermutations(String string) { // 排列結果放入list中 ArrayList<String> result = new ArrayList<>(); if (string == null || string.length() == 0) { return result; } char[] chars = string.toCharArray(); // 遞迴函式 func3(chars, 0, result); return result; } /** * 全排列的遞迴函式 * @param chars * @param i * @param result */ public static void func3(char[] chars, int i, ArrayList<String> result) { if (i == chars.length) { result.add(String.valueOf(chars)); } // 加速 boolean[] visit = new boolean[26]; for (int j = i; j < chars.length; j++) { if (!visit[chars[j] - 'a']) { visit[chars[j] - 'a'] = true; swap(chars, i, j); func3(chars, i + 1, result); swap(chars, j, i); } } } public static void swap(char[] chars, int i, int j) { char tmp = chars[i]; chars[i] = chars[j]; chars[j] = tmp; } /** * 紙牌問題 * 遞迴方法 * @param arr * @return */ public static int cardsInLine(int[] arr) { if (arr == null || arr.length == 0) { return 0; } // 先手和後手 return Math.max(func4(arr, 0, arr.length - 1), func5(arr, 0, arr.length - 1)); } /** * 先手 * @param arr * @param i * @param j * @return */ public static int func4(int[] arr, int i, int j) { if (i == j) { return arr[i]; } return Math.max(arr[i] + func5(arr, i + 1, j), arr[j] + func5(arr, i, j - 1)); } /** * 後手 * @param arr * @param i * @param j * @return */ public static int func5(int[] arr, int i, int j) { if (i == j) { return 0; } return Math.min(func4(arr, i + 1, j), func4(arr, i, j - 1)); } /** * 紙牌問題 * 記憶陣列方法 * 二維陣列是因為有遞迴方法中有2個變數,arr陣列是不變的 * 先找到陣列中初始化可以直接填寫的空 * 根據遞迴看關係,確定填寫方向 * 該題是從列從左到右,行從下往上 * @param arr * @return */ public static int cardsInLine2(int[] arr) { if (arr == null || arr.length == 0) { return 0; } int[][] func4 = new int[arr.length][arr.length]; int[][] func5 = new int[arr.length][arr.length]; for (int j = 0; j < arr.length; j++) { func4[j][j] = arr[j]; for (int i = j - 1; i >= 0; i--) { func4[i][j] = Math.max(arr[i] + func5[i + 1][j], arr[j] + func5[i][j -1]); func5[i][j] = Math.min(func4[i + 1][j], func4[i][j -1]); } } return Math.max(func4[0][arr.length - 1], func5[0][arr.length -1]); } /** * 使用遞迴逆序棧 */ public static void reverseStackUsingRecursive(Stack<Integer> stack) { if (stack.isEmpty()) { return; } int i = getAndRemoveLastElement(stack); reverseStackUsingRecursive(stack); stack.push(i); } /** * 遞迴獲得棧底元素 * @param stack * @return */ public static int getAndRemoveLastElement(Stack<Integer> stack) { int result = stack.pop(); if (stack.isEmpty()) { return result; } else { int last = getAndRemoveLastElement(stack); stack.push(result); return last; } } /** * 數字轉化字串問題 * @param string * @return */ public static int number(String string) { if (string == null || string.length() == 0) { return 0; } return func6(string.toCharArray(), 0); } /** * 數字轉化字串問題遞迴處理函式 * @param chars * @param i * @return */ public static int func6(char[] chars, int i) { // 到末尾了,說明有一種可能 if (i == chars.length) { return 1; } // 0不能開頭,返回 if (chars[i] == '0') { return 0; } // 當前為1,存在後一位就多一種情況 if (chars[i] == '1') { int result = func6(chars, i + 1); if (i + 1 < chars.length) { result += func6(chars, i + 2); } return result; } // 當前為2,後一位存在,同時要求大於等於0並且小於等於6 if (chars[i] == '2') { int result = func6(chars, i + 1); if (i + 1 < chars.length && (chars[i + 1] >= '0' && chars[i + 1] <= '6')) { result += func6(chars, i + 2); } return result; } // 當前大於2 return func6(chars, i + 1); } /** * 揹包問題 * 遞迴方式 * @param weights 重量 * @param values 價值 * @param bag 容量 * @return */ public static int maxValue(int[] weights, int[] values, int bag) { return func7(weights, values, 0, 0, bag); } /** * 揹包問題的遞迴處理函式 * @param weights * @param values * @param i * @param alreadyweight * @param bag * @return */ public static int func7(int[] weights, int[] values, int i, int alreadyweight, int bag) { if (alreadyweight > bag) { return -1; } if (i == weights.length) { return 0; } int p1 = func7(weights, values, i + 1, alreadyweight, bag); int p2next = func7(weights, values, i + 1, alreadyweight + weights[i], bag); int p2 = -1; if (p2next != -1) { p2 = values[i] + p2next; } return Math.max(p1, p2); } /** * 揹包問題的非遞迴處理函式 * 記憶陣列 * @param weights * @param values * @param bag * @return */ public static int maxValue2(int[] weights, int[] values, int bag) { int[][] dp = new int[weights.length + 1][bag + 1]; for (int i = weights.length - 1; i >= 0; i--) { for (int j = bag; j >=0; j--) { dp[i][j] = dp[i + 1][j]; if (j + weights[i] <= bag) { dp[i][j] = Math.max(dp[i][j], values[i] + dp[i + 1][j + weights[i]]); } } } return dp[0][0]; } /** * N皇后問題 * 遞迴方法 * @param n * @return */ public static int nQueens(int n) { if (n < 1) { return 0; } int[] record = new int[n]; return func8(0, record, n); } /** * N皇后問題的遞迴處理函式 * @param i 行 * @param record 列記錄 * @param n * @return */ public static int func8(int i, int[] record, int n) { if (i == n) { return 1; } int result = 0; for (int j = 0; j < n; j++) { if (isValid(record, i, j)) { record[i] = j; result += func8(i + 1, record, n); } } return result; } /** * 判斷是否符合條件 * @param record 列記錄 * @param i 行 * @param j 列 * @return */ public static boolean isValid(int[] record, int i, int j) { // 遍歷某列 for (int k = 0; k < i; k++) { // 列記錄有值或同斜線(是否共斜線通過行差與列差相等來判斷) if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) { return false; } } return true; } /** * N皇后問題 * 非遞迴方法 * 當n <= 32時 * @param n * @return */ public static int nQueens2(int n) { if (n < 1 || n > 32) { return 0; } // 對應皇后可以放的列,1表示可以放,0表示不能放 int limit = n == 32 ? -1 : (1 << n) - 1; return func9(limit, 0, 0, 0); } /** * N皇后問題非遞迴處理函式 * @param limit 對應皇后可以放的列,1表示可以放,0表示不能放 * @param colLim 列的限制,1不能放,0可以 * @param leftDiaLim 左斜線的限制,1不能放,0可以 * @param rightDiaLim 右斜線的限制,1不能放,0可以 * @return */ public static int func9(int limit, int colLim, int leftDiaLim, int rightDiaLim) { if (colLim == limit) { return 1; } // int pos = 0; // 最右邊1的位置 int mostRightOne = 0; // colLim | leftDiaLim | rightDiaLim 表示所有的限制,1表示不能放 // 取反得到,0表示不能放,1表示能放 // 和limit相與,1表示能放,0表示不能放 pos = limit & (~(colLim | leftDiaLim | rightDiaLim)); int res = 0; while (pos != 0) { // 獲取最右邊的1的位置 mostRightOne = pos & (~pos + 1); // 放最右邊的1位置 pos = pos - mostRightOne; // 列限制、左限制、右限制更新 res += func9(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >>> 1); } return res; } public static void main(String[] args) { // 漢諾塔 int n = 3; hanoi(n); // 字串子序列 String test = "abc"; printAllSubsquence(test); // 字元創全排列 String string = "abc"; ArrayList<String> strings = printAllPermutations(string); System.out.println(strings); // 紙牌問題 int[] arr = { 1, 9, 1 }; System.out.println(cardsInLine(arr)); System.out.println(cardsInLine2(arr)); // 使用遞迴逆序棧 Stack<Integer> stack = new Stack<Integer>(); stack.push(1); stack.push(2); stack.push(3); stack.push(4); stack.push(5); reverseStackUsingRecursive(stack); while (!stack.isEmpty()) { System.out.println(stack.pop()); } // 數字轉化字串問題 System.out.println(number("101515610119")); // 揹包問題 int[] weights = { 3, 2, 4, 7 }; int[] values = { 5, 6, 3, 19 }; int bag = 11; int i = maxValue(weights, values, bag); System.out.println(maxValue(weights, values, bag)); System.out.println(maxValue2(weights, values, bag)); int n1 = 8; int i1 = nQueens(n1); System.out.println(i1); int i2 = nQueens2(n1); System.out.println(i2); } }