├── .gitignore ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main └── java └── cn └── mrcode └── study └── dsalgtutorialdemo ├── Demo.java ├── algorithm ├── binarysearchnorecursion │ └── BinarySearchNoRecur.java ├── dac │ └── Hanoitower.java ├── dijkstra │ └── DijkstraAlgorithm.java ├── dynamic │ └── KnapsackProblem.java ├── floyd │ └── FloydAlgorithm.java ├── greedy │ └── GreedyAlgorithm.java ├── horse │ └── HorseChessboard.java ├── kmp │ ├── KmpAlgorithm.java │ └── ViolenceMatch.java ├── kruskal │ └── KruskalCase.java └── prim │ └── PrimAlgorithm.java └── datastructure ├── avl └── AvlTreeTest.java ├── binarysorttree └── BinarySortTreeTest.java ├── graph └── GraphTest.java ├── hashtab └── HashTabTest.java ├── huffmancode ├── HuffmanCodeTest.java ├── my │ └── MyHuffmanCodeTest.java └── nel │ └── HuffmanCode.java ├── huffmantree └── HuffmanTreeTest.java ├── linkedlist ├── SingleLinkedListDemo.java ├── doublelist │ ├── DoubleLinkedList.java │ ├── DoubleLinkedListTest.java │ └── HeroNode.java └── josepfu │ ├── Boy.java │ ├── CircleSingleLinkedList.java │ └── JosepfuTest.java ├── queue ├── ArrayQueueDemo.java └── CircleQueueDemo.java ├── recursion ├── Queue8.java ├── RecursionTest.java └── maze │ └── Maze.java ├── search ├── BinarySearchTest.java ├── FibonacciSearchTest.java ├── InsertValueSearchTest.java └── SeqSearchTest.java ├── sort ├── bubble │ └── BubbleSortTest.java ├── insertion │ └── InsertionSortTest.java ├── merge │ ├── MergeSortTest.java │ └── MyMergeSortTest.java ├── quick │ └── QuickSortTest.java ├── radix │ └── RadixSortTest.java ├── select │ └── SelectSortTest.java └── shell │ └── ShellSortTest.java ├── sparsearray └── SparseArray.java ├── stack ├── array │ ├── ArrayStack.java │ └── ArrayStackTest.java ├── calculator │ ├── ArrayStack.java │ ├── Calculator.java │ ├── InfixToSuffix.java │ ├── ReversePolishCalculator.java │ ├── ReversePolishCalculatorTest.java │ └── ReversePolishMultiCalc.java └── linkedlist │ ├── LinkedListStack.java │ └── LinkedListStackTest.java └── tree ├── ArrBinaryTreeTest.java ├── BinaryTreeTest.java ├── HeapSortTest.java └── ThreadedBinaryTreeTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | /out/ 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'cn.mrcode.study' 6 | version '1.0' 7 | 8 | repositories { 9 | mavenLocal() 10 | maven { url "http://maven.aliyun.com/nexus/content/groups/public" } 11 | maven { url 'https://repo.spring.io/libs-snapshot' } 12 | maven { url "https://maven.repository.redhat.com/ga/" } 13 | maven { url "http://maven.nuiton.org/nexus/content/groups/releases/" } 14 | maven { url "https://repository.cloudera.com/artifactory/cloudera-repos/" } 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | testCompile group: 'junit', name: 'junit', version: '4.12' 20 | compile group: 'junit', name: 'junit', version: '4.12' 21 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zq99299/dsalg-tutorial-demo/d1dcd01a8ae3f30671e9a54b803e1beaebb1f424/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 02 22:07:48 CST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dsalg-tutorial-demo' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/Demo.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo; 2 | 3 | public class Demo { 4 | public static void main(String[] args) { 5 | int total = 0; 6 | int end = 100; 7 | // 使用 for 循环计算 8 | for (int i = 1; i <= end; i++) { 9 | total += i; 10 | } 11 | 12 | System.out.println(total); 13 | System.out.println((1 + end) * end / 2); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/binarysearchnorecursion/BinarySearchNoRecur.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.binarysearchnorecursion; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 二分查找:非递归 7 | */ 8 | public class BinarySearchNoRecur { 9 | @Test 10 | public void fun() { 11 | int[] arr = new int[]{1, 3, 8, 10, 11, 67, 100}; 12 | int target = 1; 13 | int result = binarySearch(arr, target); 14 | System.out.printf("查找 %d ,找位置为 %d \n", target, result); 15 | 16 | target = 11; 17 | result = binarySearch(arr, target); 18 | System.out.printf("查找 %d ,找位置为 %d \n", target, result); 19 | 20 | target = 100; 21 | result = binarySearch(arr, target); 22 | System.out.printf("查找 %d ,找位置为 %d \n", target, result); 23 | 24 | target = -1; 25 | result = binarySearch(arr, target); 26 | System.out.printf("查找 %d ,找位置为 %d \n", target, result); 27 | 28 | target = 200; 29 | result = binarySearch(arr, target); 30 | System.out.printf("查找 %d ,找位置为 %d \n", target, result); 31 | } 32 | 33 | /** 34 | * 二分查找:非递归 35 | * 36 | * @param arr 数组,前提:升序排列 37 | * @return 找到则返回下标,找不到则返回 -1 38 | */ 39 | public int binarySearch(int[] arr, int target) { 40 | int left = 0; 41 | int right = arr.length; 42 | int mid = 0; 43 | // 表示还可以进行查找 44 | while (left <= right) { 45 | mid = (left + right) / 2; 46 | if (mid >= arr.length // 查找的值大于数组中的最大值 47 | ) { 48 | // 防止越界 49 | return -1; 50 | } 51 | if (arr[mid] == target) { 52 | return mid; 53 | } 54 | // 升序:目标值比中间值大,则向左查找 55 | if (target > arr[mid]) { 56 | left = mid + 1; 57 | } else { 58 | // 否则:向右查找 59 | right = mid - 1; 60 | } 61 | } 62 | return -1; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/dac/Hanoitower.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.dac; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 汉诺塔算法 7 | */ 8 | public class Hanoitower { 9 | /** 10 | * 汉诺塔算法 11 | * 12 | * @param num 有几个盘子 13 | * @param a a 柱子 14 | * @param b b 柱子 15 | * @param c c 柱子 16 | */ 17 | public void hanoiTower(int num, char a, char b, char c) { 18 | // 当只有一个盘时:直接从 a -> c 19 | if (num == 1) { 20 | // System.out.printf("第 1 个盘从 %s → %s \n", a, c); 21 | System.out.printf("第 %d 个盘从 %s → %s \n", num, a, c); 22 | } else { 23 | // 否则,始终看成只有两个盘 24 | // 1. 最上面的盘:a -> b, 中间会用到 c 25 | // 因为最小规模是只有一个盘的时候,直接移动到 c 26 | hanoiTower(num - 1, a, c, b); 27 | // 2. 最下面的盘:a -> c 28 | System.out.printf("第 %d 个盘从 %s → %s \n", num, a, c); 29 | // 3. 把 B 塔所有的盘,移动到 c:b -> c, 移动过程中使用到 a 30 | hanoiTower(num - 1, b, a, c); 31 | } 32 | } 33 | 34 | @Test 35 | public void han1() { 36 | hanoiTower(1, 'A', 'B', 'C'); 37 | } 38 | 39 | @Test 40 | public void han2() { 41 | hanoiTower(2, 'A', 'B', 'C'); 42 | } 43 | 44 | @Test 45 | public void han3() { 46 | hanoiTower(3, 'A', 'B', 'C'); 47 | } 48 | 49 | @Test 50 | public void han4() { 51 | hanoiTower(4, 'A', 'B', 'C'); 52 | } 53 | 54 | @Test 55 | public void han5() { 56 | hanoiTower(5, 'A', 'B', 'C'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/dijkstra/DijkstraAlgorithm.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.dijkstra; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * 迪杰斯特拉算法-最短路径问题 9 | */ 10 | public class DijkstraAlgorithm { 11 | // 不连通的默认值 12 | int N = 100000; 13 | 14 | /** 15 | * 图:首先需要有一个带权的连通无向图 16 | */ 17 | class MGraph { 18 | int vertex; // 顶点个数 19 | int[][] weights; // 邻接矩阵 20 | char[] datas; // 村庄数据 21 | int edgeNum; // 共有多少条边 22 | 23 | /** 24 | * @param vertex 村庄数量, 会按照数量,按顺序生成村庄,如 A、B、C... 25 | * @param weights 需要你自己定义好那些点是连通的,那些不是连通的 26 | */ 27 | public MGraph(int vertex, int[][] weights) { 28 | this.vertex = vertex; 29 | this.weights = weights; 30 | 31 | this.datas = new char[vertex]; 32 | for (int i = 0; i < vertex; i++) { 33 | // 大写字母 A 从 65 开始 34 | datas[i] = (char) (65 + i); 35 | } 36 | // 计算有多少条边 37 | for (int i = 0; i < weights.length; i++) { 38 | /* 39 | A B C D E F G 40 | A 0 12 100000 100000 100000 16 14 41 | B 12 0 10 100000 100000 7 100000 42 | j = i + 1:比如: 43 | i=0,j=1, 那么就是 A,B 从而跳过了 A,A 44 | i=1,j=2, 那么就是 B,C 从而跳过了 B,A B,B 45 | 那么含义就出来了:跳过双向边的统计,也跳过自己对自己值得为 0 的统计 46 | */ 47 | for (int j = i + 1; j < weights.length; j++) { 48 | if (weights[i][j] != N) { 49 | edgeNum++; 50 | } 51 | } 52 | } 53 | } 54 | 55 | public void show() { 56 | System.out.printf("%-8s", " "); 57 | for (char vertex : datas) { 58 | // 控制字符串输出长度:少于 8 位的,右侧用空格补位 59 | System.out.printf("%-8s", vertex + " "); 60 | } 61 | System.out.println(); 62 | for (int i = 0; i < weights.length; i++) { 63 | System.out.printf("%-8s", datas[i] + " "); 64 | for (int j = 0; j < weights.length; j++) { 65 | System.out.printf("%-8s", weights[i][j] + " "); 66 | } 67 | System.out.println(); 68 | } 69 | } 70 | } 71 | 72 | @Test 73 | public void mGraphTest() { 74 | int[][] weights = new int[][]{ 75 | // A B C D E F G 76 | /*A*/ {N, 5, 7, N, N, N, 2}, 77 | /*B*/ {5, N, N, 9, N, N, 3}, 78 | /*C*/ {7, N, N, N, 8, N, N}, 79 | /*D*/ {N, 9, N, N, N, 4, N}, 80 | /*E*/ {N, N, 8, N, N, 5, 4}, 81 | /*F*/ {N, N, N, 4, 5, N, 6}, 82 | /*G*/ {2, 3, N, N, 4, 6, N} 83 | }; 84 | MGraph mGraph = new MGraph(7, weights); 85 | mGraph.show(); 86 | System.out.printf("共有 %d 条边 \n", mGraph.edgeNum); 87 | } 88 | 89 | /** 90 | * 从 G 出发 91 | */ 92 | @Test 93 | public void dijkstraTest() { 94 | int[][] weights = new int[][]{ 95 | // A B C D E F G 96 | /*A*/ {N, 5, 7, N, N, N, 2}, 97 | /*B*/ {5, N, N, 9, N, N, 3}, 98 | /*C*/ {7, N, N, N, 8, N, N}, 99 | /*D*/ {N, 9, N, N, N, 4, N}, 100 | /*E*/ {N, N, 8, N, N, 5, 4}, 101 | /*F*/ {N, N, N, 4, 5, N, 6}, 102 | /*G*/ {2, 3, N, N, 4, 6, N} 103 | }; 104 | MGraph mGraph = new MGraph(7, weights); 105 | mGraph.show(); 106 | System.out.printf("共有 %d 条边 \n", mGraph.edgeNum); 107 | 108 | dijkstra(mGraph, 'G'); 109 | } 110 | 111 | // 记录各个顶点是否访问过 112 | private boolean[] already_arr; 113 | // 记录每个下标对应的值为前一个顶点下标(前驱节点) 114 | private int[] pre_visited_arr; 115 | // 记录出发点到其他所有顶点的距离 116 | private int[] dis_arr; 117 | private MGraph mGraph; 118 | 119 | private void dijkstra(MGraph mGraph, char start) { 120 | this.mGraph = mGraph; 121 | // 三个数组的长度为 顶点的个数 122 | already_arr = new boolean[mGraph.vertex]; 123 | pre_visited_arr = new int[mGraph.vertex]; 124 | dis_arr = new int[mGraph.vertex]; 125 | // 找到开始节点的下标 126 | int v = 0; 127 | for (int i = 0; i < mGraph.datas.length; i++) { 128 | if (mGraph.datas[i] == start) { 129 | v = i; 130 | break; 131 | } 132 | } 133 | // 初始化所有前驱节点为默认状态,使用不可连通的 N 值表示 134 | Arrays.fill(pre_visited_arr, N); 135 | // 标记开始节点为访问状态 136 | already_arr[v] = true; 137 | // 标记前驱节点为:笔记中有歧义,我们使用 N 表示没有前驱节点 138 | // v 是开始节点,那么它就没有前驱节点 139 | pre_visited_arr[v] = N; 140 | // 初始化从起点到到所有点的距离为最大值,后续方便通过它来与新路径距离比较 141 | Arrays.fill(dis_arr, N); 142 | // 初始化,当前访问节点的距离为 0 143 | dis_arr[v] = 0; 144 | 145 | // 准备工作完成:开始查找最短路径 146 | // 广度优先策略:从起始节点计算它能直达的点的所有距离 147 | update(v); 148 | // 一共只需要计算 6 层: 7 个站点 -1 149 | for (char data : mGraph.datas) { 150 | // 寻找下一个访问节点 151 | int index = findNext(); 152 | // 标记该节点被访问过,然后再计算与它直连点的路径 153 | already_arr[index] = true; 154 | // 并继续计算路径 155 | update(index); 156 | } 157 | 158 | // 所有节点都访问过之后:dis_arr 中就保留了从起点 到各个点的最短距离 159 | System.out.println(Arrays.toString(already_arr)); 160 | System.out.println(Arrays.toString(pre_visited_arr)); 161 | System.out.println(Arrays.toString(dis_arr)); 162 | 163 | System.out.println("从 " + start + " 到以下点的最短距离为:"); 164 | // 为了结果好认一点,格式化最后的结果 165 | for (int i = 0; i < dis_arr.length; i++) { 166 | System.out.printf("%S(%d) ", mGraph.datas[i], dis_arr[i]); 167 | } 168 | System.out.println(); 169 | } 170 | 171 | /** 172 | * 计算起点到:当前节点所有能直连的节点的距离 173 | * 174 | * @param v 175 | */ 176 | private void update(int v) { 177 | int[][] weights = mGraph.weights; // 我们的邻接矩阵图 178 | 179 | int len = 0; 180 | // weights[v]:由于是广度优先,所以每次只计算与该点能直连的点,也就是该点所在的一行 181 | for (int i = 0; i < weights[v].length; i++) { 182 | if (weights[v][i] == N) { // 不能直连,跳过 183 | continue; 184 | } 185 | // 计算从起点到当前要连通节点的距离 = 起点到当前访问节点的距离 + 访问节点到直连节点的距离 186 | len = dis_arr[v] + weights[v][i]; 187 | 188 | // 首先:起点G -> A, A 要没有被访问过 189 | // 其次:如果当前计算新的路径距离 小于 已经存在的 从 起点 G -> 当前计算点的距离 190 | // 说明之前可能从其他途径到达了 i 点,这个距离是比现在找到的距离远 191 | // 当前的近,那么就更新数组中的数据 192 | if (!already_arr[i] && len < dis_arr[i]) { 193 | dis_arr[i] = len; 194 | pre_visited_arr[i] = v; // 更改前驱节点,表示 经过了 v 这个点(当前正在访问的点),到达的 i 点 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * 广度优先策略一层计算完成之后,寻找下一个节点再计算 201 | * 202 | * @return 203 | */ 204 | private int findNext() { 205 | int min = N, index = 0; 206 | // 总共需要找 n 此 207 | for (int i = 0; i < already_arr.length; i++) { 208 | // 该节点没有被访问过 209 | // 并且:从起点到达该节点的距离是最小的 210 | // 如果是第一层执行完成之后:那么有值的则只有:与起点能直连的那几个 211 | // 这里就类似与:原来广度优先中使用队列来保存搜索路径了 212 | if (!already_arr[i] && dis_arr[i] < min) { 213 | min = dis_arr[i]; 214 | index = i; 215 | } 216 | } 217 | return index; 218 | } 219 | 220 | /** 221 | * 从 c 出发 222 | */ 223 | @Test 224 | public void dijkstraTest2() { 225 | int[][] weights = new int[][]{ 226 | // A B C D E F G 227 | /*A*/ {N, 5, 7, N, N, N, 2}, 228 | /*B*/ {5, N, N, 9, N, N, 3}, 229 | /*C*/ {7, N, N, N, 8, N, N}, 230 | /*D*/ {N, 9, N, N, N, 4, N}, 231 | /*E*/ {N, N, 8, N, N, 5, 4}, 232 | /*F*/ {N, N, N, 4, 5, N, 6}, 233 | /*G*/ {2, 3, N, N, 4, 6, N} 234 | }; 235 | MGraph mGraph = new MGraph(7, weights); 236 | mGraph.show(); 237 | System.out.printf("共有 %d 条边 \n", mGraph.edgeNum); 238 | 239 | dijkstra(mGraph, 'C'); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/dynamic/KnapsackProblem.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.dynamic; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * 动态规划 9 | */ 10 | public class KnapsackProblem { 11 | /** 12 | * 填表法:输出填表后的信息 13 | */ 14 | @Test 15 | public void table() { 16 | // 商品价值和重量前面都有一个 0 ,方便后续的公式写法 17 | int[] val = {0, 1500, 3000, 2000}; // 商品价值 18 | int[] w = {0, 1, 4, 3}; // 商品对应重量 19 | int m = 4; // 背包容量 20 | int n = val.length; // 物品个数 21 | // 构建初始表格, +1 是因为有一行列和行都是 0 22 | // v[i][j] 存放的是:前 i 个物品中能够装入容量为 j 的背包中的最大价值 23 | int[][] v = new int[n][m + 1]; 24 | 25 | // 1. 初始化 v[i][0] = v[0][j]=0 26 | // 本程序中,可以不初始化,默认就是 0 27 | // 为了体现步骤,进行初始化(有可能在你的场景中有其他的含义) 28 | // 为了与默认值 0 区分开,看出初始化效果,这里默 0 定义为 -1 29 | // 验证之后,修改回 0,否则在计算上一个格子的时候,就会导致计算错误 30 | int zero = 0; 31 | for (int i = 0; i < v.length; i++) { 32 | for (int j = 0; j < v[0].length; j++) { 33 | v[i][0] = zero; // 初始化第一列为 0 34 | v[0][i] = zero; // 初始化第一行为 0 35 | } 36 | } 37 | print(v); 38 | 39 | // 开始填表:动态规划处理 40 | for (int i = 1; i < v.length; i++) { // 第一行 0 不处理,从 1 开始 41 | // 一个商品一个容量进行尝试 42 | for (int j = 1; j < v[0].length; j++) { // 第一列 0 不处理,从 1 开始 43 | // 当当前物品重量大于,背包限定重量时: 44 | // 当前背包容量,可存放最大的商品价值为:前一格子中的存放策略总价值 45 | if (w[i] > j) { 46 | v[i][j] = v[i - 1][j]; 47 | } 48 | // 当当前背包容量 大于 当前物品重量时: 49 | // 说明:还有空余空间存放其他产品 50 | else { 51 | // max{v[i - 1][j],v[i - 1][j - w[i]] + v[i]}` 52 | int pre = v[i - 1][j]; // 前一个格子中存放策略总价值 53 | int curr = val[i]; // i当前商品总价值 54 | int free = v[i - 1][j - w[i]]; // 存放完当前商品后,剩余空间能存放的总价值 55 | // 当前格子:只会存放比上一个格子总价值大的策略价值 56 | v[i][j] = Math.max(pre, curr + free); 57 | } 58 | } 59 | } 60 | System.out.println("动态规划后"); 61 | print(v); 62 | } 63 | 64 | /** 65 | * 填表法:输出填表后的信息,包括最优存放的商品信息 66 | */ 67 | @Test 68 | public void tableAndProduct() { 69 | // 商品价值和重量前面都有一个 0 ,方便后续的公式写法 70 | int[] val = {0, 1500, 3000, 2000}; // 商品价值 71 | int[] w = {0, 1, 4, 3}; // 商品对应重量 72 | int m = 4; // 背包容量 73 | int n = val.length; // 物品个数 74 | // 构建初始表格, +1 是因为有一行列和行都是 0 75 | // v[i][j] 存放的是:前 i 个物品中能够装入容量为 j 的背包中的最大价值 76 | int[][] v = new int[n][m + 1]; 77 | 78 | int zero = 0; 79 | for (int i = 0; i < v.length; i++) { 80 | for (int j = 0; j < v[0].length; j++) { 81 | v[i][0] = zero; // 初始化第一列为 0 82 | v[0][i] = zero; // 初始化第一行为 0 83 | } 84 | } 85 | print(v); 86 | 87 | // 用于存放每个格子中保存的商品 88 | int[][] path = new int[n][m + 1]; 89 | 90 | // 开始填表:动态规划处理 91 | for (int i = 1; i < v.length; i++) { // 第一行 0 不处理,从 1 开始 92 | // 一个商品一个容量进行尝试 93 | for (int j = 1; j < v[0].length; j++) { // 第一列 0 不处理,从 1 开始 94 | // 当当前物品重量大于,背包限定重量时: 95 | // 当前背包容量,可存放最大的商品价值为:前一格子中的存放策略总价值 96 | if (w[i] > j) { 97 | v[i][j] = v[i - 1][j]; 98 | } 99 | // 当当前背包容量 大于 当前物品重量时: 100 | // 说明:还有空余空间存放其他产品 101 | else { 102 | // max{v[i - 1][j],v[i - 1][j - w[i]] + v[i]}` 103 | int pre = v[i - 1][j]; // 前一个格子中存放策略总价值 104 | int curr = val[i]; // i当前商品总价值 105 | int free = v[i - 1][j - w[i]]; // 存放完当前商品后,剩余空间能存放的总价值 106 | // 当前格子:只会存放比上一个格子总价值大的策略价值 107 | // v[i][j] = Math.max(pre, curr + free); 108 | // 当需要存放新的策略时,标记当前路径 109 | if (pre < curr + free) { 110 | v[i][j] = curr + free; 111 | path[i][j] = 1; 112 | } else { 113 | v[i][j] = pre; 114 | } 115 | } 116 | } 117 | } 118 | System.out.println("动态规划后"); 119 | print(v); 120 | 121 | System.out.println("存放路径"); 122 | print(path); 123 | 124 | System.out.println("提取最优商品存放信息"); 125 | int i = path.length - 1; // 行的最大下标 126 | int j = path[0].length - 1; // 列的最大下标 127 | // 从 path 最后开始往前找 128 | while (i > 0 && j > 0) { 129 | if (path[i][j] == 1) { 130 | System.out.printf("第 %d 个商品放入背包,坐标[%d,%d] \n", i, i, j); 131 | // 当前格子商品组成为:当前商品重量 + 剩余重量 132 | // 所以:要重置 j 为剩余重量 133 | j = j - w[i]; 134 | } 135 | i--; 136 | } 137 | } 138 | 139 | // 封装成方法,测试更多的商品 140 | @Test 141 | public void tableAndProduct2() { 142 | int[] val = {0, 1500, 3000, 2000, 200}; // 商品价值 143 | int[] w = {0, 1, 4, 3, 1}; // 商品对应重量 144 | int m = 5; // 背包容量 145 | knapsackProblem(val, w, m); 146 | } 147 | 148 | /** 149 | * 背包问题:动态规划 150 | * 151 | * @param val 商品价值,第 0 个为没有商品 152 | * @param w 商品重量,第 0 个为没有重量 153 | * @param m 背包容量 154 | */ 155 | public void knapsackProblem(int[] val, int[] w, int m) { 156 | // 商品价值和重量前面都有一个 0 ,方便后续的公式写法 157 | int n = val.length; // 物品个数 158 | // 构建初始表格, +1 是因为有一行列和行都是 0 159 | // v[i][j] 存放的是:前 i 个物品中能够装入容量为 j 的背包中的最大价值 160 | int[][] v = new int[n][m + 1]; 161 | 162 | int zero = 0; 163 | for (int i = 0; i < v.length; i++) { 164 | for (int j = 0; j < v[0].length; j++) { 165 | v[i][0] = zero; // 初始化第一列为 0 166 | v[0][i] = zero; // 初始化第一行为 0 167 | } 168 | } 169 | print(v); 170 | 171 | // 用于存放每个格子中保存的商品 172 | int[][] path = new int[n][m + 1]; 173 | 174 | // 开始填表:动态规划处理 175 | for (int i = 1; i < v.length; i++) { // 第一行 0 不处理,从 1 开始 176 | // 一个商品一个容量进行尝试 177 | for (int j = 1; j < v[0].length; j++) { // 第一列 0 不处理,从 1 开始 178 | // 当当前物品重量大于,背包限定重量时: 179 | // 当前背包容量,可存放最大的商品价值为:前一格子中的存放策略总价值 180 | if (w[i] > j) { 181 | v[i][j] = v[i - 1][j]; 182 | } 183 | // 当当前背包容量 大于 当前物品重量时: 184 | // 说明:还有空余空间存放其他产品 185 | else { 186 | // max{v[i - 1][j],v[i - 1][j - w[i]] + v[i]}` 187 | int pre = v[i - 1][j]; // 前一个格子中存放策略总价值 188 | int curr = val[i]; // i当前商品总价值 189 | int free = v[i - 1][j - w[i]]; // 存放完当前商品后,剩余空间能存放的总价值 190 | // 当前格子:只会存放比上一个格子总价值大的策略价值 191 | // v[i][j] = Math.max(pre, curr + free); 192 | // 当需要存放新的策略时,标记当前路径 193 | if (pre < curr + free) { 194 | v[i][j] = curr + free; 195 | path[i][j] = 1; 196 | } else { 197 | v[i][j] = pre; 198 | } 199 | } 200 | } 201 | } 202 | System.out.println("动态规划后"); 203 | print(v); 204 | 205 | System.out.println("存放路径"); 206 | print(path); 207 | 208 | System.out.println("提取最优商品存放信息"); 209 | int i = path.length - 1; // 行的最大下标 210 | int j = path[0].length - 1; // 列的最大下标 211 | // 从 path 最后开始往前找 212 | while (i > 0 && j > 0) { 213 | if (path[i][j] == 1) { 214 | System.out.printf("第 %d 个商品放入背包,坐标[%d,%d] \n", i, i, j); 215 | // 当前格子商品组成为:当前商品重量 + 剩余重量 216 | // 所以:要重置 j 为剩余重量 217 | j = j - w[i]; 218 | } 219 | i--; 220 | } 221 | } 222 | 223 | /** 224 | * 查看和给定商品列表顺序结果是否有变化:结论是没有变化,放入的商品都是同一个 225 | */ 226 | @Test 227 | public void tableAndProduct3() { 228 | int[] val = {0, 1500, 3000, 2000, 1500}; // 商品价值 229 | int[] w = {0, 1, 4, 3, 1}; // 商品对应重量 230 | int m = 5; // 背包容量 231 | knapsackProblem(val, w, m); 232 | } 233 | 234 | @Test 235 | public void tableAndProduct4() { 236 | int[] val = {0, 1500, 1500, 3000, 2000}; // 商品价值 237 | int[] w = {0, 1, 1, 4, 3}; // 商品对应重量 238 | int m = 5; // 背包容量 239 | knapsackProblem(val, w, m); 240 | } 241 | 242 | /** 243 | * 打印填表信息 244 | * 245 | * @param table 246 | */ 247 | private void print(int[][] table) { 248 | for (int i = 0; i < table.length; i++) { 249 | System.out.println(Arrays.toString(table[i])); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/floyd/FloydAlgorithm.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.floyd; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * 佛洛依德算法-最短路径 9 | */ 10 | public class FloydAlgorithm { 11 | /** 12 | * 图:首先需要有一个带权的连通无向图 13 | */ 14 | class MGraph { 15 | int vertex; // 顶点个数 16 | int[][] weights; // 邻接矩阵 17 | char[] datas; // 村庄数据 18 | 19 | /** 20 | * @param vertex 村庄数量, 会按照数量,按顺序生成村庄,如 A、B、C... 21 | * @param weights 需要你自己定义好那些点是连通的,那些不是连通的 22 | */ 23 | public MGraph(int vertex, int[][] weights) { 24 | this.vertex = vertex; 25 | this.weights = weights; 26 | 27 | this.datas = new char[vertex]; 28 | for (int i = 0; i < vertex; i++) { 29 | // 大写字母 A 从 65 开始 30 | datas[i] = (char) (65 + i); 31 | } 32 | } 33 | 34 | public void show() { 35 | System.out.printf("%-8s", " "); 36 | for (char vertex : datas) { 37 | // 控制字符串输出长度:少于 8 位的,右侧用空格补位 38 | System.out.printf("%-8s", vertex + " "); 39 | } 40 | System.out.println(); 41 | for (int i = 0; i < weights.length; i++) { 42 | System.out.printf("%-8s", datas[i] + " "); 43 | for (int j = 0; j < weights.length; j++) { 44 | System.out.printf("%-8s", weights[i][j] + " "); 45 | } 46 | System.out.println(); 47 | } 48 | } 49 | } 50 | 51 | @Test 52 | public void mGraphTest() { 53 | // 不连通的默认值: 54 | // 这里设置为较大的数,是为了后续的计算方便,计算权值的时候,不会选择 55 | int defaultNo = 100000; 56 | int[][] weights = new int[][]{ 57 | {defaultNo, 5, 7, defaultNo, defaultNo, defaultNo, 2}, // A 58 | {5, defaultNo, defaultNo, 9, defaultNo, defaultNo, 3},// B 59 | {7, defaultNo, defaultNo, defaultNo, 8, defaultNo, defaultNo},// C 60 | {defaultNo, 9, defaultNo, defaultNo, defaultNo, 4, defaultNo},// D 61 | {defaultNo, defaultNo, 8, defaultNo, defaultNo, 5, 4},// E 62 | {defaultNo, defaultNo, defaultNo, 4, 5, defaultNo, 6},// F 63 | {2, 3, defaultNo, defaultNo, 4, 6, defaultNo}// G 64 | }; 65 | MGraph mGraph = new MGraph(7, weights); 66 | mGraph.show(); 67 | } 68 | 69 | int defaultNo = 100000; 70 | 71 | @Test 72 | public void floydTest() { 73 | int[][] weights = new int[][]{ 74 | {defaultNo, 5, 7, defaultNo, defaultNo, defaultNo, 2}, // A 75 | {5, defaultNo, defaultNo, 9, defaultNo, defaultNo, 3},// B 76 | {7, defaultNo, defaultNo, defaultNo, 8, defaultNo, defaultNo},// C 77 | {defaultNo, 9, defaultNo, defaultNo, defaultNo, 4, defaultNo},// D 78 | {defaultNo, defaultNo, 8, defaultNo, defaultNo, 5, 4},// E 79 | {defaultNo, defaultNo, defaultNo, 4, 5, defaultNo, 6},// F 80 | {2, 3, defaultNo, defaultNo, 4, 6, defaultNo}// G 81 | }; 82 | MGraph mGraph = new MGraph(7, weights); 83 | mGraph.show(); 84 | floyd(mGraph); 85 | 86 | showFloydDis(); 87 | showFloydPre(); 88 | showFormat(); 89 | } 90 | 91 | private char[] vertexs; // 存放顶点 92 | private int[][] dis; // 从各个顶点出发到其他顶点的距离 93 | private int[][] pre; // 到达目标顶点的前驱顶点 94 | 95 | public void floyd(MGraph mGraph) { 96 | vertexs = mGraph.datas; 97 | dis = mGraph.weights; 98 | pre = new int[mGraph.vertex][mGraph.vertex]; 99 | // 初始化 pre 100 | for (int i = 0; i < pre.length; i++) { 101 | Arrays.fill(pre[i], i); 102 | } 103 | 104 | // 从中间顶点的遍历 105 | for (int i = 0; i < vertexs.length; i++) { 106 | // 出发顶点 107 | for (int j = 0; j < vertexs.length; j++) { 108 | // 终点 109 | for (int k = 0; k < vertexs.length; k++) { 110 | // 中间节点连接: 从 j 到 i 到 k 的距离 111 | int lji = dis[j][i]; 112 | int lik = dis[i][k]; 113 | int leng = lji + lik; 114 | 115 | // 直连 116 | int ljk = dis[j][k]; 117 | 118 | // 如果间接距离比直连短,则更新 119 | if (leng < ljk) { 120 | dis[j][k] = leng; 121 | /* 122 | 最难理解的是这里: 123 | i 是已知的中间节点,前驱的时候直接设置为 i (pre[j][k] = i;) ,结果是不对的。 124 | 比如:A-G-F-D , 中间节点是是 两个节点,那么 A 到 D 的前驱节点是 F,而不是 G 125 | 如果直接赋值 i,前驱节点就会计算错误。 126 | 理解步骤为: 127 | 1. A-G-F:距离 8 128 | A-F : 不能直连 129 | 那么设置:A,F 的前驱节点是 G; 对应这里的代码是 j,i 130 | 2. G-F-D: 距离是 10 131 | G-D:不能直连 132 | 那么设置:G,D 的前驱节点是 F; 对应这里的代码是 i,k 133 | 3. 那么最终 A,D 的前驱节点是是什么呢? 134 | 其实就应该是 G,D 指向的值; 对应这里的代码是 i,k 135 | */ 136 | pre[j][k] = pre[i][k]; // 前驱节点更新为中间节点 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * 显示 dis 和 pre,这个数据也是最后的结果数据 145 | */ 146 | public void showFloydDis() { 147 | System.out.println("dis 结果"); 148 | show(dis); 149 | } 150 | 151 | public void showFloydPre() { 152 | System.out.println("pre 结果"); 153 | show(pre); 154 | } 155 | 156 | public void show(int[][] weights) { 157 | System.out.printf("%-8s", " "); 158 | for (char vertex : vertexs) { 159 | // 控制字符串输出长度:少于 8 位的,右侧用空格补位 160 | System.out.printf("%-8s", vertex + " "); 161 | } 162 | System.out.println(); 163 | for (int i = 0; i < weights.length; i++) { 164 | System.out.printf("%-8s", vertexs[i] + " "); 165 | for (int j = 0; j < weights.length; j++) { 166 | System.out.printf("%-8s", weights[i][j] + " "); 167 | } 168 | System.out.println(); 169 | } 170 | } 171 | 172 | /** 173 | * 直接打印出我们的结果 174 | */ 175 | public void showFormat() { 176 | System.out.println("最终结果格式化显示:"); 177 | for (int i = 0; i < dis.length; i++) { 178 | // 先将 pre 数组输出一行 179 | System.out.println(vertexs[i] + " 到其他顶点的最短距离"); 180 | // 输出 dis 数组的一行数据 181 | // 每一行数据是,一个顶点,到达其他顶点的最短路径 182 | for (int k = 0; k < dis.length; k++) { 183 | System.out.printf("%-16s", vertexs[i] + " → " + vertexs[k] + " = " + dis[i][k] + ""); 184 | } 185 | System.out.println(); 186 | System.out.println(); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/greedy/GreedyAlgorithm.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.greedy; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.*; 6 | 7 | /** 8 | * 贪心算法 9 | */ 10 | public class GreedyAlgorithm { 11 | 12 | /** 13 | * 构建广播电台 与 覆盖的地区 14 | *
 15 |      * k: 电台
 16 |      * v:覆盖的地区
 17 |      * 
18 | * 19 | * @return 20 | */ 21 | public Map> buildBroadcasts() { 22 | Map> broadcasts = new HashMap<>(); 23 | Set k1 = new HashSet<>(); 24 | k1.add("北京"); 25 | k1.add("上海"); 26 | k1.add("天津"); 27 | Set k2 = new HashSet<>(); 28 | k2.add("广州"); 29 | k2.add("北京"); 30 | k2.add("深圳"); 31 | Set k3 = new HashSet<>(); 32 | k3.add("成都"); 33 | k3.add("上海"); 34 | k3.add("杭州"); 35 | Set k4 = new HashSet<>(); 36 | k4.add("上海"); 37 | k4.add("天津"); 38 | Set k5 = new HashSet<>(); 39 | k5.add("杭州"); 40 | k5.add("大连"); 41 | 42 | broadcasts.put("k1", k1); 43 | broadcasts.put("k2", k2); 44 | broadcasts.put("k3", k3); 45 | broadcasts.put("k4", k4); 46 | broadcasts.put("k5", k5); 47 | 48 | return broadcasts; 49 | } 50 | 51 | /** 52 | * 贪心算法: 选择最少的电台,覆盖所有的地区 53 | * 54 | * @param broadcasts 电台 55 | * @return 返回选择的电台列表 56 | */ 57 | public Set greedy(Map> broadcasts) { 58 | // 构建待覆盖的所有地区 59 | Set allAreas = new HashSet<>(); 60 | broadcasts.forEach((k, v) -> { 61 | allAreas.addAll(v); 62 | }); 63 | System.out.println("需要覆盖的地区:" + allAreas); 64 | 65 | // 存放已选择的电台 66 | Set selects = new HashSet<>(); 67 | 68 | // 当所有需要覆盖的地区还有时,则可以继续选择 69 | 70 | String maxKey = null; // 当次覆盖地区最多的电台 71 | int maxKeyCoverNum = 0; // maxKey 覆盖的数量 72 | Set temp = new HashSet<>(); // 临时变量,用于计算电台中的覆盖地区:在要未覆盖地区中 覆盖的数量 73 | while (!allAreas.isEmpty()) { 74 | // 选择出当次还未选择中:覆盖地区最多的电台 75 | for (String key : broadcasts.keySet()) { 76 | Set areas = broadcasts.get(key); 77 | temp.addAll(areas); 78 | temp.retainAll(allAreas); 79 | // 如果:当前尝试选择的电台,覆盖数量比 maxKey 还大,则把它设置为 maxKey 80 | if (temp.size() > 0 && temp.size() > maxKeyCoverNum) { 81 | maxKey = key; 82 | maxKeyCoverNum = temp.size(); 83 | } 84 | temp.clear(); 85 | } 86 | if (maxKey == null) { 87 | continue; 88 | } 89 | // 循环完成后,找到了本轮的 maxKey 90 | // 添加到已选择列表中,并且从 未覆盖列表 中删除已经覆盖过的地区 91 | selects.add(maxKey); 92 | allAreas.removeAll(broadcasts.get(maxKey)); 93 | // 清空临时变量,方便下次查找 94 | maxKey = null; 95 | maxKeyCoverNum = 0; 96 | } 97 | return selects; 98 | } 99 | 100 | @Test 101 | public void fun() { 102 | Map> broadcasts = buildBroadcasts(); 103 | System.out.println("电台列表" + broadcasts); 104 | Set greedy = greedy(broadcasts); 105 | System.out.println("选择好的电台列表:" + greedy); 106 | } 107 | 108 | /** 109 | * 贪心算法: 选择最 多 的电台,覆盖所有的地区 110 | * 111 | * @param broadcasts 电台 112 | * @return 返回选择的电台列表 113 | */ 114 | public Set greedy2(Map> broadcasts) { 115 | // 构建待覆盖的所有地区 116 | Set allAreas = new HashSet<>(); 117 | broadcasts.forEach((k, v) -> { 118 | allAreas.addAll(v); 119 | }); 120 | System.out.println("需要覆盖的地区:" + allAreas); 121 | 122 | // 存放已选择的电台 123 | Set selects = new HashSet<>(); 124 | 125 | // 当所有需要覆盖的地区还有时,则可以继续选择 126 | 127 | String maxKey = ""; // 当次覆盖地区最多的电台 128 | int maxKeyCoverNum = 0; // maxKey 覆盖的数量 129 | Set temp = new HashSet<>(); // 临时变量,用于计算电台中的覆盖地区:在要未覆盖地区中 覆盖的数量 130 | while (!allAreas.isEmpty()) { 131 | // 选择出当次还未选择中:覆盖地区最多的电台 132 | for (String key : broadcasts.keySet()) { 133 | // 跳过已选择过的电台: 否则:当在选择最多电台,覆盖所有电台的策略时,将还会匹配到已选择的,导致死循环 134 | if (selects.contains(key)) { 135 | continue; 136 | } 137 | 138 | Set areas = broadcasts.get(key); 139 | temp.addAll(areas); 140 | temp.retainAll(allAreas); 141 | 142 | if (temp.size() < maxKeyCoverNum) { 143 | maxKey = key; 144 | maxKeyCoverNum = temp.size(); 145 | } else if (maxKeyCoverNum == 0) { 146 | maxKey = key; 147 | maxKeyCoverNum = temp.size(); 148 | } 149 | 150 | temp.clear(); 151 | } 152 | // 循环完成后,找到了本轮的 maxKey 153 | // 添加到已选择列表中,并且从 未覆盖列表 中删除已经覆盖过的地区 154 | selects.add(maxKey); 155 | allAreas.removeAll(broadcasts.get(maxKey)); 156 | // 清空临时变量,方便下次查找 157 | maxKey = ""; 158 | maxKeyCoverNum = 0; 159 | } 160 | return selects; 161 | } 162 | 163 | /** 164 | * 选择最多的电台,覆盖所有地区 165 | */ 166 | @Test 167 | public void fun2() { 168 | Map> broadcasts = buildBroadcasts(); 169 | System.out.println("电台列表" + broadcasts); 170 | Set greedy = greedy2(broadcasts); 171 | System.out.println("选择好的电台列表:" + greedy); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/horse/HorseChessboard.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.horse; 2 | 3 | import org.junit.Test; 4 | 5 | import java.awt.*; 6 | import java.time.Duration; 7 | import java.time.Instant; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | 11 | 12 | /** 13 | * 骑士周游问题-马踏棋盘算法 14 | */ 15 | public class HorseChessboard { 16 | @Test 17 | public void nextTest() { 18 | // 测试 19 | Point current = new Point(4, 4); 20 | System.out.println(current + " 下一步可选位置有:"); 21 | ArrayList next = next(current); 22 | System.out.println(next); 23 | 24 | // 测试一个 0,0 25 | current = new Point(0, 0); 26 | System.out.println(current + " 下一步可选位置有:"); 27 | next = next(current); 28 | System.out.println(next); 29 | } 30 | 31 | int X = 8; // 棋盘的行数 32 | int Y = 8; // 棋盘的列数 33 | 34 | /** 35 | * 根据马儿所在的位置,查找它下一步可以走的位置; 从 0 ~ 7 的策略,耗时很长很长,回溯太久了,好几分钟都出不来 36 | * 37 | * @param current 38 | * @return 39 | */ 40 | public ArrayList next1(Point current) { 41 | ArrayList result = new ArrayList<>(); 42 | int cx = current.x; 43 | int cy = current.y; 44 | // 第 0 个点 45 | if (cx - 1 >= 0 && cy + 2 < Y) { 46 | result.add(new Point(cx - 1, cy + 2)); 47 | } 48 | // 第 1 个点 49 | if (cx + 1 < X && cy + 2 < Y) { 50 | result.add(new Point(cx + 1, cy + 2)); 51 | } 52 | // 第 2 个点 53 | if (cx + 2 < X && cy - 1 >= 0) { 54 | result.add(new Point(cx + 2, cy - 1)); 55 | } 56 | // 第 3 个点 57 | if (cx + 2 < X && cy + 1 < Y) { 58 | result.add(new Point(cx + 2, cy + 1)); 59 | } 60 | // 第 4 个点 61 | if (cx + 1 < X && cy - 2 >= 0) { 62 | result.add(new Point(cx + 1, cy - 2)); 63 | } 64 | // 第 5 个点 65 | if (cx - 1 >= 0 && cy - 2 >= 0) { 66 | result.add(new Point(cx - 1, cy - 2)); 67 | } 68 | // 第 6 个点 69 | if (cx - 2 >= 0 && cy - 1 >= 0) { 70 | result.add(new Point(cx - 2, cy - 1)); 71 | } 72 | // 第 7 个点 73 | if (cx - 2 >= 0 && cy + 1 < Y) { 74 | result.add(new Point(cx - 2, cy + 1)); 75 | } 76 | return result; 77 | } 78 | 79 | /** 80 | * 从 5 ~ 7,7 ~ 0 ,这个策略大概需要 44 秒 81 | * 82 | * @param current 83 | * @return 84 | */ 85 | public ArrayList next(Point current) { 86 | ArrayList result = new ArrayList<>(); 87 | int cx = current.x; 88 | int cy = current.y; 89 | // 第 5 个点 90 | if (cx - 1 >= 0 && cy - 2 >= 0) { 91 | result.add(new Point(cx - 1, cy - 2)); 92 | } 93 | // 第 6 个点 94 | if (cx - 2 >= 0 && cy - 1 >= 0) { 95 | result.add(new Point(cx - 2, cy - 1)); 96 | } 97 | // 第 7 个点 98 | if (cx - 2 >= 0 && cy + 1 < Y) { 99 | result.add(new Point(cx - 2, cy + 1)); 100 | } 101 | // 第 0 个点 102 | if (cx - 1 >= 0 && cy + 2 < Y) { 103 | result.add(new Point(cx - 1, cy + 2)); 104 | } 105 | // 第 1 个点 106 | if (cx + 1 < X && cy + 2 < Y) { 107 | result.add(new Point(cx + 1, cy + 2)); 108 | } 109 | // 第 2 个点 110 | if (cx + 2 < X && cy - 1 >= 0) { 111 | result.add(new Point(cx + 2, cy - 1)); 112 | } 113 | // 第 3 个点 114 | if (cx + 2 < X && cy + 1 < Y) { 115 | result.add(new Point(cx + 2, cy + 1)); 116 | } 117 | // 第 4 个点 118 | if (cx + 1 < X && cy - 2 >= 0) { 119 | result.add(new Point(cx + 1, cy - 2)); 120 | } 121 | return result; 122 | } 123 | 124 | private boolean finished; // 是否已经完成,由于是递归,在某一步已经完成,回溯时就可以跳过 125 | private boolean[] visited = new boolean[X * Y]; // 标记是否访问过,访问过的不再访问 126 | 127 | /** 128 | * 马踏棋盘算法核心代码 129 | * 130 | * @param chessboard 棋盘,用于标识哪一个点是第几步走的 131 | * @param cx 当前要尝试访问的点 x 坐标(行) 132 | * @param cy 当前要尝试访问的点 y 坐标(列) 133 | * @param step 当前是第几步 134 | */ 135 | public void traversalChessboard(int[][] chessboard, int cx, int cy, int step) { 136 | // 1. 标识当前点已经访问 137 | visited[buildVisitedIndex(cx, cy)] = true; 138 | // 2. 标识当前棋盘上的点是第几步走的 139 | chessboard[cx][cy] = step; 140 | // 3. 根据当前节点计算马儿可以走的点 141 | ArrayList points = next(new Point(cx, cy)); 142 | sort(points); 143 | // 不为空则可以一直尝试走 144 | while (!points.isEmpty()) { 145 | Point point = points.remove(0); 146 | // 如果该点,没有被访问过,则递归访问:深度优先 147 | if (!visited[buildVisitedIndex(point.x, point.y)]) { 148 | traversalChessboard(chessboard, point.x, point.y, step + 1); 149 | } 150 | } 151 | 152 | // 所有可走的点都走完了,如果还没有完成,则重置当前访问的点为没有访问过 153 | 154 | if (step < X * Y && !finished) { 155 | visited[buildVisitedIndex(cx, cy)] = false; 156 | chessboard[cx][cy] = 0; // 重置为 0 表示没有走过 157 | } else { 158 | finished = true; // 表示已经完成任务 159 | } 160 | // System.out.println(step); 161 | // show(chessboard); 162 | } 163 | 164 | /** 165 | * 贪心算法优化:按每一个点的 next 可选择的点数量进行升序排列 166 | * 167 | * @param points 168 | */ 169 | private void sort(ArrayList points) { 170 | points.sort((o1, o2) -> { 171 | ArrayList next1 = next(o1); 172 | ArrayList next2 = next(o2); 173 | // 你可以尝试修改下这里:按降序排列的话,这个等待时间就太多了 174 | // 升序排列,我这里只需要 100 毫秒左右,而降序排列需要接近 1 分多钟甚至几分钟 175 | if (next1.size() > next2.size()) { 176 | return 1; 177 | } else if (next1.size() == next2.size()) { 178 | return 0; 179 | } else { 180 | return -1; 181 | } 182 | 183 | }); 184 | } 185 | 186 | /** 187 | * 使用的是一个一维数组来表示某个点是否被访问过 188 | *
189 |      *   那么就直接数格子,第 N 个格子对应某一个点,从左到右,上到下数
190 |      *   0,1,2,3,4,5,6,7,
191 |      *   8,9,10,11...
192 |      * 
193 | * 194 | * @param cx 195 | * @param xy 196 | * @return 197 | */ 198 | private int buildVisitedIndex(int cx, int xy) { 199 | //比如 0,1: 0*8 + 1 = 1 200 | return cx * X + xy; 201 | } 202 | 203 | @Test 204 | public void traversalChessboardTest() { 205 | int[][] chessboard = new int[X][Y]; 206 | Instant start = Instant.now(); 207 | traversalChessboard(chessboard, 0, 0, 1); 208 | Duration re = Duration.between(start, Instant.now()); 209 | System.out.println("耗时:" + re.toMillis() + "毫秒"); 210 | System.out.println("耗时:" + re.getSeconds() + "秒"); 211 | show(chessboard); 212 | } 213 | 214 | private void show(int[][] chessboard) { 215 | for (int[] row : chessboard) { 216 | System.out.println(Arrays.toString(row)); 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/kmp/KmpAlgorithm.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.kmp; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * KMP 算法 9 | */ 10 | public class KmpAlgorithm { 11 | /** 12 | * KMP 简单推导 13 | */ 14 | @Test 15 | public void kmpNextTest1() { 16 | String dest = "A"; 17 | System.out.println("字符串:" + dest); 18 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext1(dest))); 19 | System.out.println(); 20 | dest = "AA"; 21 | System.out.println("字符串:" + dest); 22 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext1(dest))); 23 | System.out.println(); 24 | dest = "AAA"; 25 | System.out.println("字符串:" + dest); 26 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext1(dest))); 27 | System.out.println(); 28 | dest = "AAAB"; 29 | System.out.println("字符串:" + dest); 30 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext1(dest))); 31 | System.out.println(); 32 | dest = "ABCDABD"; 33 | System.out.println("字符串:" + dest); 34 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext1(dest))); 35 | System.out.println(); 36 | } 37 | 38 | /** 39 | * 生成此字符串的 部分匹配表 40 | * 41 | * @param dest 42 | */ 43 | public int[] buildKmpNext1(String dest) { 44 | int[] next = new int[dest.length()]; 45 | // 第一个字符的前缀和后缀都没有,所以不会有公共元素,因此必定为 0 46 | next[0] = 0; 47 | for (int i = 1, j = 0; i < dest.length(); i++) { 48 | /* 49 | ABCDA 50 | 前缀:`A、AB、ABC、ABCD` 51 | 后缀:`BCDA、CDA、DA、A` 52 | 公共元素 A 53 | 部分匹配值:1 54 | */ 55 | // 当相等时,表示有一个部分匹配值 56 | if (dest.charAt(i) == dest.charAt(j)) { 57 | j++; 58 | } 59 | next[i] = j; 60 | } 61 | return next; 62 | } 63 | 64 | /** 65 | * kmp 部分匹配表生成 66 | */ 67 | @Test 68 | public void kmpNextTest() { 69 | String dest = "A"; 70 | System.out.println("字符串:" + dest); 71 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext(dest))); 72 | System.out.println(); 73 | dest = "AA"; 74 | System.out.println("字符串:" + dest); 75 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext(dest))); 76 | System.out.println(); 77 | dest = "AAA"; 78 | System.out.println("字符串:" + dest); 79 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext(dest))); 80 | System.out.println(); 81 | dest = "AAAB"; 82 | System.out.println("字符串:" + dest); 83 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext(dest))); 84 | System.out.println(); 85 | dest = "ABCDABD"; 86 | System.out.println("字符串:" + dest); 87 | System.out.println("的部分匹配表为:" + Arrays.toString(buildKmpNext(dest))); 88 | System.out.println(); 89 | } 90 | 91 | /** 92 | * 生成此字符串的 部分匹配表 93 | * 94 | * @param dest 95 | */ 96 | public int[] buildKmpNext(String dest) { 97 | int[] next = new int[dest.length()]; 98 | // 第一个字符的前缀和后缀都没有,所以不会有公共元素,因此必定为 0 99 | next[0] = 0; 100 | for (int i = 1, j = 0; i < dest.length(); i++) { 101 | /* 102 | ABCDA 103 | 前缀:`A、AB、ABC、ABCD` 104 | 后缀:`BCDA、CDA、DA、A` 105 | 公共元素 A 106 | 部分匹配值:1 107 | */ 108 | // 当 dest.charAt(i) != dest.charAt(j) 时 109 | // 需要从 next[j-1] 中获取新的 j 110 | // 这步骤是 部分匹配表的 核心点 111 | while (j > 0 && dest.charAt(i) != dest.charAt(j)) { 112 | j = next[j - 1]; 113 | } 114 | // 当相等时,表示有一个部分匹配值 115 | if (dest.charAt(i) == dest.charAt(j)) { 116 | j++; 117 | } 118 | next[i] = j; 119 | } 120 | return next; 121 | } 122 | 123 | /** 124 | * kmp 搜索: 推导过程 125 | */ 126 | @Test 127 | public void kmpSearchTest1() { 128 | String str1 = "BBC ABCDAB ABCDABCDABDE"; 129 | String str2 = "BBC"; 130 | // 获得部分匹配表 131 | int[] next = buildKmpNext(str2); 132 | int result = kmpSearch1(str1, str2, next); 133 | System.out.println(result); 134 | 135 | str2 = "ABCDABD"; 136 | // 获得部分匹配表 137 | next = buildKmpNext(str2); 138 | result = kmpSearch1(str1, str2, next); 139 | System.out.println(result); 140 | } 141 | 142 | /** 143 | * kmp 搜索算法 144 | * 145 | * @param str1 源字符串 146 | * @param str2 子串 147 | * @param next 子串的部分匹配表 148 | * @return 149 | */ 150 | private int kmpSearch1(String str1, String str2, int[] next) { 151 | for (int i = 0, j = 0; i < str1.length(); i++) { 152 | if (str1.charAt(i) == str2.charAt(j)) { 153 | j++; 154 | } 155 | if (j == str2.length()) { 156 | return i - j + 1; 157 | } 158 | } 159 | return -1; 160 | } 161 | 162 | 163 | /** 164 | * kmp 搜索: 推导过程 165 | */ 166 | @Test 167 | public void kmpSearchTest() { 168 | String str1 = "BBC ABCDAB ABCDABCDABDE"; 169 | String str2 = "ABCDABD"; 170 | // 获得部分匹配表 171 | int[] next = buildKmpNext(str2); 172 | int result = kmpSearch(str1, str2, next); 173 | System.out.println(result); 174 | } 175 | 176 | /** 177 | * kmp 搜索算法 178 | * 179 | * @param str1 源字符串 180 | * @param str2 子串 181 | * @param next 子串的部分匹配表 182 | * @return 183 | */ 184 | private int kmpSearch(String str1, String str2, int[] next) { 185 | for (int i = 0, j = 0; i < str1.length(); i++) { 186 | while (j > 0 && str1.charAt(i) != str2.charAt(j)) { 187 | /* 188 | 从源字符串中挨个的取出字符与 子串的第一个相比 189 | 直到匹配上时:j++, 如果有一个已经匹配上了比如 190 | ↓ i = 10 191 | BBC ABCDAB ABCDABCDABDE 192 | ABCDABD 193 | ↑ j = 6 194 | 然后继续下一个,这个时候由于 i 与 j 同步在增加,直到匹配到 空格与 D 时,不匹配 195 | 此时:需要调整子串的匹配其实点: 196 | j = next[6 - 1] = 2, 调整后 197 | 198 | ↓ i = 10 199 | BBC ABCDAB ABCDABCDABDE 200 | ABCDABD 201 | ↑ j = 2 202 | 203 | 会发现不等于,继续调整 204 | j = next[2 - 1] = 0, 调整后 205 | 206 | ↓ i = 10 207 | BBC ABCDAB ABCDABCDABDE 208 | ABCDABD 209 | ↑ j = 0 210 | 此时:不满足 j > 0 了 211 | */ 212 | j = next[j - 1]; 213 | } 214 | 215 | /* 216 | 从源字符串中挨个的取出字符与 子串的第一个相比 217 | 直到匹配上时:j++, 如果有一个已经匹配上了比如 218 | ↓ i = 4 219 | BBC ABCDAB ABCDABCDABDE 220 | ABCDABD 221 | ↑ j = 0 222 | 然后继续下一个,这个时候由于 i 与 j 同步在增加,直到匹配到 空格与 D 时,不匹配 223 | */ 224 | if (str1.charAt(i) == str2.charAt(j)) { 225 | j++; 226 | } 227 | if (j == str2.length()) { 228 | return i - j + 1; 229 | } 230 | } 231 | return -1; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/kmp/ViolenceMatch.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.kmp; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 暴力匹配 7 | */ 8 | public class ViolenceMatch { 9 | /** 10 | * 暴力匹配 11 | * 12 | * @param str1 要匹配的文本 13 | * @param str2 关键词 14 | * @return 15 | */ 16 | public int violenceMatch(String str1, String str2) { 17 | char[] s1 = str1.toCharArray(); 18 | char[] s2 = str2.toCharArray(); 19 | 20 | int s1Len = s1.length; 21 | int s2Len = s2.length; 22 | 23 | int i = 0; // 指向 s1 中正在匹配的位置 24 | int j = 0; // 执行 s2 中正在匹配的位置 25 | while (i < s1Len && j < s2Len) { 26 | // 如果相等,则让两个指针都往前移动 27 | if (s1[i] == s2[j]) { 28 | i++; 29 | j++; 30 | } else { 31 | // 当不匹配的时候 32 | // j 重置为 0,子串要重新匹配 33 | i = i - (j - 1); 34 | j = 0; 35 | } 36 | } 37 | // 如果找到,则返回当前索引 38 | // 因为在匹配过程中,没有匹配上 j 就重置为 0 了 39 | if (j == s2Len) { 40 | // 返回匹配开始的字符 41 | return i - j; 42 | } 43 | return -1; 44 | } 45 | 46 | /** 47 | * 测试匹配上 48 | */ 49 | @Test 50 | public void fun1() { 51 | String str1 = "硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好"; 52 | String str2 = "尚硅谷你尚硅你"; 53 | System.out.println(violenceMatch(str1, str2)); 54 | } 55 | 56 | /** 57 | * 测试匹配失败 58 | */ 59 | @Test 60 | public void fun2() { 61 | String str1 = "硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好"; 62 | String str2 = "尚硅谷你尚硅你x"; 63 | System.out.println(violenceMatch(str1, str2)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/algorithm/prim/PrimAlgorithm.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.algorithm.prim; 2 | 3 | import org.junit.Test; 4 | 5 | 6 | /** 7 | * 普利姆算法 8 | */ 9 | public class PrimAlgorithm { 10 | /** 11 | * 图:首先需要有一个带权的连通无向图 12 | */ 13 | class MGraph { 14 | int vertex; // 顶点个数 15 | int[][] weights; // 邻接矩阵 16 | char[] datas; // 村庄数据 17 | 18 | /** 19 | * @param vertex 村庄数量, 会按照数量,按顺序生成村庄,如 A、B、C... 20 | * @param weights 需要你自己定义好那些点是连通的,那些不是连通的 21 | */ 22 | public MGraph(int vertex, int[][] weights) { 23 | this.vertex = vertex; 24 | this.weights = weights; 25 | 26 | this.datas = new char[vertex]; 27 | for (int i = 0; i < vertex; i++) { 28 | // 大写字母 A 从 65 开始 29 | datas[i] = (char) (65 + i); 30 | } 31 | } 32 | 33 | public void show() { 34 | System.out.printf("%-8s", " "); 35 | for (char vertex : datas) { 36 | // 控制字符串输出长度:少于 8 位的,右侧用空格补位 37 | System.out.printf("%-8s", vertex + " "); 38 | } 39 | System.out.println(); 40 | for (int i = 0; i < weights.length; i++) { 41 | System.out.printf("%-8s", datas[i] + " "); 42 | for (int j = 0; j < weights.length; j++) { 43 | System.out.printf("%-8s", weights[i][j] + " "); 44 | } 45 | System.out.println(); 46 | } 47 | } 48 | } 49 | 50 | @Test 51 | public void mGraphTest() { 52 | // 不连通的默认值: 53 | // 这里设置为较大的数,是为了后续的计算方便,计算权值的时候,不会选择 54 | int defaultNo = 100000; 55 | int[][] weights = new int[][]{ 56 | {defaultNo, 5, 7, defaultNo, defaultNo, defaultNo, 2}, // A 57 | {5, defaultNo, defaultNo, 9, defaultNo, defaultNo, 3},// B 58 | {7, defaultNo, defaultNo, defaultNo, 8, defaultNo, defaultNo},// C 59 | {defaultNo, 9, defaultNo, defaultNo, defaultNo, 4, defaultNo},// D 60 | {defaultNo, defaultNo, 8, defaultNo, defaultNo, 5, 4},// E 61 | {defaultNo, defaultNo, defaultNo, 4, 5, defaultNo, 6},// F 62 | {2, 3, defaultNo, defaultNo, 4, 6, defaultNo}// G 63 | }; 64 | MGraph mGraph = new MGraph(7, weights); 65 | mGraph.show(); 66 | } 67 | 68 | 69 | /** 70 | * 最小生成树 71 | */ 72 | class MinTree { 73 | /** 74 | * 普利姆算法 75 | * 76 | * @param mGraph 无向图 77 | * @param v 从哪一个点开始生成 78 | */ 79 | public void prim(MGraph mGraph, int v) { 80 | int minTotalWeight = 0; // 记录已选择的边的总权值,仅仅只是为了测试打印验证 81 | 82 | // 记录已经选择过的节点 83 | boolean[] selects = new boolean[mGraph.vertex]; 84 | // 从哪个节点开始,则标识已经被访问 85 | selects[v] = true; 86 | 87 | // 一共要生成 n-1 条边 88 | for (int i = 1; i < mGraph.vertex; i++) { 89 | // 每次循环:选择一条权值最小的边出来 90 | 91 | // 记录最小值 92 | int minWeight = 10000; 93 | int x = -1; 94 | int y = -1; 95 | 96 | // 每次查找权值最小的边:根据思路,每次都是从已经选择过的点,中去找与该点直连的点的权值 97 | // 并且该点还没有被选择过:如果两个点都被选择过,要么他们是双向的,要么就是被其他的点选择过了 98 | // 这里双循环的含义:建议对照笔记中步骤分析理解 99 | for (int j = 0; j < mGraph.vertex; j++) { 100 | for (int k = 0; k < mGraph.vertex; k++) { 101 | // 通过 j 来限定已经选择过的点 102 | // 通过 k 来遍历匹配,没有选择过的点 103 | // 假设第一轮是 A 点:j = 0 104 | // 那么在这里将会执行 0,1 0,2, 0,3 也就是与 A 点直连,且没有被选择过的点,的最小权值 105 | if (selects[j] && !selects[k] 106 | && mGraph.weights[j][k] < minWeight 107 | ) { 108 | // 记录最小权值,与这条边的信息 109 | minWeight = mGraph.weights[j][k]; 110 | x = j; 111 | y = k; 112 | } 113 | } 114 | } 115 | // 当一次循环结束时:就找到了一条权值最小的边 116 | System.out.printf("%s,%s [%s] \n", mGraph.datas[x], mGraph.datas[y], mGraph.weights[x][y]); 117 | minTotalWeight += mGraph.weights[x][y]; // 统计已选择边权值 118 | 119 | minWeight = 10000; 120 | // 记录该点已经被选择 121 | // 在查找最小权值边的代码中:y=k, k 表示没有被选择过的点,所以,找到之后,这里记录 y 为这条边的另一个点 122 | selects[y] = true; 123 | } 124 | System.out.println("总权值:" + minTotalWeight); 125 | } 126 | } 127 | 128 | @Test 129 | public void primTest() { 130 | // 不连通的默认值: 131 | // 这里设置为较大的数,是为了后续的计算方便,计算权值的时候,不会选择 132 | int defaultNo = 100000; 133 | int[][] weights = new int[][]{ 134 | {defaultNo, 5, 7, defaultNo, defaultNo, defaultNo, 2}, // A 135 | {5, defaultNo, defaultNo, 9, defaultNo, defaultNo, 3},// B 136 | {7, defaultNo, defaultNo, defaultNo, 8, defaultNo, defaultNo},// C 137 | {defaultNo, 9, defaultNo, defaultNo, defaultNo, 4, defaultNo},// D 138 | {defaultNo, defaultNo, 8, defaultNo, defaultNo, 5, 4},// E 139 | {defaultNo, defaultNo, defaultNo, 4, 5, defaultNo, 6},// F 140 | {2, 3, defaultNo, defaultNo, 4, 6, defaultNo}// G 141 | }; 142 | MGraph mGraph = new MGraph(7, weights); 143 | mGraph.show(); 144 | 145 | MinTree minTree = new MinTree(); 146 | minTree.prim(mGraph, 0); 147 | minTree.prim(mGraph, 1); 148 | 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/hashtab/HashTabTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.hashtab; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 数组 + 链表的 哈希表实现 7 | */ 8 | public class HashTabTest { 9 | /** 10 | * 测试添加和打印 11 | */ 12 | @Test 13 | public void test1() { 14 | HashTab hashtable = new HashTab(7); 15 | hashtable.list(); 16 | hashtable.add(new Emp(1, "小明")); 17 | hashtable.add(new Emp(2, "小红")); 18 | hashtable.add(new Emp(3, "小蓝")); 19 | System.out.println(""); 20 | hashtable.list(); 21 | hashtable.add(new Emp(3, "小蓝")); 22 | hashtable.add(new Emp(4, "小蓝4")); 23 | hashtable.add(new Emp(5, "小蓝5")); 24 | hashtable.add(new Emp(6, "小蓝6")); 25 | hashtable.add(new Emp(7, "小蓝7")); 26 | hashtable.add(new Emp(8, "小蓝8")); 27 | hashtable.add(new Emp(9, "小蓝9")); 28 | System.out.println(""); 29 | hashtable.list(); 30 | 31 | } 32 | 33 | /** 34 | * 测试查找 35 | */ 36 | @Test 37 | public void findByIdTest() { 38 | HashTab hashtable = new HashTab(7); 39 | hashtable.add(new Emp(1, "小明")); 40 | hashtable.add(new Emp(2, "小红")); 41 | hashtable.add(new Emp(3, "小蓝")); 42 | hashtable.add(new Emp(3, "小蓝")); 43 | hashtable.add(new Emp(4, "小蓝4")); 44 | hashtable.add(new Emp(5, "小蓝5")); 45 | hashtable.add(new Emp(6, "小蓝6")); 46 | hashtable.add(new Emp(7, "小蓝7")); 47 | hashtable.add(new Emp(8, "小蓝8")); 48 | hashtable.add(new Emp(9, "小蓝9")); 49 | System.out.println(""); 50 | hashtable.list(); 51 | 52 | hashtable.findById(-1); 53 | hashtable.findById(1); 54 | hashtable.findById(9); 55 | hashtable.findById(5); 56 | hashtable.findById(19); 57 | 58 | } 59 | 60 | /** 61 | * 删除查找 62 | */ 63 | @Test 64 | public void deleteByIdTest() { 65 | HashTab hashtable = new HashTab(7); 66 | hashtable.add(new Emp(1, "小明")); 67 | hashtable.add(new Emp(2, "小红")); 68 | hashtable.add(new Emp(3, "小蓝")); 69 | hashtable.add(new Emp(3, "小蓝")); 70 | hashtable.add(new Emp(4, "小蓝4")); 71 | hashtable.add(new Emp(5, "小蓝5")); 72 | hashtable.add(new Emp(6, "小蓝6")); 73 | hashtable.add(new Emp(7, "小蓝7")); 74 | hashtable.add(new Emp(8, "小蓝8")); 75 | hashtable.add(new Emp(9, "小蓝9")); 76 | System.out.println(""); 77 | hashtable.list(); 78 | 79 | hashtable.deleteById(1); 80 | hashtable.findById(1); 81 | 82 | hashtable.deleteById(-1); 83 | hashtable.deleteById(9); 84 | hashtable.deleteById(10); 85 | 86 | System.out.println(""); 87 | hashtable.list(); 88 | 89 | } 90 | } 91 | // 为了方便,下面的各种需要获取的属性都用 public 92 | 93 | /** 94 | * 员工信息 95 | */ 96 | class Emp { 97 | public int id; 98 | public String name; 99 | public Emp next; 100 | 101 | public Emp(int id, String name) { 102 | this.id = id; 103 | this.name = name; 104 | } 105 | } 106 | 107 | /** 108 | * 链表类 109 | */ 110 | class EmpLinkedList { 111 | /** 112 | * 这里头直接保存元素,和普通完整的链表有一点不一样 113 | */ 114 | private Emp head; 115 | 116 | /** 117 | * 添加一个员工 118 | * 119 | * @param emp 120 | */ 121 | public void add(Emp emp) { 122 | if (head == null) { 123 | head = emp; 124 | return; 125 | } 126 | Emp temp = head; 127 | while (true) { 128 | if (temp.next == null) { 129 | break; 130 | } 131 | temp = temp.next; 132 | } 133 | temp.next = emp; 134 | } 135 | 136 | /** 137 | * 打印链表元素 138 | * 139 | * @param no 140 | */ 141 | public void list(int no) { 142 | if (head == null) { 143 | System.out.println("链表为空"); 144 | return; 145 | } 146 | Emp temp = head; 147 | while (true) { 148 | System.out.printf("%d : \t id=%d,\t name=%s \n", no, temp.id, temp.name); 149 | if (temp.next == null) { 150 | break; 151 | } 152 | temp = temp.next; 153 | } 154 | } 155 | 156 | /** 157 | * 根据 ID 查找雇员 158 | * 159 | * @param id 160 | * @return 161 | */ 162 | public Emp findById(int id) { 163 | if (head == null) { 164 | return null; 165 | } 166 | // 有 head 则循环查找链表 167 | Emp temp = head; 168 | while (true) { 169 | if (temp.id == id) { 170 | // 已经找到 171 | break; 172 | } 173 | if (temp.next == null) { 174 | // 当下一个为空的时候,则表示没有找到 175 | temp = null; 176 | break; 177 | } 178 | temp = temp.next; 179 | } 180 | return temp; 181 | } 182 | 183 | /** 184 | * 按 ID 删除雇员,如果删除成功,则返回删除的元素,如果删除失败,则返回 null 185 | * 186 | * @param id 187 | * @return 188 | */ 189 | public Emp deleteById(int id) { 190 | if (head == null) { 191 | return null; 192 | } 193 | // 有 head 则循环查找链表 194 | Emp temp = head; 195 | Emp prev = head; 196 | while (true) { 197 | if (temp.id == id) { 198 | // 已经找到 199 | break; 200 | } 201 | if (temp.next == null) { 202 | // 当下一个为空的时候,则表示没有找到 203 | temp = null; 204 | break; 205 | } 206 | prev = temp; // 标记上一个雇员 207 | temp = temp.next; 208 | } 209 | 210 | // 没有找到 211 | if (temp == null) { 212 | return null; 213 | } 214 | // 如果找到的就是 head ,则删除自己 215 | if (head == temp) { 216 | head = temp.next; 217 | return temp; 218 | } 219 | // 如果已经找到目标元素,从它的上一个雇员节点中删掉自己 220 | prev.next = temp.next; 221 | return temp; 222 | } 223 | } 224 | 225 | /** 226 | * 哈希表,对外暴露的也就是奔类了。关于里面的数组怎么算,链表怎么放,都是本类来做包装 227 | */ 228 | class HashTab { 229 | // 链表数组 230 | private EmpLinkedList[] linkedArray; 231 | private int size; 232 | 233 | /** 234 | * 构造一个哈希表 235 | * 236 | * @param size 哈希表大小 237 | */ 238 | public HashTab(int size) { 239 | this.size = size; 240 | this.linkedArray = new EmpLinkedList[size]; 241 | // 初始化哈希表中的链表对象 242 | for (int i = 0; i < size; i++) { 243 | linkedArray[i] = new EmpLinkedList(); 244 | } 245 | } 246 | 247 | /** 248 | * 往哈希表中添加一个员工 249 | * 250 | * @param emp 251 | */ 252 | public void add(Emp emp) { 253 | // 首先需要确定:该员工的 id 所在的哈希位置,用散列函数来计算 254 | int id = emp.id; 255 | int index = hashFun(id); 256 | linkedArray[index].add(emp); 257 | } 258 | 259 | /** 260 | * 打印哈希表 261 | */ 262 | public void list() { 263 | for (int i = 0; i < size; i++) { 264 | linkedArray[i].list(i); 265 | } 266 | } 267 | 268 | public Emp findById(int id) { 269 | // 先找到该 id 属于那一条链表 270 | int no = hashFun(id); 271 | // 先判断边界 272 | if (no > size || no < 0) { 273 | System.out.printf("id = %d 异常,计算出目标链表为 %d \n", id, no); 274 | return null; 275 | } 276 | Emp emp = linkedArray[no].findById(id); 277 | if (emp == null) { 278 | System.out.printf("在第 %d 条链表中未找到 id = %d 的雇员 \n", no, id); 279 | } else { 280 | System.out.printf("在第 %d 条链表中找到 id = %d 的雇员, name = %s \n", no, id, emp.name); 281 | } 282 | return emp; 283 | } 284 | 285 | public Emp deleteById(int id) { 286 | // 先找到该 id 属于那一条链表 287 | int no = hashFun(id); 288 | // 先判断边界 289 | if (no > size || no < 0) { 290 | System.out.printf("id = %d 异常,计算出目标链表为 %d \n", id, no); 291 | return null; 292 | } 293 | 294 | Emp emp = linkedArray[no].deleteById(id); 295 | if (emp == null) { 296 | System.out.printf("在第 %d 条链表中未找到 id = %d 的雇员,删除失败 \n", no, id); 297 | } else { 298 | System.out.printf("在第 %d 条链表中找到 id = %d 的雇员, name = %s ,删除成功\n", no, id, emp.name); 299 | } 300 | return emp; 301 | } 302 | 303 | /** 304 | * 散列函数 305 | * 306 | * @param id 307 | * @return 308 | */ 309 | private int hashFun(int id) { 310 | // 散列函数的计算法方法有很多种 311 | // 这里就使用最简单的取模 312 | return id % size; 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/huffmantree/HuffmanTreeTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.huffmantree; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * 赫夫曼树实现 12 | */ 13 | public class HuffmanTreeTest { 14 | /** 15 | * 首先推导实现 16 | */ 17 | @Test 18 | public void processDemo() { 19 | int[] arr = {13, 7, 8, 3, 29, 6, 1}; 20 | 21 | // 1. 为了实现方便,先将每个元素转成 Node 对象,并装入 arrayList 中 22 | List nodes = new ArrayList<>(); 23 | for (int i : arr) { 24 | nodes.add(new Node(i)); 25 | } 26 | 27 | // 2. 从小到大排序 28 | Collections.sort(nodes); 29 | 30 | // 3. 取出两个较小的树 31 | Node left = nodes.get(0); 32 | Node right = nodes.get(1); 33 | // 4. 构成成新的二叉树 34 | Node parent = new Node(left.value + right.value); 35 | parent.left = left; 36 | parent.right = right; 37 | // 5. 从 list 中删除已经处理过的二叉树 38 | nodes.remove(left); 39 | nodes.remove(right); 40 | // 6. 将新的二叉树添加到 list 中,为下一轮构建做准备 41 | nodes.add(parent); 42 | 43 | // 最后来看一下结果 44 | System.out.println("原始数组:" + Arrays.toString(arr)); 45 | System.out.println("新的节点:" + nodes); 46 | } 47 | 48 | @Test 49 | public void createHuffmanTreeTest() { 50 | int[] arr = {13, 7, 8, 3, 29, 6, 1}; 51 | Node huffmanTree = createHuffmanTree(arr); 52 | huffmanTree.list(); 53 | } 54 | 55 | private Node createHuffmanTree(int[] arr) { 56 | List nodes = new ArrayList<>(); 57 | for (int i : arr) { 58 | nodes.add(new Node(i)); 59 | } 60 | 61 | while (nodes.size() > 1) { 62 | // 2. 从小到大排序 63 | Collections.sort(nodes); 64 | 65 | // 3. 取出两个较小的树 66 | Node left = nodes.get(0); 67 | Node right = nodes.get(1); 68 | // 4. 构成成新的二叉树 69 | Node parent = new Node(left.value + right.value); 70 | parent.left = left; 71 | parent.right = right; 72 | // 5. 从 list 中删除已经处理过的二叉树 73 | nodes.remove(left); 74 | nodes.remove(right); 75 | // 6. 将新的二叉树添加到 list 中,为下一轮构建做准备 76 | nodes.add(parent); 77 | } 78 | 79 | // 返回赫夫曼树的 root 节点 80 | // 因为前面从小到大排序的,最后一个就是最大节点 81 | return nodes.get(0); 82 | } 83 | } 84 | 85 | 86 | /** 87 | * 节点 88 | */ 89 | class Node implements Comparable { 90 | int value; // 权 91 | Node left; 92 | Node right; 93 | 94 | public Node(int value) { 95 | this.value = value; 96 | } 97 | 98 | /** 99 | * 为了打印方便 100 | * 101 | * @return 102 | */ 103 | @Override 104 | public String toString() { 105 | return value + ""; 106 | } 107 | 108 | /** 109 | * 从小到大排序 110 | * 111 | * @param o 112 | * @return 113 | */ 114 | @Override 115 | public int compareTo(Node o) { 116 | return this.value - o.value; 117 | } 118 | 119 | /** 120 | * 前序遍历 121 | */ 122 | public void list() { 123 | System.out.println(value); 124 | if (left != null) { 125 | left.list(); 126 | } 127 | if (right != null) { 128 | right.list(); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/linkedlist/doublelist/DoubleLinkedList.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.linkedlist.doublelist; 2 | 3 | /** 4 | * 双向链表的操作实现 5 | */ 6 | public class DoubleLinkedList { 7 | private HeroNode head = new HeroNode(0, "", ""); 8 | 9 | /** 10 | * 添加:将节点添加到链表尾部 11 | * 12 | * @param node 13 | */ 14 | public void add(HeroNode node) { 15 | HeroNode temp = head; 16 | // 找到链表的末尾 17 | while (temp.next != null) { 18 | temp = temp.next; 19 | } 20 | // 将新节点添加到末尾的节点上 21 | temp.next = node; 22 | node.pre = temp; 23 | } 24 | 25 | /** 26 | *
 27 |      *  按编号顺序添加
 28 |      *  思路:
 29 |      *     1. 从头遍历节点,
 30 |      *     2. 找到比目标大的节点:插入到该节点之前(升序)
 31 |      *     2. 如果已经存在相同编号的节点,则提示不允许添加
 32 |      *
 33 |      * 
34 | * 35 | * @param node 36 | */ 37 | public void addByOrder(HeroNode node) { 38 | HeroNode temp = head; 39 | boolean exist = false; // 添加的节点是否已经在链表中存在 40 | 41 | while (true) { 42 | // 已到列表尾部 43 | if (temp.next == null) { 44 | break; 45 | } 46 | // 已找到 47 | if (temp.next.no > node.no) { 48 | break; 49 | } 50 | 51 | // 已存在该编号 52 | if (temp.next.no == node.no) { 53 | exist = true; 54 | break; 55 | } 56 | temp = temp.next; 57 | } 58 | if (exist) { 59 | System.out.printf("准备插入的英雄编号 %d 已经存在,不能加入 \n", node.no); 60 | return; 61 | } 62 | 63 | // 把节点插入到 temp 和 temp.next 之间 64 | // temp -> node -> temp.next 65 | node.next = temp.next; 66 | temp.next = node; 67 | } 68 | 69 | /** 70 | * 更新:以 id 匹配,更新链表中找到的节点;与单向链表的逻辑一样 71 | * 72 | * @param newNode 73 | */ 74 | public void update(HeroNode newNode) { 75 | if (head.next == null) { 76 | System.out.println("链表为空"); 77 | return; 78 | } 79 | 80 | HeroNode temp = head.next; 81 | boolean exist = false; // 是否找到要修改的节点 82 | while (true) { 83 | // 如果是链表尾部 84 | if (temp == null) { 85 | break; 86 | } 87 | // 如果已找到 88 | if (temp.no == newNode.no) { 89 | exist = true; 90 | break; 91 | } 92 | temp = temp.next; 93 | } 94 | // 如果已找到,则修改信息 95 | if (exist) { 96 | temp.name = newNode.name; 97 | temp.nickName = newNode.nickName; 98 | } else { 99 | System.out.printf("未找到编号为 %d 的英雄", newNode.no); 100 | } 101 | } 102 | 103 | /** 104 | *
105 |      * 删除:按编号匹配,将其删除;
106 |      *  思路:直接找到该节点,然后自我删除
107 |      * 
108 | * 109 | * @param no 110 | */ 111 | public void delete(int no) { 112 | if (head.next == null) { 113 | System.out.println("链表为空"); 114 | return; 115 | } 116 | HeroNode cur = head.next; 117 | boolean exist = false; // 是否找到要删除的节点 118 | while (true) { 119 | if (cur == null) { 120 | break; 121 | } 122 | // 找到与自己相同的 id 123 | if (cur.no == no) { 124 | exist = true; 125 | break; 126 | } 127 | cur = cur.next; 128 | } 129 | if (!exist) { 130 | System.out.printf("未找到匹配的编号 %d \n", no); 131 | return; 132 | } 133 | 134 | // 完成自我删除 135 | // 这里有一个边界问题:当 cur 是末尾节点的时候,next 为空,不处理 136 | if (cur.next != null) { 137 | cur.next.pre = cur.pre; 138 | } 139 | cur.pre.next = cur.next; 140 | } 141 | 142 | /** 143 | * 打印链表 144 | */ 145 | public void print() { 146 | if (head.next == null) { 147 | System.out.println("链表为空"); 148 | return; 149 | } 150 | HeroNode cur = head.next; 151 | while (cur != null) { 152 | System.out.println(cur); 153 | cur = cur.next; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/linkedlist/doublelist/DoubleLinkedListTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.linkedlist.doublelist; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | /** 7 | * 双向链表测试 8 | */ 9 | public class DoubleLinkedListTest { 10 | DoubleLinkedList doubleLinkedList; 11 | 12 | @Before 13 | public void before() { 14 | HeroNode hero1 = new HeroNode(1, "宋江", "及时雨"); 15 | HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟"); 16 | HeroNode hero3 = new HeroNode(3, "吴用", "智多星"); 17 | HeroNode hero4 = new HeroNode(4, "林冲", "豹子头"); 18 | 19 | // 测试新增 20 | doubleLinkedList = new DoubleLinkedList(); 21 | doubleLinkedList.add(hero1); 22 | doubleLinkedList.add(hero4); 23 | doubleLinkedList.add(hero2); 24 | doubleLinkedList.add(hero3); 25 | } 26 | 27 | @Test 28 | public void addTest() { 29 | // before 中已测试 30 | } 31 | 32 | /** 33 | * 更新测试 34 | */ 35 | @Test 36 | public void updateTest() { 37 | System.out.println("更新前"); 38 | doubleLinkedList.print(); 39 | HeroNode hero4New = new HeroNode(4, "林冲-修改测试", "豹子头-修改测试"); 40 | doubleLinkedList.update(hero4New); 41 | System.out.println("更新后"); 42 | doubleLinkedList.print(); 43 | } 44 | 45 | /** 46 | * 删除测试 47 | */ 48 | @Test 49 | public void deleteTest() { 50 | System.out.println("删除前"); 51 | doubleLinkedList.print(); 52 | doubleLinkedList.delete(1); 53 | doubleLinkedList.delete(4); 54 | doubleLinkedList.delete(3); 55 | System.out.println("删除后"); 56 | doubleLinkedList.print(); 57 | } 58 | 59 | @Test 60 | public void addByOrderTest() { 61 | HeroNode hero1 = new HeroNode(1, "宋江", "及时雨"); 62 | HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟"); 63 | HeroNode hero3 = new HeroNode(3, "吴用", "智多星"); 64 | HeroNode hero4 = new HeroNode(4, "林冲", "豹子头"); 65 | 66 | // 测试新增 67 | doubleLinkedList = new DoubleLinkedList(); 68 | doubleLinkedList.addByOrder(hero1); 69 | doubleLinkedList.addByOrder(hero4); 70 | doubleLinkedList.addByOrder(hero2); 71 | doubleLinkedList.addByOrder(hero3); 72 | doubleLinkedList.addByOrder(hero3); 73 | doubleLinkedList.print(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/linkedlist/doublelist/HeroNode.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.linkedlist.doublelist; 2 | 3 | /** 4 | * 链表中的一个节点:英雄节点 5 | */ 6 | class HeroNode { 7 | public int no; 8 | public String name; 9 | public String nickName; 10 | public HeroNode next; 11 | public HeroNode pre; 12 | 13 | public HeroNode(int no, String name, String nickName) { 14 | this.no = no; 15 | this.name = name; 16 | this.nickName = nickName; 17 | } 18 | 19 | /** 20 | * 为了显示方便,重写 21 | * 22 | * @return 23 | */ 24 | @Override 25 | public String toString() { 26 | return "HeroNode{" + 27 | "no=" + no + 28 | ", name='" + name + '\'' + 29 | ", nickName='" + nickName + '\'' + 30 | '}'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/linkedlist/josepfu/Boy.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.linkedlist.josepfu; 2 | 3 | /** 4 | * 小孩节点 5 | */ 6 | public class Boy { 7 | int no; 8 | Boy next; 9 | 10 | public Boy(int no) { 11 | this.no = no; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/linkedlist/josepfu/CircleSingleLinkedList.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.linkedlist.josepfu; 2 | 3 | /** 4 | * 单向环形链表实现 5 | */ 6 | public class CircleSingleLinkedList { 7 | Boy first = null; 8 | 9 | /** 10 | * 添加几个小孩:这里的添加至少用于初始化时,构建一个约舍夫丢手帕的 n 个孩子,与传统的入队列还不一样 11 | * 12 | * @param nums 13 | */ 14 | public void add(int nums) { 15 | if (nums < 1) { 16 | System.out.println("至少要添加一个"); 17 | return; 18 | } 19 | Boy cur = null; 20 | for (int i = 1; i <= nums; i++) { 21 | Boy boy = new Boy(i); 22 | // 初始化 first 节点 23 | if (first == null) { 24 | first = boy; 25 | boy.next = first; // 自己和自己构成环状 26 | cur = first; 27 | continue; 28 | } 29 | cur.next = boy; 30 | boy.next = first; 31 | cur = boy; 32 | } 33 | } 34 | 35 | /** 36 | * 打印队列 37 | */ 38 | public void print() { 39 | if (first == null) { 40 | System.out.println("队列为空"); 41 | return; 42 | } 43 | Boy cur = first; 44 | while (true) { 45 | System.out.printf("小孩编号 %d \n", cur.no); 46 | cur = cur.next; 47 | // 如果和 first 一致,则标识已经走了一圈了 48 | if (cur == first) { 49 | return; 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * 游戏开始,计算出圈顺序 56 | * 57 | * @param startNo 从第几个小孩开始数 58 | * @param countNum 数几下 59 | * @param nums 参与该游戏的小孩有多少个 60 | */ 61 | public void countBoy(int startNo, int countNum, int nums) { 62 | // 进行一个数据校验 63 | if (first == null || // 环形队列没有构建 64 | countNum < 1 || // 每次至少数 1 下 65 | startNo > nums // 开始小孩不能大于参与游戏的人数 66 | ) { 67 | System.out.println("参数有误,请重新输入"); 68 | } 69 | // 1. 初始化辅助变量到 first 的后面 70 | Boy helper = first; 71 | // 当 helper.next = first 时,就说明已经定位了 72 | while (helper.next != first) { 73 | helper = helper.next; 74 | } 75 | 76 | // 2. 定位 first 和 helper 在 startNo 位置 77 | // first 初始在最开始,移动到 startNo 位置 78 | for (int i = 0; i < startNo - 1; i++) { 79 | helper = first; 80 | first = first.next; 81 | } 82 | 83 | // 为了测试方便,这里添加一个日志输出 84 | System.out.printf("定位到位置: %d \n", startNo); 85 | print(); 86 | 87 | // 3. 开始报数 和 出圈 88 | while (true) { 89 | // 当队列中只剩下一个人的时候,跳出循环,因为最后一个必然是他自己出队列 90 | if (helper == first) { 91 | break; 92 | } 93 | // 报数:每次报数 m-1 94 | for (int i = 0; i < countNum - 1; i++) { 95 | // 因为 helper 永远在 first 后面,只要在 first 移动时,指向 first 原来所在的位置即可 96 | helper = first; 97 | first = first.next; 98 | } 99 | // 出圈 100 | System.out.printf("出圈小孩编号 %d \n", first.no); 101 | first = first.next; // first 重置为下一次报数的小孩节点上 102 | helper.next = first; // helper 重置为下一次报数的小孩节点上 103 | } 104 | System.out.printf("最后剩下小孩编号 %d \n", first.no); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/linkedlist/josepfu/JosepfuTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.linkedlist.josepfu; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 约瑟夫问题测试 7 | */ 8 | public class JosepfuTest { 9 | /** 10 | * 添加测试 11 | */ 12 | @Test 13 | public void addTest() { 14 | CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList(); 15 | circleSingleLinkedList.add(5); 16 | circleSingleLinkedList.print(); 17 | } 18 | 19 | @Test 20 | public void countBoy() { 21 | CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList(); 22 | circleSingleLinkedList.add(5); 23 | System.out.println("构建环形队列"); 24 | circleSingleLinkedList.print(); 25 | 26 | // 开始玩游戏 27 | // 正确的输出顺序为:2、4、1、5、3 28 | circleSingleLinkedList.countBoy(1, 2, 5); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/queue/ArrayQueueDemo.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.queue; 2 | 3 | /** 4 | * 数组模拟队列 5 | */ 6 | public class ArrayQueueDemo { 7 | public static void main(String[] args) { 8 | ArrayQueue queue = new ArrayQueue(3); 9 | queue.add(1); 10 | queue.add(2); 11 | queue.add(3); 12 | System.out.println("查看队列中的数据"); 13 | queue.show(); 14 | System.out.println("查看队列头数据:" + queue.head()); 15 | System.out.println("查看队列尾数据:" + queue.tail()); 16 | // queue.add(4); 17 | System.out.println("获取队列数据:" + queue.get()); 18 | System.out.println("查看队列中的数据"); 19 | queue.show(); 20 | 21 | } 22 | } 23 | 24 | class ArrayQueue { 25 | private int maxSize; // 队列最大容量 26 | private int front; // 队列头,指向队列头的前一个位置 27 | private int rear; // 队列尾,指向队列尾的数据(及最后一个数据) 28 | private int arr[]; // 用于存储数据,模拟队列 29 | 30 | public ArrayQueue(int arrMaxSize) { 31 | maxSize = arrMaxSize; 32 | arr = new int[maxSize]; 33 | front = -1; 34 | rear = -1; 35 | } 36 | 37 | /** 38 | * 取出队列数据 39 | */ 40 | public int get() { 41 | if (isEmpty()) { 42 | throw new RuntimeException("队列空"); 43 | } 44 | return arr[++front]; 45 | } 46 | 47 | /** 48 | * 往队列存储数据 49 | */ 50 | public void add(int n) { 51 | if (isFull()) { 52 | System.out.println("队列已满"); 53 | return; 54 | } 55 | arr[++rear] = n; 56 | } 57 | 58 | /** 59 | * 显示队列中的数据 60 | */ 61 | public void show() { 62 | if (isEmpty()) { 63 | System.out.println("队列为空"); 64 | return; 65 | } 66 | for (int i = 0; i < arr.length; i++) { 67 | System.out.printf("arr[%d] = %d \n", i, arr[i]); 68 | } 69 | } 70 | 71 | /** 72 | * 查看队列的头部数据,注意:不是取出数据,只是查看 73 | * 74 | * @return 75 | */ 76 | public int head() { 77 | if (isEmpty()) { 78 | throw new RuntimeException("队列空"); 79 | } 80 | return arr[front + 1]; // front 指向队列头前一个元素,取头要 +1 81 | } 82 | 83 | /** 84 | * 查看队尾数据 85 | * 86 | * @return 87 | */ 88 | public int tail() { 89 | if (isEmpty()) { 90 | throw new RuntimeException("队列空"); 91 | } 92 | return arr[rear]; 93 | } 94 | 95 | // 队列是否已满 96 | private boolean isFull() { 97 | return rear == maxSize - 1; 98 | } 99 | 100 | // 队列是否为空 101 | private boolean isEmpty() { 102 | return rear == front; 103 | } 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/queue/CircleQueueDemo.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.queue; 2 | 3 | import java.util.Scanner; 4 | 5 | /** 6 | * 数组拟环形队列 7 | */ 8 | public class CircleQueueDemo { 9 | public static void main(String[] args) { 10 | CircleQueue queue = new CircleQueue(3); 11 | 12 | // 为了测试方便,写一个控制台输入的小程序 13 | Scanner scanner = new Scanner(System.in); 14 | boolean loop = true; 15 | char key = ' '; // 接受用户输入指令 16 | System.out.println("s(show): 显示队列"); 17 | System.out.println("e(exit): 退出程序"); 18 | System.out.println("a(add): 添加数据到队列"); 19 | System.out.println("g(get): 从队列取出数据"); 20 | System.out.println("h(head): 查看队列头的数据"); 21 | System.out.println("t(tail): 查看队列尾的数据"); 22 | System.out.println("p(isEmpty): 队列是否为空"); 23 | while (loop) { 24 | key = scanner.next().charAt(0); 25 | switch (key) { 26 | case 's': 27 | queue.show(); 28 | break; 29 | case 'e': 30 | loop = false; 31 | break; 32 | case 'a': 33 | System.out.println("请输入要添加到队列的整数:"); 34 | int value = scanner.nextInt(); 35 | queue.add(value); 36 | break; 37 | case 'g': 38 | try { 39 | int res = queue.get(); 40 | System.out.printf("取出的数据是:%d\n", res); 41 | } catch (Exception e) { 42 | System.out.println(e.getMessage()); 43 | } 44 | break; 45 | case 'h': 46 | try { 47 | int res = queue.head(); 48 | System.out.printf("队首数据:%d\n", res); 49 | } catch (Exception e) { 50 | System.out.println(e.getMessage()); 51 | } 52 | break; 53 | case 't': 54 | try { 55 | int res = queue.tail(); 56 | System.out.printf("队尾数据:%d\n", res); 57 | } catch (Exception e) { 58 | System.out.println(e.getMessage()); 59 | } 60 | break; 61 | case 'p': 62 | System.out.printf("队列是否为空:%s", queue.isEmpty()); 63 | break; 64 | } 65 | } 66 | } 67 | } 68 | 69 | class CircleQueue { 70 | private int maxSize; // 队列最大容量 71 | private int front; // 队列头,指向 队头 的元素 72 | private int rear; // 队列尾,指向 队尾 的下一个元素 73 | private int arr[]; // 用于存储数据,模拟队列 74 | 75 | public CircleQueue(int arrMaxSize) { 76 | maxSize = arrMaxSize + 1; 77 | arr = new int[maxSize]; 78 | front = 0; 79 | rear = 0; 80 | } 81 | 82 | /** 83 | * 取出队列数据 84 | */ 85 | public int get() { 86 | if (isEmpty()) { 87 | throw new RuntimeException("队列空"); 88 | } 89 | // front 指向的是队首的位置 90 | int value = arr[front]; 91 | // 需要向后移动,但是由于是环形,同样需要使用取模的方式来计算 92 | front = (front + 1) % maxSize; 93 | return value; 94 | } 95 | 96 | /** 97 | * 往队列存储数据 98 | */ 99 | public void add(int n) { 100 | if (isFull()) { 101 | System.out.println("队列已满"); 102 | return; 103 | } 104 | arr[rear] = n; 105 | // rear 指向的是下一个位置 106 | // 由于是环形队列,需要使用取模的形式来唤醒他的下一个位置 107 | rear = (rear + 1) % maxSize; 108 | } 109 | 110 | /** 111 | * 显示队列中的数据 112 | */ 113 | public void show() { 114 | if (isEmpty()) { 115 | System.out.println("队列为空"); 116 | return; 117 | } 118 | // 打印的时候,需要从队首开始打印 119 | // 打印的次数则是:有效的元素个数 120 | // 获取数据的下标:由于是环形的,需要使用取模的方式来获取 121 | for (int i = front; i < front + size(); i++) { 122 | int index = i % maxSize; 123 | System.out.printf("arr[%d] = %d \n", index, arr[index]); 124 | } 125 | } 126 | 127 | /** 128 | * 查看队列的头部数据,注意:不是取出数据,只是查看 129 | * 130 | * @return 131 | */ 132 | public int head() { 133 | if (isEmpty()) { 134 | throw new RuntimeException("队列空"); 135 | } 136 | return arr[front]; 137 | } 138 | 139 | /** 140 | * 查看队尾数据 141 | * 142 | * @return 143 | */ 144 | public int tail() { 145 | if (isEmpty()) { 146 | throw new RuntimeException("队列空"); 147 | } 148 | // rear - 1 是队尾数据,但是如果是环形收尾相接的时候 149 | // 那么 0 -1 就是 -1 了,负数时,则是数组的最后一个元素 150 | return rear - 1 < 0 ? arr[maxSize - 1] : arr[rear - 1]; 151 | } 152 | 153 | // 队列是否已满 154 | private boolean isFull() { 155 | return (rear + 1) % maxSize == front; 156 | } 157 | 158 | // 队列是否为空 159 | public boolean isEmpty() { 160 | return rear == front; 161 | } 162 | 163 | // 有效个数 164 | public int size() { 165 | return (rear + maxSize - front) % maxSize; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/recursion/Queue8.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.recursion; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 八皇后问题: 7 | *
  8 |  *     规则:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意 两个皇后 都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
  9 |  * 
10 | */ 11 | public class Queue8 { 12 | // 共有多少个皇后 13 | int max = 8; 14 | /** 15 | * 存放皇后位置的结果 16 | *
 17 |      * 下标:表示棋盘中的某一行
 18 |      * 对应的值:表示在这一行上,该皇后摆放在哪一列
 19 |      * 比如:array[0] = 1,表示在第 1 行的第 2 列上摆放了一个皇后
 20 |      *
 21 |      * 由于规则,一行只能有一个皇后,所以可以使用一维数组来代替二维数组的棋盘结果
 22 |      * 
23 | */ 24 | int[] array = new int[max]; 25 | int count = 0; // 统计有多少个结果 26 | 27 | public static void main(String[] args) { 28 | Queue8 queue8 = new Queue8(); 29 | queue8.check(0); // 从第 1 行开始放置 30 | } 31 | 32 | /** 33 | * 放置第 n 个(行)皇后 34 | * 35 | * @param n 36 | */ 37 | private void check(int n) { 38 | // n = 8,那么表示放第 9 个皇后,8 个皇后已经放完了 39 | // 表示找到了一个正确的结果,打印这个结果,并返回 40 | if (n == max) { 41 | count++; 42 | print(); 43 | return; 44 | } 45 | 46 | // 开始暴力对比,从该行的第一列开始尝试放置皇后,直到与前面所放置的不冲突 47 | for (int i = 0; i < max; i++) { 48 | // 在该行的第 i 列上放置一个皇后 49 | array[n] = i; 50 | // 检测与已经放置的是否冲突 51 | if (judge(n)) { 52 | // 如果不冲突,则表示该行的皇后放置没有问题 53 | // 开始进入下一行的皇后放置 54 | check(n + 1); 55 | } 56 | // 如果冲突,这里什么也不做 57 | // 因为是从第一列开始放置,如果冲突,则尝试放置到第 2 列上,直到放置成功 58 | } 59 | } 60 | 61 | /** 62 | * 判定要放置的这一个皇后,和前面已经摆放的位置是否冲突 63 | * 64 | * @param n 第 n 个皇后 65 | * @return 66 | */ 67 | private boolean judge(int n) { 68 | for (int i = 0; i < n; i++) { 69 | if ( 70 | /* 71 | 如果他们的摆放位置一样,说明是在同一列 72 | x .... 73 | x .... 74 | */ 75 | array[i] == array[n] 76 | /* 77 | 检测是否是同一斜列 78 | 下标: 代表的是第几行 79 | 值:代表的是第几列 80 | . x . . . n = 1,value = 1 81 | x . . . . i = 0,value = 0 82 | Math.abs(n - i) = 1 83 | Math.abs(array[n] - array[i]) = 1 84 | 85 | . . x . . n = 1,value = 2 86 | . x . . . i = 0,value = 1 87 | Math.abs(n - i) = 1 88 | Math.abs(array[n] - array[i]) = 1 89 | */ 90 | || Math.abs(n - i) == Math.abs(array[n] - array[i]) 91 | ) { 92 | return false; 93 | } 94 | } 95 | return true; 96 | } 97 | 98 | /** 99 | * 打印皇后的位置 100 | */ 101 | private void print() { 102 | System.out.printf("第 %02d 个结果 :", count); 103 | for (int i = 0; i < array.length; i++) { 104 | System.out.print(array[i] + " "); 105 | } 106 | System.out.println(); 107 | } 108 | 109 | /** 110 | * 下面是对于判定是否是同行,同列、同一斜列的分步骤测试,比较好理解 111 | */ 112 | @Test 113 | public void judgeTest() { 114 | /* 115 | * . . . . . . . . 116 | * x . . . . . . . 117 | */ 118 | Queue8 queue8 = new Queue8(); 119 | queue8.array[0] = 0; 120 | 121 | //======== 放置第 1 个皇后 122 | // 判断是否是同一列 123 | /* 124 | * x . . . . . . . // 计划放到这里 125 | * x . . . . . . . 126 | */ 127 | queue8.array[1] = 0; // 从第一列开始放置 128 | System.out.println("同一列,是否通过:" + queue8.judge(1)); 129 | 130 | /* 131 | * . x . . . . . . // 计划放到这里 132 | * x . . . . . . . 133 | */ 134 | queue8.array[1] = 1; 135 | // 第一列不行,放置到第 2 列上 136 | System.out.println("同一斜列,是否通过:" + queue8.judge(1)); 137 | 138 | /* 139 | * . . x . . . . . // 计划放到这里 140 | * x . . . . . . . 141 | */ 142 | queue8.array[1] = 2; 143 | // 第 2 列不行,放置到第 3 列上,这个肯定是可以的 144 | System.out.println("同一列或同一斜列,是否通过:" + queue8.judge(1)); 145 | 146 | //======== 放置第 3 个皇后 147 | /* 148 | * x . . . . . . . // 计划放到这里 149 | * . . x . . . . . 150 | * x . . . . . . . 151 | */ 152 | queue8.array[2] = 0; 153 | // 与第一行的在同一列上了 154 | System.out.println("同一列,是否通过:" + queue8.judge(2)); 155 | 156 | /* 157 | * . x . . . . . . // 计划放到这里 158 | * . . x . . . . . 159 | * x . . . . . . . 160 | */ 161 | queue8.array[2] = 1; 162 | // 第一列不行,放置到第 2 列 163 | // 这里与第 2 行的同一斜列了,也是不行的 164 | System.out.println("同一斜列,是否通过:" + queue8.judge(2)); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/recursion/RecursionTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.recursion; 2 | 3 | public class RecursionTest { 4 | public static void main(String[] args) { 5 | // test(4); 6 | // System.out.println(factorial(5)); 7 | System.out.println(factorial2(2)); 8 | } 9 | 10 | /** 11 | * 打印问题:打印信息是什么? 12 | * 13 | * @param n 14 | */ 15 | public static void test(int n) { 16 | if (n > 2) { 17 | test(n - 1); 18 | } 19 | System.out.println("n=" + n); 20 | } 21 | 22 | /** 23 | *
24 |      * 阶乘问题:传递一个数值, 从 1 开始乘以 2,再乘以 3,一直到 N,的结果,比如从 1 阶乘到 5,如下所示
25 |      * 1 * 2 = 2
26 |      * 2 * 3 = 6
27 |      * 6 * 4 = 24
28 |      * 24 * 5 = 120
29 |      * 结果是 120
30 |      * 
31 | * 32 | * @return 33 | */ 34 | public static int factorial(int n) { 35 | if (n == 1) { 36 | return 1; 37 | } else { 38 | return factorial(n - 1) * n; 39 | } 40 | } 41 | 42 | public static int factorial2(int n) { 43 | if (n == 1) { 44 | return 1; 45 | } else { 46 | int factorial = factorial2(n - 1); 47 | System.out.printf("%d * %d = %d \n", factorial, n, factorial * n); 48 | return factorial * n; 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/recursion/maze/Maze.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.recursion.maze; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | *
  7 |  * 迷宫问题求解
  8 |  * 
9 | */ 10 | public class Maze { 11 | @Test 12 | public void demo() { 13 | // 构建一个 8 行 7 列的地图 14 | int[][] map = initMap(8, 7); 15 | printMap(map); 16 | // 回溯不明显的路线 17 | setWay(map, 1, 1, 6, 5); 18 | // 回溯很明显的路线 19 | // setWay(map, 1, 1, 4, 1); 20 | System.out.println("路线查找完毕"); 21 | printMap(map); 22 | } 23 | 24 | /** 25 | *
 26 |      *  思路说明:
 27 |      *  1. 从 startX,startY 开始走,到 endX,endY 结束
 28 |      *  2. 当走到 endX,endY 时,表示已经找到了路线
 29 |      *  3. 约定:map 中的含义:
 30 |      *      0:表示该点没有走过
 31 |      *      1:表示围墙,不能走
 32 |      *      2:表示改点已走过,并且可以走
 33 |      *      3:表示改点已走过,但是走不通
 34 |      *
 35 |      *  4. 走迷宫,约定一个寻找路线的策略,也就是当你站在一个点的时候,你从哪一个方向开始探索?
 36 |      *     这里规定探索的方向为:下 -> 右 -> 上 -> 左,如果该点走不通,再回溯
 37 |      * 
38 | * 39 | * @param map 代表一张地图 40 | * @param startX 从哪一个点开始走 41 | * @param startY 42 | * @param endX 到哪一个点结束 43 | * @param endY 44 | * @return true: 表示该点可以走,false:表示改点不能走 45 | */ 46 | public boolean setWay(int[][] map, int startX, int startY, int endX, int endY) { 47 | // 如果当结束点已经走过,表示已经到达了出口 48 | // System.out.println(); 49 | // printMap(map); // 打开这个可以看到每一步的探索路径 50 | if (map[endX][endY] == 2) { 51 | return true; 52 | } 53 | // 那么开始我们的策略探索 54 | 55 | // 如果该点还没有走过,则可以尝试探索 56 | if (map[startX][startY] == 0) { 57 | // 先假定该点标可以通过,因为要去探索四周的点是否可以走 58 | map[startX][startY] = 2; 59 | // 下 -> 右 -> 上 -> 左 60 | // 根据策略:先往下走,如果可以走则返回 true 61 | if (setWay(map, startX + 1, startY, endX, endY)) { 62 | return true; 63 | } 64 | // 如果走不通,则继续往右边探索 65 | else if (setWay(map, startX, startY + 1, endX, endY)) { 66 | return true; 67 | } 68 | // 如果走不通,则继续往上边探索 69 | else if (setWay(map, startX - 1, startY, endX, endY)) { 70 | return true; 71 | } 72 | // 如果走不通,则继续往左边探索 73 | else if (setWay(map, startX, startY - 1, endX, endY)) { 74 | return true; 75 | } 76 | // 都走不通,表示改点是一个死点,四周都无法出去 77 | else { 78 | map[startX][startY] = 3; 79 | return false; 80 | } 81 | } else { 82 | // 如果不为 0,可能的情况是:1,2,3,这三种表示都表示不可以走 83 | return false; 84 | } 85 | } 86 | 87 | /** 88 | * 构建一个有挡板的地图 89 | *
 90 |      * 数字 1:表示挡板围墙,小球不可以经过
 91 |      * 数字 0:表示是路,小球可以经过
 92 |      * 起点:可以自定义起点
 93 |      * 出口:其实也可以自定义出口,但是本题规定,出口就是右下角的 0
 94 |      * 1 1 1 1 1 1 1
 95 |      * 1 0 0 0 0 0 1
 96 |      * 1 0 0 0 0 0 1
 97 |      * 1 1 1 0 0 0 1
 98 |      * 1 0 0 0 0 0 1
 99 |      * 1 0 0 0 0 0 1
100 |      * 1 0 0 0 0 0 1
101 |      * 1 1 1 1 1 1 1
102 |      * 
103 | * 104 | * @return 105 | */ 106 | private int[][] initMap(int row, int cloum) { 107 | // 构建一个 8 行 7 列的地图 108 | int[][] map = new int[row][cloum]; 109 | // 数字 1 表示挡板,构建一个有挡板的地图 110 | 111 | for (int i = 0; i < map[0].length; i++) { 112 | map[0][i] = 1; // 顶部增加挡板 113 | map[map.length - 1][i] = 1; // 底部增加挡板 114 | } 115 | 116 | for (int i = 0; i < map.length; i++) { 117 | map[i][0] = 1; // 左侧增加挡板 118 | map[i][map[0].length - 1] = 1; // 右侧增加挡板 119 | } 120 | 121 | // 中间的其他固定挡板 122 | map[3][1] = 1; 123 | map[3][2] = 1; 124 | return map; 125 | } 126 | 127 | public void printMap(int[][] map) { 128 | for (int i = 0; i < map.length; i++) { 129 | for (int j = 0; j < map[0].length; j++) { 130 | System.out.print(map[i][j] + " "); 131 | } 132 | System.out.println(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/search/BinarySearchTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.search; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | /** 10 | * 二分查找 11 | */ 12 | public class BinarySearchTest { 13 | @Test 14 | public void binaryTest() { 15 | int[] arr = new int[]{1, 8, 10, 89, 1000, 1234}; 16 | int findVal = 89; 17 | int result = binary(arr, 0, arr.length - 1, findVal); 18 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 19 | 20 | findVal = -1; 21 | result = binary(arr, 0, arr.length - 1, findVal); 22 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 23 | 24 | findVal = 123456; 25 | result = binary(arr, 0, arr.length - 1, findVal); 26 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 27 | 28 | findVal = 1; 29 | result = binary(arr, 0, arr.length - 1, findVal); 30 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 31 | } 32 | 33 | /** 34 | * @param arr 35 | * @param left 左边索引 36 | * @param right 右边索引 37 | * @param findVal 要查找的值 38 | * @return 未找到返回 -1,否则返回该值的索引 39 | */ 40 | private int binary(int[] arr, int left, int right, int findVal) { 41 | // 当找不到时,则返回 -1 42 | if (left > right) { 43 | return -1; 44 | } 45 | int mid = (left + right) / 2; 46 | int midVal = arr[mid]; 47 | // 相等则找到 48 | if (midVal == findVal) { 49 | return mid; 50 | } 51 | // 要查找的值在右边,则右递归 52 | if (findVal > midVal) { 53 | // mid 的值,就是当前对比的值,所以不需要判定 54 | return binary(arr, mid + 1, right, findVal); 55 | } 56 | return binary(arr, left, mid - 1, findVal); 57 | } 58 | 59 | 60 | @Test 61 | public void binary2Test() { 62 | int[] arr = new int[]{1, 8, 10, 89, 1000, 1000, 1234, 1234}; 63 | int findVal = 89; 64 | List result = binary2(arr, 0, arr.length - 1, findVal); 65 | System.out.println("查找值 " + findVal + ":" + (result == null ? "未找到" : "找到值,索引为:" + result)); 66 | 67 | findVal = -1; 68 | result = binary2(arr, 0, arr.length - 1, findVal); 69 | System.out.println("查找值 " + findVal + ":" + (result == null ? "未找到" : "找到值,索引为:" + result)); 70 | 71 | findVal = 123456; 72 | result = binary2(arr, 0, arr.length - 1, findVal); 73 | System.out.println("查找值 " + findVal + ":" + (result == null ? "未找到" : "找到值,索引为:" + result)); 74 | 75 | findVal = 1; 76 | result = binary2(arr, 0, arr.length - 1, findVal); 77 | System.out.println("查找值 " + findVal + ":" + (result == null ? "未找到" : "找到值,索引为:" + result)); 78 | 79 | findVal = 1000; 80 | result = binary2(arr, 0, arr.length - 1, findVal); 81 | System.out.println("查找值 " + findVal + ":" + (result == null ? "未找到" : "找到值,索引为:" + result)); 82 | 83 | findVal = 1234; 84 | result = binary2(arr, 0, arr.length - 1, findVal); 85 | System.out.println("查找值 " + findVal + ":" + (result == null ? "未找到" : "找到值,索引为:" + result)); 86 | } 87 | 88 | /** 89 | * 查找所有符合条件的下标 90 | * 91 | * @param arr 92 | * @param left 左边索引 93 | * @param right 右边索引 94 | * @param findVal 要查找的值 95 | * @return 未找到返回 -1,否则返回该值的索引 96 | */ 97 | private List binary2(int[] arr, int left, int right, int findVal) { 98 | // 当找不到时,则返回 -1 99 | if (left > right) { 100 | return null; 101 | } 102 | int mid = (left + right) / 2; 103 | int midVal = arr[mid]; 104 | // 相等则找到 105 | if (midVal == findVal) { 106 | List result = new ArrayList<>(); 107 | // 如果已经找到,则先不要退出 108 | // 因为二分查找的前提是:对一个有序的数组进行查找 109 | // 所以,我们只需要,继续挨个的往左边和右边查找目标值就好了 110 | int tempIndex = mid - 1; 111 | result.add(mid); 112 | // 先往左边找 113 | while (true) { 114 | // 当左边已经找完 115 | // 或则找到一个不与目标值相等的值,就可以跳出左边查找 116 | if (tempIndex < 0 || arr[tempIndex] != midVal) { 117 | break; 118 | } 119 | result.add(tempIndex); 120 | tempIndex--; 121 | } 122 | // 再往右边查找 123 | tempIndex = mid + 1; 124 | while (true) { 125 | if (tempIndex >= arr.length || arr[tempIndex] != midVal) { 126 | break; 127 | } 128 | result.add(tempIndex); 129 | tempIndex++; 130 | } 131 | return result; 132 | } 133 | // 要查找的值在右边,则右递归 134 | if (findVal > midVal) { 135 | // mid 的值,就是当前对比的值,所以不需要判定 136 | return binary2(arr, mid + 1, right, findVal); 137 | } 138 | return binary2(arr, left, mid - 1, findVal); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/search/FibonacciSearchTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.search; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | public class FibonacciSearchTest { 8 | @Test 9 | public void fibTest() { 10 | int[] arr = {1, 8, 10, 89, 1000, 1234}; 11 | System.out.println("原数组:" + Arrays.toString(arr)); 12 | int findVal = 1; 13 | int result = fibSearch(arr, findVal); 14 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 15 | 16 | findVal = -1; 17 | result = fibSearch(arr, findVal); 18 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 19 | 20 | findVal = 8; 21 | result = fibSearch(arr, findVal); 22 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 23 | 24 | findVal = 10; 25 | result = fibSearch(arr, findVal); 26 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 27 | 28 | findVal = 1000; 29 | result = fibSearch(arr, findVal); 30 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 31 | 32 | findVal = 1234; 33 | result = fibSearch(arr, findVal); 34 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 35 | 36 | findVal = 12345; 37 | result = fibSearch(arr, findVal); 38 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 39 | 40 | } 41 | 42 | public static int max_size = 20; 43 | 44 | private int fibSearch(int[] arr, int key) { 45 | // 构建一个斐波那契数列 46 | int[] f = fib(); 47 | // 查找 k,由数组长度,找到在斐波那契数列中的一个值 48 | int k = 0; 49 | int low = 0; 50 | int high = arr.length - 1; 51 | while (high > f[k] - 1) { 52 | k++; 53 | } 54 | 55 | // 构建临时数组 56 | int[] temp = Arrays.copyOf(arr, f[k]); 57 | // 将临时数组扩充的值用原始数组的最后一个值(最大值)填充 58 | for (int i = high + 1; i < temp.length; i++) { 59 | temp[i] = arr[high]; 60 | } 61 | 62 | int mid = 0; 63 | // 当两边没有交叉的时候,就都可以继续查找 64 | while (low <= high) { 65 | if (k == 0) { 66 | // 如果 k = 0 的话,就只有一个元素了,mid 则就是这个元素 67 | mid = low; 68 | } else { 69 | mid = low + f[k - 1] - 1; 70 | } 71 | // 要查找的值说明在数组的左侧 72 | if (key < temp[mid]) { 73 | high = mid - 1; 74 | // 1. 全部元素 = 前面的元素 + 后面的元素 75 | // 2. f[k] = f[k-1] + f[k-2] 76 | // k -1 , 得到这一段的个数,然后下一次按照这个个数进行黄金分割 77 | k--; 78 | } 79 | // 要查找的值在数组的右侧 80 | else if (key > temp[mid]) { 81 | low = mid + 1; 82 | k -= 2; 83 | } 84 | // 找到的话 85 | else { 86 | if (mid <= high) { 87 | return mid; 88 | } 89 | // 当 mid 值大于最高点的话 90 | // 也就是我们后面填充的值,其实他的索引就是最后一个值,也就是 high 91 | else { 92 | return high; 93 | } 94 | } 95 | } 96 | return -1; 97 | } 98 | 99 | private int[] fib() { 100 | int[] f = new int[20]; 101 | f[0] = 1; 102 | f[1] = 1; 103 | for (int i = 2; i < max_size; i++) { 104 | f[i] = f[i - 1] + f[i - 2]; 105 | } 106 | return f; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/search/InsertValueSearchTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.search; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class InsertValueSearchTest { 9 | /** 10 | * 先来看一个场景,在二分查找中查找需要几次 11 | */ 12 | @Test 13 | public void binary2Test() { 14 | int[] arr = new int[]{1, 8, 10, 89, 1000, 1000, 1234, 1234}; 15 | int findVal = 1; 16 | Integer result = binary(arr, 0, arr.length - 1, findVal); 17 | System.out.println("查找值 " + findVal + ":" + (result == null ? "未找到" : "找到值,索引为:" + result)); 18 | 19 | findVal = 1000; 20 | result = binary(arr, 0, arr.length - 1, findVal); 21 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 22 | 23 | findVal = 1234; 24 | result = binary(arr, 0, arr.length - 1, findVal); 25 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 26 | 27 | findVal = 12345; 28 | result = binary(arr, 0, arr.length - 1, findVal); 29 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 30 | 31 | findVal = 89; 32 | result = binary(arr, 0, arr.length - 1, findVal); 33 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 34 | } 35 | 36 | /** 37 | * @param arr 38 | * @param left 左边索引 39 | * @param right 右边索引 40 | * @param findVal 要查找的值 41 | * @return 未找到返回 -1,否则返回该值的索引 42 | */ 43 | private int binary(int[] arr, int left, int right, int findVal) { 44 | System.out.println("binary"); 45 | // 当找不到时,则返回 -1 46 | if (left > right) { 47 | return -1; 48 | } 49 | int mid = (left + right) / 2; 50 | int midVal = arr[mid]; 51 | // 相等则找到 52 | if (midVal == findVal) { 53 | return mid; 54 | } 55 | // 要查找的值在右边,则右递归 56 | if (findVal > midVal) { 57 | // mid 的值,就是当前对比的值,所以不需要判定 58 | return binary(arr, mid + 1, right, findVal); 59 | } 60 | return binary(arr, left, mid - 1, findVal); 61 | } 62 | 63 | @Test 64 | public void insertValueTest() { 65 | int[] arr = new int[100]; 66 | for (int i = 0; i < 100; i++) { 67 | arr[i] = i + 1; 68 | } 69 | 70 | int findVal = 1; 71 | int result = insertValueSearch(arr, 0, arr.length - 1, findVal); 72 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 73 | 74 | findVal = 50; 75 | result = insertValueSearch(arr, 0, arr.length - 1, findVal); 76 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 77 | 78 | findVal = 100; 79 | result = insertValueSearch(arr, 0, arr.length - 1, findVal); 80 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 81 | } 82 | 83 | public int insertValueSearch(int[] arr, int left, int right, int findValue) { 84 | System.out.println("insertValueSearch"); 85 | // if (left > right) { 86 | // return -1; 87 | // } 88 | // 这里可以对未找到进行优化 89 | // 同时对于查找的边界判定来说是必须的,因为 mid 求职是根据值来求,不进行边界判定,有可能就越界了 90 | if (left > right || findValue < arr[0] || findValue > arr[arr.length - 1]) { 91 | return -1; 92 | } 93 | int mid = left + (right - left) * (findValue - arr[left]) / (arr[right] - arr[left]); 94 | if (arr[mid] == findValue) { 95 | return mid; 96 | } 97 | 98 | // 当查找值大于该值,则表示目标值在右侧 99 | if (findValue > arr[mid]) { 100 | return insertValueSearch(arr, mid + 1, right, findValue); 101 | } 102 | 103 | // 当查找值小于该值,则表示目标值在左侧 104 | return insertValueSearch(arr, left, mid - 1, findValue); 105 | } 106 | 107 | /** 108 | * 查找非连续性的值 109 | */ 110 | @Test 111 | public void insertValueTest2() { 112 | int[] arr = new int[]{1, 8, 10, 89, 1000, 1000, 1234, 1234}; 113 | 114 | int findVal = 1; 115 | int result = insertValueSearch(arr, 0, arr.length - 1, findVal); 116 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 117 | 118 | findVal = 1000; 119 | result = insertValueSearch(arr, 0, arr.length - 1, findVal); 120 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 121 | 122 | findVal = 1234; 123 | result = insertValueSearch(arr, 0, arr.length - 1, findVal); 124 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 125 | 126 | findVal = 12345; 127 | result = insertValueSearch(arr, 0, arr.length - 1, findVal); 128 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 129 | 130 | findVal = 89; 131 | result = insertValueSearch(arr, 0, arr.length - 1, findVal); 132 | System.out.println("查找值 " + findVal + ":" + (result == -1 ? "未找到" : "找到值,索引为:" + result)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/search/SeqSearchTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.search; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 线性查找 7 | */ 8 | public class SeqSearchTest { 9 | @Test 10 | public void seqSearchTest() { 11 | int[] arr = {1, 8, 10, 89, 1000, 1234}; 12 | int i = seqSearch(arr, 1000); 13 | System.out.println("查找目标值 1000:" + (i == -1 ? "未找到" : "已找到,下标为 " + i)); 14 | i = seqSearch(arr, -990); 15 | System.out.println("查找目标值 -990:" + (i == -1 ? "未找到" : "已找到,下标为 " + i)); 16 | } 17 | 18 | /** 19 | * 实现的线性查找法是找到首个出现的位置 20 | * 21 | * @param arr 22 | * @param value 23 | * @return 24 | */ 25 | public int seqSearch(int[] arr, int value) { 26 | for (int i = 0; i < arr.length; i++) { 27 | if (arr[i] == value) { 28 | return i; 29 | } 30 | } 31 | return -1; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/bubble/BubbleSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.bubble; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 冒泡排序测试 11 | */ 12 | public class BubbleSortTest { 13 | /** 14 | * 为了更好的理解,这里把冒泡排序的演变过程演示出来 15 | */ 16 | @Test 17 | public void processDemo() { 18 | int arr[] = {3, 9, -1, 10, -2}; 19 | 20 | // 第 1 趟排序:将最大的数排在最后 21 | // 总共排序:arr.length - 1 22 | int temp = 0; // 临时变量,交换的时候使用 23 | for (int i = 0; i < arr.length - 1; i++) { 24 | if (arr[i] > arr[i + 1]) { 25 | temp = arr[i]; 26 | arr[i] = arr[i + 1]; 27 | arr[i + 1] = temp; 28 | } 29 | } 30 | System.out.println("第 1 趟排序后的数组"); 31 | System.out.println(Arrays.toString(arr)); 32 | 33 | // 第 2 趟排序:将第 2 大的数排在倒数第 2 位 34 | // 总共排序:arr.length - 1 - 1 ; 35 | // 从头开始排序,其他没有变化,只是将排序次数减少了一次 36 | for (int i = 0; i < arr.length - 1 - 1; i++) { 37 | if (arr[i] > arr[i + 1]) { 38 | temp = arr[i]; 39 | arr[i] = arr[i + 1]; 40 | arr[i + 1] = temp; 41 | } 42 | } 43 | System.out.println("第 2 趟排序后的数组"); 44 | System.out.println(Arrays.toString(arr)); 45 | 46 | // 第 3 趟排序:将第 3 大的数排在倒数第 3 位 47 | // 总共排序:arr.length - 1 - 2 ; 48 | // 从头开始排序,其他没有变化,只是将排序次数减少了 2 次 49 | for (int i = 0; i < arr.length - 1 - 2; i++) { 50 | if (arr[i] > arr[i + 1]) { 51 | temp = arr[i]; 52 | arr[i] = arr[i + 1]; 53 | arr[i + 1] = temp; 54 | } 55 | } 56 | System.out.println("第 3 趟排序后的数组"); 57 | System.out.println(Arrays.toString(arr)); 58 | 59 | // 第 4 趟排序:将第 4 大的数排在倒数第 4 位 60 | // 总共排序:arr.length - 1 - 3 ; 61 | // 从头开始排序,其他没有变化,只是将排序次数减少了 3 次 62 | for (int i = 0; i < arr.length - 1 - 3; i++) { 63 | if (arr[i] > arr[i + 1]) { 64 | temp = arr[i]; 65 | arr[i] = arr[i + 1]; 66 | arr[i + 1] = temp; 67 | } 68 | } 69 | System.out.println("第 4 趟排序后的数组"); 70 | System.out.println(Arrays.toString(arr)); 71 | 72 | // 第 5 趟没有必要,因为这里有 5 个数字,确定了 4 个数字,剩下的那一个就已经出来了 73 | } 74 | 75 | /** 76 | * 从上述的 4 趟排序过程来看,循环体都是一样的,只是每次循环的次数在减少,那么就可以如下演变 77 | */ 78 | @Test 79 | public void processDemo2() { 80 | int arr[] = {3, 9, -1, 10, -2}; 81 | 82 | // 总共排序:arr.length - 1 83 | int temp = 0; // 临时变量,交换的时候使用 84 | for (int j = 0; j < arr.length - 1; j++) { 85 | for (int i = 0; i < arr.length - 1 - j; i++) { 86 | if (arr[i] > arr[i + 1]) { 87 | temp = arr[i]; 88 | arr[i] = arr[i + 1]; 89 | arr[i + 1] = temp; 90 | } 91 | } 92 | System.out.println("第 " + (j + 1) + " 趟排序后的数组"); 93 | System.out.println(Arrays.toString(arr)); 94 | } 95 | } 96 | 97 | /** 98 | * 优化:当某一轮未交换位置,则表示序列有序了 99 | */ 100 | @Test 101 | public void processDemo3() { 102 | int arr[] = {3, 9, -1, 10, 20}; 103 | 104 | // 总共排序:arr.length - 1 105 | int temp = 0; // 临时变量,交换的时候使用 106 | boolean change = false; 107 | for (int j = 0; j < arr.length - 1; j++) { 108 | change = false; 109 | for (int i = 0; i < arr.length - 1 - j; i++) { 110 | if (arr[i] > arr[i + 1]) { 111 | temp = arr[i]; 112 | arr[i] = arr[i + 1]; 113 | arr[i + 1] = temp; 114 | change = true; 115 | } 116 | } 117 | if (!change) { 118 | // 如果 1 轮下来,都没有进行排序,则可以提前退出 119 | break; 120 | } 121 | System.out.println("第 " + (j + 1) + " 趟排序后的数组"); 122 | System.out.println(Arrays.toString(arr)); 123 | } 124 | } 125 | 126 | /** 127 | * 测试封装后的算法 128 | */ 129 | @Test 130 | public void bubbleSortTest() { 131 | int[] arr = {3, 9, -1, 10, 20}; 132 | System.out.println("排序前:" + Arrays.toString(arr)); 133 | bubbleSort(arr); 134 | System.out.println("排序后:" + Arrays.toString(arr)); 135 | } 136 | 137 | /** 138 | * 把排序算法封装成一个方法,方便被复用 139 | * 140 | * @param arr 141 | */ 142 | public static void bubbleSort(int[] arr) { 143 | // 总共排序:arr.length - 1 144 | int temp = 0; // 临时变量,交换的时候使用 145 | boolean change = false; 146 | for (int j = 0; j < arr.length - 1; j++) { 147 | change = false; 148 | for (int i = 0; i < arr.length - 1 - j; i++) { 149 | if (arr[i] > arr[i + 1]) { 150 | temp = arr[i]; 151 | arr[i] = arr[i + 1]; 152 | arr[i + 1] = temp; 153 | change = true; 154 | } 155 | } 156 | if (!change) { 157 | // 如果 1 轮下来,都没有进行排序,则可以提前退出 158 | break; 159 | } 160 | } 161 | } 162 | 163 | /** 164 | * 大量数据排序时间测试 165 | */ 166 | @Test 167 | public void bulkDataSort() { 168 | int max = 80_000; 169 | int[] arr = new int[max]; 170 | for (int i = 0; i < max; i++) { 171 | arr[i] = (int) (Math.random() * 80_000); 172 | } 173 | 174 | Instant startTime = Instant.now(); 175 | bubbleSort(arr); 176 | // System.out.println(Arrays.toString(arr)); 177 | Instant endTime = Instant.now(); 178 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/insertion/InsertionSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.insertion; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 插入排序测试 11 | */ 12 | public class InsertionSortTest { 13 | 14 | /** 15 | * 为了更好的理解,将插入排序演变过程演示出来,逐步推导的方式演示 16 | */ 17 | @Test 18 | public void processDemo() { 19 | int arr[] = {101, 34, 119, 1}; 20 | System.out.println("原始数组:" + Arrays.toString(arr)); 21 | processSelectSort(arr); 22 | } 23 | 24 | private void processSelectSort(int[] arr) { 25 | // 第 1 轮: 26 | // 有序表:101 27 | // 无序表:34, 119, 1 28 | // 值的注意的是:不一定就要分两个数组来操作,这样想,就想太复杂了。只是看成两段 29 | int currentInsertValue = arr[1]; // 当前要找位置插入的数 30 | int insertIndex = 1 - 1; // 要插入的数与有序表中最后一个开始比较(存储的是下标)。也就是当前数的上一个开始比较 31 | 32 | while (insertIndex >= 0 // 保证插入数组不越界 33 | // 当前与有序列表中的数据进行对比 34 | // 如果小于:则说明有序表中的该值需要向后移动 35 | // 例如:101, 34, 119, 1 , 34 < 101, 移动后变成 101, 101, 119, 1 36 | // currentInsertValue = 34,insertIndex = -1 37 | && currentInsertValue < arr[insertIndex] 38 | ) { 39 | arr[insertIndex + 1] = arr[insertIndex]; // 向后移动一位 40 | insertIndex--; // 并将下一次要比较的有序列表往前一位 41 | } 42 | // 当退出循环后:要么 insertIndex = -1, 说明应该插入到有序列表中的第一个位置 43 | // 要么 就找到了合适的位置,所以无论哪一种情况, insertIndex + 1,才是当前数要插入的位置 44 | arr[insertIndex + 1] = currentInsertValue; 45 | System.out.println("第 1 轮排序后:" + Arrays.toString(arr)); 46 | 47 | // 第 2 轮: 48 | // 有序表:34, 101 49 | // 无序表:119, 1 50 | currentInsertValue = arr[2]; // 当前要找位置插入的数:119 51 | insertIndex = 2 - 1; // 当前的上一个开始 52 | 53 | while (insertIndex >= 0 // 保证插入数组不越界 54 | // 当前与有序列表中的数据进行对比 55 | // 如果小于:则说明有序表中的该值需要向后移动 56 | // 例如:34, 101, 119, 1 , 119 > 101, 这里不满足,则退出 57 | // currentInsertValue = 119,insertIndex = 1 58 | && currentInsertValue < arr[insertIndex] 59 | ) { 60 | arr[insertIndex + 1] = arr[insertIndex]; // 向后移动一位 61 | insertIndex--; // 并将下一次要比较的有序列表往前一位 62 | } 63 | // 当退出循环后:这一轮,则是当前数大于有序表中的最后一个,不用换位置 64 | // 那么 1 + 1 = 2,下标和当前的数是相同的,就算这里替换了,数组还是保持不变的 65 | // 这里可以判断下是否相同,不相同再赋值,也是可以的 66 | arr[insertIndex + 1] = currentInsertValue; 67 | System.out.println("第 2 轮排序后:" + Arrays.toString(arr)); 68 | 69 | // 第 3 轮: 70 | // 有序表:34, 101, 119 71 | // 无序表:1 72 | currentInsertValue = arr[3]; // 当前要找位置插入的数:1 73 | insertIndex = 3 - 1; // 当前的上一个开始 74 | 75 | while (insertIndex >= 0 // 保证插入数组不越界 76 | // 当前与有序列表中的数据进行对比 77 | // 如果小于:则说明有序表中的该值需要向后移动 78 | // 例如:34, 101, 119, 1 , 1 < 119, 移动后:34, 101, 119, 119 79 | // currentInsertValue = 1,insertIndex = 2 80 | // 然后继续下一个比较: 34, 101, 119, 119, 1 < 101 , 移动后:34, 101, 101, 119 81 | // currentInsertValue = 1,insertIndex = 1 82 | // 然后继续下一个比较: 34, 101, 101, 119, 1 < 34 , 移动后:34, 34, 101, 119 83 | // currentInsertValue = 1,insertIndex = -1 84 | && currentInsertValue < arr[insertIndex] 85 | ) { 86 | arr[insertIndex + 1] = arr[insertIndex]; // 向后移动一位 87 | insertIndex--; // 并将下一次要比较的有序列表往前一位 88 | } 89 | // 当退出循环后:这一轮,则是 insertIndex = -1,退出循环,把当前的数插入到有序表的开头 90 | arr[insertIndex + 1] = currentInsertValue; 91 | System.out.println("第 3 轮排序后:" + Arrays.toString(arr)); 92 | } 93 | 94 | /** 95 | *
 96 |      * 从上述推导过程,我们发现了规律:
 97 |      *   循环体和赋值都是一样的
 98 |      *   不同的是当前待插入的数,和要比较的有序表下标
 99 |      * 
100 | */ 101 | @Test 102 | public void processDemo2() { 103 | int arr[] = {101, 34, 119, 1}; 104 | System.out.println("原始数组:" + Arrays.toString(arr)); 105 | processSelectSort2(arr); 106 | } 107 | 108 | private void processSelectSort2(int[] arr) { 109 | for (int i = 1; i < arr.length; i++) { 110 | // 只是将这两句代码的代表当前要插入的下标用外循环的变量来代替了 111 | int currentInsertValue = arr[i]; 112 | int insertIndex = i - 1; 113 | 114 | while (insertIndex >= 0 115 | && currentInsertValue < arr[insertIndex] 116 | ) { 117 | arr[insertIndex + 1] = arr[insertIndex]; 118 | insertIndex--; 119 | } 120 | arr[insertIndex + 1] = currentInsertValue; 121 | System.out.println("第 " + i + " 轮排序后:" + Arrays.toString(arr)); 122 | } 123 | } 124 | 125 | /** 126 | * 大量数据排序时间测试 127 | */ 128 | @Test 129 | public void bulkDataSort() { 130 | int max = 80_000; 131 | int[] arr = new int[max]; 132 | for (int i = 0; i < max; i++) { 133 | arr[i] = (int) (Math.random() * 80_000); 134 | } 135 | 136 | Instant startTime = Instant.now(); 137 | selectSort(arr); 138 | // System.out.println(Arrays.toString(arr)); 139 | Instant endTime = Instant.now(); 140 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 141 | } 142 | 143 | private void selectSort(int[] arr) { 144 | for (int i = 1; i < arr.length; i++) { 145 | // 只是将这两句代码的代表当前要插入的下标用外循环的变量来代替了 146 | int currentInsertValue = arr[i]; 147 | int insertIndex = i - 1; 148 | 149 | while (insertIndex >= 0 150 | && currentInsertValue < arr[insertIndex] 151 | ) { 152 | arr[insertIndex + 1] = arr[insertIndex]; 153 | insertIndex--; 154 | } 155 | arr[insertIndex + 1] = currentInsertValue; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/merge/MergeSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.merge; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 归并排序 11 | */ 12 | public class MergeSortTest { 13 | @Test 14 | public void sortTest() { 15 | int[] arr = new int[]{8, 4, 5, 7, 1, 3, 6, 2}; 16 | int[] temp = new int[arr.length]; 17 | mergeSort(arr, 0, arr.length - 1, temp); 18 | System.out.println("排序后:" + Arrays.toString(arr)); 19 | } 20 | 21 | /** 22 | * 分 和 合并 23 | */ 24 | public void mergeSort(int[] arr, int left, int right, int[] temp) { 25 | if (left < right) { 26 | int mid = (left + right) / 2; 27 | // 先分解左侧 28 | mergeSort(arr, left, mid, temp); 29 | // 再分解右侧 30 | mergeSort(arr, mid + 1, right, temp); 31 | // 最后合并 32 | // 由于是递归,合并这里一定是栈顶的先执行 33 | // 第一次:left = 0,right=1 34 | // 第二次:left = 2,right=3 35 | // 第三次:left = 0,right=3 36 | // System.out.println("left=" + left + ";right=" + right); 37 | merge(arr, left, mid, right, temp); 38 | } 39 | } 40 | 41 | /** 42 | *
 43 |      *  最难的是合并,所以可以先完成合并的方法,此方法请参考 基本思想 和 思路分析中的图解来完成
 44 |      *
 45 |      * 
46 | * 47 | * @param arr 要排序的原始数组 48 | * @param left 因为是合并,所以要得到左右两边的的数组信息,这个是左侧数组的第一个索引值 49 | * @param mid 因为是一个数组,标识是第一个数组的最后一个索引,即 mid+1 是右侧数组的第一个索引 50 | * @param right 右侧数组的结束索引 51 | * @param temp 临时空间 52 | */ 53 | public void merge(int[] arr, int left, int mid, int right, int[] temp) { 54 | // 1. 按规则将两个数组合并到 temp 中 55 | // 定时临时变量,用来遍历数组比较 56 | int l = left; // 左边有序数组的初始索引 57 | int r = mid + 1; // 右边有序数组的初始索引 58 | int t = 0; // temp 数组中当前最后一个有效数据的索引 59 | 60 | // 因为是合并两个数组,所以需要两边的数组都还有值的时候才能进行比较合并 61 | while (l <= mid && r <= right) { 62 | // 如果左边的比右边的小,则将左边的该元素填充到 temp 中 63 | // 并移动 t++,l++,好继续下一个 64 | if (arr[l] < arr[r]) { 65 | temp[t] = arr[l]; 66 | t++; 67 | l++; 68 | } 69 | // 否则则将右边的移动到 temp 中 70 | else { 71 | temp[t] = arr[r]; 72 | t++; 73 | r++; 74 | } 75 | } 76 | // 2. 如果有任意一边的数组还有值,则依序将剩余数据填充到 temp 中 77 | // 如果左侧还有值 78 | while (l <= mid) { 79 | temp[t] = arr[l]; 80 | t++; 81 | l++; 82 | } 83 | // 如果右侧还有值 84 | while (r <= right) { 85 | temp[t] = arr[r]; 86 | t++; 87 | r++; 88 | } 89 | 90 | // 3. 将 temp 数组,拷贝到原始数组 91 | // 注意:只拷贝当前有效数据到对应的原始数据中 92 | int tempL = left; // 从左边开始拷贝 93 | t = 0; // temp 中的有效值,可以通过下面的 right 判定,因为合并两个数组到 temp 中,最大值则是 right 94 | /* 95 | * 对于 8,4,5,7,1,3,6,2 这个数组, 96 | * 从栈顶开始合并:8,4 | 5,7 1,3 | 6,2 97 | * 先左递归的话: 98 | * 第一次:8,4 合并:tempL=0,right=1 99 | * 第二次:5,7 合并:tempL=2,right=3 100 | * 第三次:4,8 | 5,7 进行合并,tempL=0,right=3 101 | * 直到左递归完成,得到左侧一个有序的序列:4,5,7,8 然后开始右递归 102 | * 最后回到栈底分解成两个数组的地方,将两个数组合并成一个有序数组 103 | */ 104 | // System.out.println("tempL=" + tempL + "; right=" + right); 105 | while (tempL <= right) { 106 | arr[tempL] = temp[t]; 107 | tempL++; 108 | t++; 109 | } 110 | } 111 | 112 | /** 113 | * 大量数据排序时间测试 114 | */ 115 | @Test 116 | public void bulkDataSort() { 117 | int max = 80_000; 118 | // max = 8; 119 | int[] arr = new int[max]; 120 | for (int i = 0; i < max; i++) { 121 | arr[i] = (int) (Math.random() * 80_000); 122 | } 123 | if (arr.length < 10) { 124 | System.out.println("原始数组:" + Arrays.toString(arr)); 125 | } 126 | Instant startTime = Instant.now(); 127 | int[] temp = new int[arr.length]; 128 | mergeSort(arr, 0, arr.length - 1, temp); 129 | if (arr.length < 10) { 130 | System.out.println("排序后:" + Arrays.toString(arr)); 131 | } 132 | Instant endTime = Instant.now(); 133 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/merge/MyMergeSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.merge; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * 为了巩固记忆,自己根据基本思想进行默写实现 9 | */ 10 | public class MyMergeSortTest { 11 | @Test 12 | public void sortTest() { 13 | int[] arr = new int[]{8, 4, 5, 7, 1, 3, 6, 2}; 14 | mergeSort(arr); 15 | System.out.println("排序后:" + Arrays.toString(arr)); 16 | } 17 | 18 | public void mergeSort(int arr[]) { 19 | int[] temp = new int[arr.length]; 20 | doMergeSort(arr, 0, arr.length - 1, temp); 21 | } 22 | 23 | /** 24 | * 分治 与 合并 25 | * 26 | * @param arr 27 | * @param left 28 | * @param right 29 | * @param temp 30 | */ 31 | private void doMergeSort(int[] arr, int left, int right, int[] temp) { 32 | // 当还可以分解时,就做分解操作 33 | if (left < right) { 34 | int mid = (left + right) / 2; 35 | // 先左递归分解 36 | doMergeSort(arr, left, mid, temp); 37 | // 再右递归分解 38 | doMergeSort(arr, mid + 1, right, temp); 39 | // 左递归分解到栈顶时,其实也是分为左右数组 40 | // 左右都到栈顶时,开始合并: 41 | // 第一次:合并的是 0,1 的索引,分解到最后的时候,其实只有一个数为一组,所以第一次合并是合并两个数字 42 | merge(arr, left, mid, right, temp); 43 | } 44 | } 45 | 46 | /** 47 | * 开始合并,每次合并都是两组,第一次合并是两个数字,左右一组都只有一个数字 48 | * 49 | * @param arr 50 | * @param left 51 | * @param mid 52 | * @param right 53 | * @param temp 54 | */ 55 | private void merge(int[] arr, int left, int mid, int right, int[] temp) { 56 | // 1. 按照规则,将 temp 数组填充 57 | // 2. 当任意一边填充完成后,剩余未填充的依次填充到 temp 数组中 58 | // 3. 将 temp 数组的有效内容,拷贝会原数组,也就是将这次排序好的数组覆盖回原来排序的原数组中 59 | 60 | int l = left; // 左侧数组初始索引 61 | int r = mid + 1; // 右侧数组初始索引 62 | int t = 0; // 当前 temp 中有效数据的最后一个元素索引 63 | // 1. 按照规则,将 temp 数组填充 64 | // 当两边都还有数字可比较的时候,进行比较,然后填充 temp 数组 65 | // 只要任意一边没有数值可用时,就仅需到下一步骤 66 | while (l <= mid && r <= right) { 67 | // 当左边比右边小时,则填充到 temp 数组中 68 | if (arr[l] < arr[r]) { 69 | // 赋值完成后,t 和 l 都需要 +1,往后移动一个位置 70 | // t++ 是先把 t 进行赋值,再进行 t+1 操作 71 | temp[t++] = arr[l++]; 72 | } else { 73 | // 当不满足时,则说明 右侧数字比左侧的小,进行右侧的填充 74 | temp[t++] = arr[r++]; 75 | } 76 | } 77 | 78 | // 2. 有可能有其中一边会有剩余数字未填充到 temp 中,进行收尾处理 79 | while (l <= mid) { 80 | temp[t++] = arr[l++]; 81 | } 82 | while (r <= right) { 83 | temp[t++] = arr[r++]; 84 | } 85 | 86 | // 3. 将这个有序数组,覆盖会原始排序的数组中 87 | t = 0; 88 | int tempL = left; // 从左侧开始,到右侧结束,就是这一次要合并的两组数据 89 | // System.out.println("tempL=" + tempL + "; right=" + right); 90 | while (tempL <= right) { 91 | arr[tempL++] = temp[t++]; 92 | } 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/quick/QuickSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.quick; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 快速排序测试 11 | */ 12 | public class QuickSortTest { 13 | @Test 14 | public void processDemo() { 15 | int arr[] = {-9, 78, 0, 23, -567, 70}; 16 | System.out.println("原始数组:" + Arrays.toString(arr)); 17 | processQuickSort(arr, 0, arr.length - 1); 18 | } 19 | 20 | /** 21 | * @param arr 22 | * @param left 左变这一组的下标起始点,到中间值,则为一组 23 | * @param right 右边这一组的下标结束点,到中间值,则为一组 24 | */ 25 | public void processQuickSort(int[] arr, int left, int right) { 26 | /* 27 | 基本思想:选择一个基准值,将基准值小分成一组,比基准值大的分成一组。 28 | 这里的实现思路: 29 | 1. 挑选的基准值为数组中间的值 30 | 2. 中间值就把数组分成了两组 31 | 3. 左边一组,从左到右,挨个与 基准值 比较,找出比基准值大的值 32 | 4. 右边一组,从右到左,挨个与 基准值 比较,找出比基准值小的值 33 | 5. 左右两边各找到一个值,那么就让这两个值进行交换 34 | 6. 然后继续找,直到左右两边碰到,这一轮就结束。这一轮就称为快速排序 35 | 7. 继续对分出来的小组,进行上述的快速排序操作,直到组内只剩下一个数时,则排序完成 36 | 37 | l ------------ pivot --------------- r 38 | 一组从左往右找 一组从右往左找 39 | */ 40 | int l = left; 41 | int r = right; 42 | // 中心点,让这个点作为基准值 43 | int pivot = arr[(left + right) / 2]; 44 | // 当他们没有碰到的时候,说明还这一轮还可以继续找 45 | while (l < r) { 46 | // 左边:当找到大于基准值时,则表示该值需要交换到右侧去: arr[l] > pivot 47 | // 也就是说,如果 arr[l] < pivot,则表示还没有找到比基准值大的数 48 | // 注意:不能等于 pivort,因为最差的情况没有找到,则最后 arr[l] 就是 pivot 这个值,那么就会出现死循环 49 | while (arr[l] < pivot) { 50 | l++; // 所以让左边这一组继续找 51 | } 52 | 53 | // 右边:当找到小于基准值时,则表示该值需要交换到左侧去:arr[r] < pivot 54 | // 那么这里和上面的相反 55 | while (arr[r] > pivot) { 56 | r--; 57 | } 58 | 59 | // 当左侧与右侧相碰时,说明两边都没有找到,这一轮比进行交换 60 | // 等于表示,找到了中间的下标 61 | if (l >= r) { 62 | break; 63 | } 64 | 65 | // 当找到时,则进行交换 66 | int temp = arr[l]; 67 | arr[l] = arr[r]; 68 | arr[r] = temp; 69 | 70 | // 当交换换后, 71 | // 当数组是: {-9, 78, 0, -23, 0, 70} 时,就可以验证这里的逻辑 72 | // 如果没有这个判定,将会导致,l 永远 小于 r。循环不能退出来的情况 73 | if (arr[l] == pivot) { 74 | /* 75 | 不能让自己往前移动 1,因为当交换完成后为:{-9, 0, 0, -23, 78, 70} 76 | l = 1, = 0 77 | r = 4, = 78 78 | 如果 l 等于 2,那么相当于下一轮,基准值 - 将会与 -23 进行交换,导致基准值变化了 79 | 所以,让 r - 1,还有一个原因是,r 是刚刚交换过的,一定比 基准值大,所以没有必要再和基准值比较了 80 | */ 81 | r -= 1; 82 | } 83 | // 这里和上面一致,如果说,先走了上面的 r-=1 84 | // 这里也满足,那么说明,下一次是相同的两个值,一个是 r 一个是基准值(满足条件 l+1 了,所以就有可能指向了基准值),进行交换 85 | // 但是他们是相同的值,交换后,不影响。但是再走这完这里逻辑时,就会导致 l > r,循环退出 86 | if (arr[r] == pivot) { 87 | l += 1; 88 | } 89 | } 90 | System.out.println("第 1 轮排序后:" + Arrays.toString(arr)); 91 | 92 | // 如果 l = r,会出现死循环 93 | if (l == r) { 94 | l++; 95 | r--; 96 | } 97 | 98 | // 开始左递归 99 | // 上面算法是 r--,l++ ,往中间走,当 left < r 时,表示还可以继续分组 100 | if (left < r) { 101 | processQuickSort(arr, left, r); 102 | } 103 | 104 | if (right > l) { 105 | processQuickSort(arr, l, right); 106 | } 107 | } 108 | 109 | /** 110 | * 快速排序默写实现 111 | *
112 |      *     基本思想:通过一趟将要排序的数据,分隔成独立的两个部分,一部分的所有数据都比另一部分的所有数据要小。
113 |      *     思路分析:
114 |      *      {-9, 78, 0, 23, -567, 70};  length=6
115 |      *      1. 挑选中间的值作为 基准值:(0 + (6 -1))/2= [2] = 0
116 |      *      2. 左侧 left 部分,从 0 开始到中间值 -1: 0,1: -9, 78,找出一个比基准值大的数
117 |      *      3. 右侧 right 部分,从中间值 + 1 到数组大小-1:3,5:23,-567, 70,找出一个比基准值小的数
118 |      *      4. 如果找到,则将他们进行交换,这样一轮下来,就完成了一次快速排序:一部分的所有数据都比另一部分的所有数据要小。
119 |      *      4. 如果左侧部分还可以分组,则进行左侧递归调用
120 |      *      5. 如果右侧部分还可以分组,则进行右侧递归调用
121 |      *
122 |      *    简单说:一轮快速排序示意图如下:
123 |      *                   中间的基准值
124 |      *      l ------------ pivot --------------- r
125 |      *      一组从左往右找               一组从右往左找
126 |      *      找到比基准值大的数          找出一个比基准值小的数
127 |      *                    然后进行交换
128 |      * 
129 | */ 130 | @Test 131 | public void quickSortTest() { 132 | int arr[] = {-9, 78, 0, 23, -567, 70}; 133 | // int arr[] = {-9, 78, 0, -23, 0, 70}; // 在推导过程中,将会导致交换异常的数组,在这里不会出现那种情况 134 | int left = 0; 135 | int right = arr.length - 1; 136 | System.out.println("原始数组:" + Arrays.toString(arr)); 137 | quickSort(arr, left, right); 138 | System.out.println("排序后:" + Arrays.toString(arr)); 139 | } 140 | 141 | public void quickSort(int[] arr, int left, int right) { 142 | // 找到中间值 143 | int pivotIndex = (left + right) / 2; 144 | int pivot = arr[pivotIndex]; 145 | int l = left; 146 | int r = right; 147 | while (l < r) { 148 | // 从左往右找,直到找到一个数,比基准值大的数 149 | while (arr[l] < pivot) { 150 | l++; 151 | } 152 | // 从右往左找,知道找到一个数,比基准值小的数 153 | while (arr[r] > pivot) { 154 | r--; 155 | } 156 | // 表示未找到 157 | if (l >= r) { 158 | break; 159 | } 160 | // 进行交换 161 | int temp = arr[l]; 162 | arr[l] = arr[r]; 163 | arr[r] = temp; 164 | 165 | // 那么下一轮,左侧的这个值将不再参与排序,因为刚交换过,一定比基准值小 166 | // 那么下一轮,右侧的这个值将不再参与排序,因为刚交换过,一定比基准值大 167 | r--; 168 | l++; 169 | } 170 | 171 | // 当一轮找完后,没有找到,则是中间值时, 172 | // 需要让他们插件而过,也就是重新分组,中间值不再参与分组 173 | // 否则,在某些情况下,会进入死循环 174 | if (l == r) { 175 | l++; 176 | r--; 177 | } 178 | // 如果左侧还可以继续分组,则继续快排 179 | // 由于擦肩而过了,那么左侧的组值,则是最初的开始与中间值的前一个,也就是这里得到的 r 180 | if (left < r) { 181 | quickSort(arr, left, r); 182 | } 183 | if (right > l) { 184 | quickSort(arr, l, right); 185 | } 186 | } 187 | 188 | /** 189 | * 大量数据排序时间测试 190 | */ 191 | @Test 192 | public void bulkDataSort() { 193 | int max = 80_000; 194 | // int max = 8; 195 | int[] arr = new int[max]; 196 | for (int i = 0; i < max; i++) { 197 | arr[i] = (int) (Math.random() * 80_000); 198 | } 199 | if (arr.length < 10) { 200 | System.out.println("原始数组:" + Arrays.toString(arr)); 201 | } 202 | Instant startTime = Instant.now(); 203 | // processQuickSort(arr, 0, arr.length - 1); // 和老师的原版代码对比,结果是一样的 204 | quickSort(arr, 0, arr.length - 1); 205 | if (arr.length < 10) { 206 | System.out.println("排序后:" + Arrays.toString(arr)); 207 | } 208 | Instant endTime = Instant.now(); 209 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/radix/RadixSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.radix; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 基数排序 11 | */ 12 | public class RadixSortTest { 13 | /** 14 | * 推导:推导每一步的状态,然后找规律 15 | */ 16 | @Test 17 | public void processDemo() { 18 | int arr[] = {53, 3, 542, 748, 14, 214}; 19 | System.out.println("原始数组:" + Arrays.toString(arr)); 20 | processRadixSort(arr); 21 | } 22 | 23 | public void processRadixSort(int[] arr) { 24 | // 第一轮 25 | // 1. 将每个元素的 个位数 取出,然后放到对应的桶中(桶为一个一维数组) 26 | // 2. 按照这个桶的顺序,依次取出数据,放回原来的数组 27 | 28 | // 定义 10 个桶,每个桶是一个一维数组 29 | // 由于无法知道每个桶需要多少个元素,所以声明为数组长度 30 | // 加入:10 个数字都是 1,那么只会分配到同一个通中 31 | int[][] buckets = new int[10][arr.length]; 32 | // 定义每个桶中有效的数据个数 33 | // 桶长度为数组大小,那么每一个桶中存放了几个有效的元素呢?就需要有这个变量来指示 34 | int[] bucketCounts = new int[buckets.length]; 35 | 36 | // 开始第一轮的代码实现 37 | // 1. 将每个元素的 个位数 取出,然后放到对应的桶中(桶为一个一维数组) 38 | for (int i = 0; i < arr.length; i++) { 39 | // 获取到个位数 40 | int temp = arr[i] % 10; 41 | // 根据规则,将该数放到对应的桶中 42 | buckets[temp][bucketCounts[temp]] = arr[i]; 43 | // 并将该桶的有效个数+1 44 | bucketCounts[temp]++; 45 | } 46 | // 2. 按照这个桶的顺序,依次取出数据,放回原来的数组 47 | int index = 0; // 标识当前放回原数组的哪一个了 48 | for (int i = 0; i < buckets.length; i++) { 49 | if (bucketCounts[i] == 0) { 50 | // 标识该桶无数据 51 | continue; 52 | } 53 | for (int j = 0; j < bucketCounts[i]; j++) { 54 | arr[index++] = buckets[i][j]; 55 | } 56 | // 取完数据后,要重置每个桶的有效数据个数 57 | bucketCounts[i] = 0; 58 | } 59 | System.out.println("第一轮排序后:" + Arrays.toString(arr)); 60 | 61 | // 第 2 轮:比较十位数 62 | for (int i = 0; i < arr.length; i++) { 63 | // 获取到十位数 64 | int temp = arr[i] / 10 % 10; 65 | buckets[temp][bucketCounts[temp]] = arr[i]; 66 | bucketCounts[temp]++; 67 | } 68 | index = 0; // 标识当前放回原数组的哪一个了 69 | for (int i = 0; i < buckets.length; i++) { 70 | if (bucketCounts[i] == 0) { 71 | continue; 72 | } 73 | for (int j = 0; j < bucketCounts[i]; j++) { 74 | arr[index++] = buckets[i][j]; 75 | } 76 | bucketCounts[i] = 0; 77 | } 78 | System.out.println("第二轮排序后:" + Arrays.toString(arr)); 79 | 80 | // 第 3 轮:比较百位数 81 | for (int i = 0; i < arr.length; i++) { 82 | // 获取到十位数 83 | int temp = arr[i] / 100 % 10; 84 | buckets[temp][bucketCounts[temp]] = arr[i]; 85 | bucketCounts[temp]++; 86 | } 87 | index = 0; // 标识当前放回原数组的哪一个了 88 | for (int i = 0; i < buckets.length; i++) { 89 | if (bucketCounts[i] == 0) { 90 | continue; 91 | } 92 | for (int j = 0; j < bucketCounts[i]; j++) { 93 | arr[index++] = buckets[i][j]; 94 | } 95 | bucketCounts[i] = 0; 96 | } 97 | System.out.println("第三轮排序后:" + Arrays.toString(arr)); 98 | } 99 | 100 | @Test 101 | public void radixSortTest() { 102 | int arr[] = {53, 3, 542, 748, 14, 214}; 103 | System.out.println("原始数组:" + Arrays.toString(arr)); 104 | radixSort(arr); 105 | System.out.println("排序后:" + Arrays.toString(arr)); 106 | } 107 | 108 | /** 109 | * 根据推导规律,整理出完整算法 110 | * 111 | * @param arr 112 | */ 113 | public void radixSort(int[] arr) { 114 | // 1. 得到数组中的最大值,并获取到该值的位数。用于循环几轮 115 | int max = arr[0]; 116 | for (int i = 0; i < arr.length; i++) { 117 | if (arr[i] > max) { 118 | max = arr[i]; 119 | } 120 | } 121 | // 得到位数 122 | int maxLength = (max + "").length(); 123 | 124 | // 定义桶 和 标识桶中元素个数 125 | int[][] bucket = new int[10][arr.length]; 126 | int[] bucketCounts = new int[bucket.length]; 127 | 128 | // 总共需要进行 maxLength 轮 129 | for (int k = 1, n = 1; k <= maxLength; k++, n *= 10) { 130 | // 进行桶排序 131 | for (int i = 0; i < arr.length; i++) { 132 | // 获取该轮的桶索引:每一轮按 10 的倍数递增,获取到对应数位数 133 | // 这里额外使用一个步长为 10 的变量 n 来得到每一次递增后的值 134 | int bucketIndex = arr[i] / n % 10; 135 | // 放入该桶中 136 | bucket[bucketIndex][bucketCounts[bucketIndex]] = arr[i]; 137 | // 标识该桶元素多了一个 138 | bucketCounts[bucketIndex]++; 139 | } 140 | // 将桶中元素获取出来,放到原数组中 141 | int index = 0; 142 | for (int i = 0; i < bucket.length; i++) { 143 | if (bucketCounts[i] == 0) { 144 | // 该桶无有效元素,跳过不获取 145 | continue; 146 | } 147 | // 获取桶中有效的个数 148 | for (int j = 0; j < bucketCounts[i]; j++) { 149 | arr[index++] = bucket[i][j]; 150 | } 151 | // 取完后,重置该桶的元素个数为 0 ,下一次才不会错乱数据 152 | bucketCounts[i] = 0; 153 | } 154 | // System.out.println("第" + k + "轮排序后:" + Arrays.toString(arr)); 155 | } 156 | } 157 | 158 | /** 159 | * 大量数据排序时间测试 160 | */ 161 | @Test 162 | public void bulkDataSort() { 163 | int max = 80_0000; 164 | // max = 8; 165 | int[] arr = new int[max]; 166 | for (int i = 0; i < max; i++) { 167 | arr[i] = (int) (Math.random() * 80_000); 168 | } 169 | if (arr.length < 10) { 170 | System.out.println("原始数组:" + Arrays.toString(arr)); 171 | } 172 | Instant startTime = Instant.now(); 173 | radixSort(arr); 174 | if (arr.length < 10) { 175 | System.out.println("排序后:" + Arrays.toString(arr)); 176 | } 177 | Instant endTime = Instant.now(); 178 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/select/SelectSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.select; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 选择排序 11 | */ 12 | public class SelectSortTest { 13 | /** 14 | * 推导每一步的演变过程,便于理解 15 | *
 16 |      *   这是一个很重要的思想:
 17 |      *      一个算法:先简单  -->  做复杂:
 18 |      *      就是可以把一个复杂的算法,拆分成简单的问题  -> 逐步解决
 19 |      * 
20 | */ 21 | @Test 22 | public void processDemo() { 23 | int arr[] = {101, 34, 119, 1}; 24 | System.out.println("原始数组:" + Arrays.toString(arr)); 25 | processSelectSort(arr); 26 | } 27 | 28 | public void processSelectSort(int[] arr) { 29 | // 第 1 轮: 30 | // 原始数组:101, 34, 119, 1 31 | // 排序后: 1, 34, 119, 101 32 | int min = arr[0]; // 先假定第一个数为最小值 33 | int minIndex = 0; 34 | for (int j = 0 + 1; j < arr.length; j++) { 35 | // 挨个与最小值对比,如果小于,则进行交换 36 | if (min > arr[j]) { 37 | // 如果后面的值比当前的 min 小,则重置为这个数 38 | min = arr[j]; 39 | minIndex = j; 40 | } 41 | } 42 | // 第 1 轮结束后,得到了最小值 43 | // 将这个最小值与 arr[0] 交换 44 | arr[minIndex] = arr[0]; 45 | arr[0] = min; 46 | System.out.println("第 1 轮排序后:" + Arrays.toString(arr)); 47 | 48 | // 第 2 轮 49 | // 当前数组:1, 34, 119, 101 50 | // 排序后: 1, 34, 119, 101 51 | min = arr[1]; 52 | minIndex = 1; 53 | // 第二轮,与第 3 个数开始比起 54 | for (int j = 0 + 2; j < arr.length; j++) { 55 | // 挨个与最小值对比,如果小于,则进行交换 56 | if (min > arr[j]) { 57 | // 如果后面的值比当前的 min 小,则重置为这个数 58 | min = arr[j]; 59 | minIndex = j; 60 | } 61 | } 62 | // 第 2 轮结束后,得到了最小值 63 | // 将这个最小值与 arr[1] 交换 64 | arr[minIndex] = arr[1]; 65 | arr[1] = min; 66 | System.out.println("第 2 轮排序后:" + Arrays.toString(arr)); 67 | 68 | // 第 3 轮 69 | // 当前数组:1, 34, 119, 101 70 | // 排序后: 1, 34, 101, 119 71 | min = arr[2]; 72 | minIndex = 2; 73 | // 第二轮,与第 4 个数开始比起 74 | for (int j = 0 + 3; j < arr.length; j++) { 75 | // 挨个与最小值对比,如果小于,则进行交换 76 | if (min > arr[j]) { 77 | // 如果后面的值比当前的 min 小,则重置为这个数 78 | min = arr[j]; 79 | minIndex = j; 80 | } 81 | } 82 | // 第 2 轮结束后,得到了最小值 83 | // 将这个最小值与 arr[2] 交换 84 | arr[minIndex] = arr[2]; 85 | arr[2] = min; 86 | System.out.println("第 3 轮排序后:" + Arrays.toString(arr)); 87 | } 88 | 89 | /** 90 | * 从上述的演变过程来看,循环体都是相同的,只是每次比较假定的最小值顺序在递增。 91 | * 因此可以改写成如下方式 92 | */ 93 | @Test 94 | public void processDemo2() { 95 | int arr[] = {101, 34, 119, 1}; 96 | System.out.println("原始数组:" + Arrays.toString(arr)); 97 | processSelectSort2(arr); 98 | } 99 | 100 | public void processSelectSort2(int[] arr) { 101 | // 把之前假定当前最小值的地方,使用变量 i 代替了 102 | // 由于需要 arr.length -1 轮,所以使用外层一个循环,就完美的解决了这个需求 103 | for (int i = 0; i < arr.length - 1; i++) { 104 | int min = arr[i]; // 先假定第一个数为最小值 105 | int minIndex = i; 106 | for (int j = i + 1; j < arr.length; j++) { 107 | // 挨个与最小值对比,如果小于,则进行交换 108 | if (min > arr[j]) { 109 | // 如果后面的值比当前的 min 小,则重置为这个数 110 | min = arr[j]; 111 | minIndex = j; 112 | } 113 | } 114 | // 第 i 轮结束后,得到了最小值 115 | // 将这个最小值与 arr[i] 交换 116 | if (minIndex == i) { 117 | // 如果最小值未发生改变,则不需要执行后面的交换了 118 | continue; 119 | } 120 | arr[minIndex] = arr[i]; 121 | arr[i] = min; 122 | System.out.println("第 " + (i + 1) + " 轮排序后:" + Arrays.toString(arr)); 123 | } 124 | } 125 | 126 | 127 | @Test 128 | public void selectSortTest() { 129 | int arr[] = {101, 34, 119, 1}; 130 | System.out.println("升序"); 131 | System.out.println("原始数组:" + Arrays.toString(arr)); 132 | selectSort(arr, true); 133 | System.out.println("排序后:" + Arrays.toString(arr)); 134 | System.out.println("降序"); 135 | System.out.println("原始数组:" + Arrays.toString(arr)); 136 | selectSort(arr, false); 137 | System.out.println("排序后:" + Arrays.toString(arr)); 138 | } 139 | 140 | /** 141 | * 选择排序算法封装 142 | * 143 | * @param arr 要排序的数组 144 | * @param asc 升序排列,否则降序 145 | */ 146 | public void selectSort(int[] arr, boolean asc) { 147 | 148 | // 把之前假定当前最小值的地方,使用变量 i 代替了 149 | // 由于需要 arr.length -1 轮,所以使用外层一个循环,就完美的解决了这个需求 150 | for (int i = 0; i < arr.length - 1; i++) { 151 | int current = arr[i]; // 先假定第一个数为最小值 152 | int currentIndex = i; 153 | for (int j = i + 1; j < arr.length; j++) { 154 | // 挨个与最小值对比,如果小于,则进行交换 155 | if (asc) { 156 | if (current > arr[j]) { 157 | // 如果后面的值比当前的 min 小,则重置为这个数 158 | current = arr[j]; 159 | currentIndex = j; 160 | } 161 | } else { 162 | if (current < arr[j]) { 163 | // 如果后面的值比当前的 min 大,则重置为这个数 164 | current = arr[j]; 165 | currentIndex = j; 166 | } 167 | } 168 | } 169 | // 第 i 轮结束后,得到了最小/大值 170 | // 将这个值与 arr[i] 交换 171 | if (currentIndex == i) { 172 | // 如果最小值未发生改变,则不需要执行后面的交换了 173 | continue; 174 | } 175 | arr[currentIndex] = arr[i]; 176 | arr[i] = current; 177 | } 178 | } 179 | 180 | 181 | /** 182 | * 大量数据排序时间测试 183 | */ 184 | @Test 185 | public void bulkDataSort() { 186 | int max = 80_000; 187 | int[] arr = new int[max]; 188 | for (int i = 0; i < max; i++) { 189 | arr[i] = (int) (Math.random() * 80_000); 190 | } 191 | 192 | Instant startTime = Instant.now(); 193 | selectSort(arr, true); 194 | // System.out.println(Arrays.toString(arr)); 195 | Instant endTime = Instant.now(); 196 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sort/shell/ShellSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.shell; 2 | 3 | import cn.mrcode.study.dsalgtutorialdemo.datastructure.sort.insertion.InsertionSortTest; 4 | import org.junit.Test; 5 | 6 | import java.time.Duration; 7 | import java.time.Instant; 8 | import java.util.Arrays; 9 | 10 | /** 11 | * 希尔排序 12 | */ 13 | public class ShellSortTest { 14 | /** 15 | * 推到的方式来演示每一步怎么做,然后找规律 16 | */ 17 | @Test 18 | public void processDemo() { 19 | int arr[] = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0}; 20 | System.out.println("原始数组:" + Arrays.toString(arr)); 21 | processShellSort(arr); 22 | } 23 | 24 | public void processShellSort(int[] arr) { 25 | // 按照笔记中的基本思想,一共三轮 26 | // 第 1 轮:初始数组 [8, 9, 1, 7, 2, 3, 5, 4, 6, 0] 27 | // 将 10 个数字分成了 5 组( length / 2),增量也是 5,需要对 5 组进行排序 28 | // 外层循环,并不是循环 5 次,只是这里巧合了。 29 | // 一定要记得,希尔排序:先分组,在对组进行插入排序 30 | for (int i = 5; i < arr.length; i++) { 31 | // 第 1 组:[8,3] , 分别对应原始数组的下标 0,5 32 | // 第 2 组:[9,5] , 分别对应原始数组的下标 1,6 33 | // ... 34 | // 内层循环对 每一组 进行直接排序操作 35 | // i = 5 :j = 0, j-=5 = 0 - 5 = -5,跳出循环,这是对第 1 组进行插入排序 36 | // i = 6 :j = 1, j-=5 = 0 - 1 = -1,跳出循环,这是对第 2 组进行插入排序 37 | // i = 9 :j = 4, j-=5 = 0 - 4 = -4,跳出循环,这是对第 3 组进行插入排序 38 | for (int j = i - 5; j >= 0; j -= 5) { 39 | if (arr[j] > arr[j + 5]) { 40 | int temp = arr[j]; 41 | arr[j] = arr[j + 5]; 42 | arr[j + 5] = temp; 43 | } 44 | } 45 | } 46 | System.out.println("第 1 轮排序后:" + Arrays.toString(arr)); 47 | 48 | // 第 2 轮:上一轮排序后的数组:[3, 5, 1, 6, 0, 8, 9, 4, 7, 2] 49 | // 将 10 个数字分成了 2 组(上一次的增量 5 / 2),增量也为 2,需要对 2 组进行排序 50 | for (int i = 2; i < arr.length; i++) { 51 | // 第 1 组:[3,1,0,9,7] , 分别对应原始数组的下标 0,2,4,6,8 52 | // 第 2 组:[5,6,8,4,2] , 分别对应原始数组的下标 1,3,5,7,9 53 | // ... 54 | // 内层循环对 每一组 进行直接排序操作 55 | // i = 2 :j = 0, j-=2 = 0 - 2 = -2,跳出循环, 56 | // 这是对第 1 组中的 3,1 进行比较,1 为无序列表中的比较元素,3 为有序列表中的最后一个元素,3 > 1,进行交换 57 | // 交换后的数组:[1, 5, 3, 6, 0, 8, 9, 4, 7, 2] 58 | // 第 1 组:[1,3,0,9,7] 59 | // i = 3 :j = 1, j-=2 = 1 - 2 = -1,跳出循环 60 | // 这是对第 2 组中的 5,6 进行比较,6 为无序列表中的比较元素,5 为有序列表中的最后一个元素,5 < 6,不进行交换 61 | // 交换后的数组:[1, 5, 3, 6, 0, 8, 9, 4, 7, 2] , 没有交换 62 | // 第 2 组:[5,6,8,4,2] 63 | // i = 4 :j = 2, j-=2 = 2 - 2 = 0, 64 | // 这是对第 1 组中的 3,0 进行比较,0 为无序列表中的比较元素,3 为有序列表中的最后一个元素,3 > 0,进行交换 65 | // 交换后的数组:[1, 5, 0, 6, 3, 8, 9, 4, 7, 2], 66 | // 第 1 组:[1,0,3,9,7] 67 | // 由于 2 - 2 = 0,此时 j = 0,满足条件,继续循环 i = 4 :j = 0, j-=2 = 0 - 2 = -2, 68 | // 这是对第 1 组中的有序列表中的剩余数据进行交换,1,0, 1>0 ,他们进行交换 69 | // 第 2 组:[0,1,3,9,7] 70 | for (int j = i - 2; j >= 0; j -= 2) { 71 | if (arr[j] > arr[j + 2]) { 72 | int temp = arr[j]; 73 | arr[j] = arr[j + 2]; 74 | arr[j + 2] = temp; 75 | } 76 | } 77 | } 78 | System.out.println("第 2 轮排序后:" + Arrays.toString(arr)); 79 | 80 | // 第 3 轮:上一轮排序后的数组:[0, 2, 1, 4, 3, 5, 7, 6, 9, 8] 81 | // 将 10 个数字分成了 1 组(上一次的增量 2 / 2),增量也为 1,需要对 1 组进行排序 82 | for (int i = 1; i < arr.length; i++) { 83 | // 第 1 组:[0, 2, 1, 4, 3, 5, 7, 6, 9, 8] 84 | // i = 1 :j = 0, j-=1 = 0 - 1 = -1,跳出循环 85 | // 0 为有序列表中的最后一个元素,2 为无须列表中要比较的元素。 0 < 2,不交换 86 | // [0, 2 有序 <-> 无序, 1, 4, 3, 5, 7, 6, 9, 8] 87 | // i = 2 :j = 1, j-=1 = 1 - 1 = o 88 | // 2 为有序列表中的最后一个元素,1 为无序列表中要比较的元素, 2 > 1,交换 89 | // 交换后:[0, 1, 2, 4, 3, 5, 7, 6, 9, 8] 90 | // 由于不退出循环,还要比较有序列表中的数据,0 与 1 91 | for (int j = i - 1; j >= 0; j -= 1) { 92 | if (arr[j] > arr[j + 1]) { 93 | int temp = arr[j]; 94 | arr[j] = arr[j + 1]; 95 | arr[j + 1] = temp; 96 | } 97 | } 98 | } 99 | System.out.println("第 3 轮排序后:" + Arrays.toString(arr)); 100 | } 101 | 102 | @Test 103 | public void shellSortTest() { 104 | int arr[] = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0}; 105 | System.out.println("原始数组:" + Arrays.toString(arr)); 106 | shellSort(arr); 107 | } 108 | 109 | /** 110 | * 根据前面的分析,得到规律,变化的只是增量步长,那么可以改写为如下方式 111 | */ 112 | public void shellSort(int[] arr) { 113 | int temp = 0; 114 | // 第 1 层循环:得到每一次的增量步长 115 | for (int gap = arr.length / 2; gap > 0; gap /= 2) { 116 | // 第 2 层和第 3 层循环,是对每一个增量中的每一组进行插入排序 117 | for (int i = gap; i < arr.length; i++) { 118 | for (int j = i - gap; j >= 0; j -= gap) { 119 | if (arr[j] > arr[j + gap]) { 120 | temp = arr[j]; 121 | arr[j] = arr[j + gap]; 122 | arr[j + gap] = temp; 123 | } 124 | } 125 | } 126 | // System.out.println("增量为 " + gap + " 的这一轮排序后:" + Arrays.toString(arr)); 127 | } 128 | } 129 | 130 | /** 131 | * 大量数据排序时间测试 132 | */ 133 | @Test 134 | public void bulkDataSort() { 135 | int max = 80_000; 136 | // int max = 8; 137 | int[] arr = new int[max]; 138 | for (int i = 0; i < max; i++) { 139 | arr[i] = (int) (Math.random() * 80_000); 140 | } 141 | 142 | Instant startTime = Instant.now(); 143 | shellSort(arr); 144 | // System.out.println(Arrays.toString(arr)); 145 | Instant endTime = Instant.now(); 146 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 147 | } 148 | 149 | 150 | /** 151 | * 移动法希尔排序 152 | */ 153 | @Test 154 | public void moveShellSortTest() { 155 | int arr[] = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0}; 156 | System.out.println("原始数组:" + Arrays.toString(arr)); 157 | moveShellSort(arr); 158 | } 159 | 160 | /** 161 | * 插入排序采用移动法 162 | */ 163 | public void moveShellSort(int[] arr) { 164 | // 第 1 层循环:得到每一次的增量步长 165 | for (int gap = arr.length / 2; gap > 0; gap /= 2) { 166 | /** 167 | 这里的内层循环,除了是获得每一组的值(按增量取), 168 | 移动法使用的是简单插入排序的算法 {@link InsertionSortTest#processSelectSort2(int[])} 169 | 唯一不同的是,这里的组前一个是按增量来计算的 170 | */ 171 | // 每一轮,都是针对某一个组的插入排序中:待排序的起点 172 | for (int i = gap; i < arr.length; i++) { 173 | int currentInsertValue = arr[i]; // 无序列表中的第一个元素 174 | int insertIndex = i - gap; // 有序列表中的最后一个元素 175 | while (insertIndex >= 0 176 | && currentInsertValue < arr[insertIndex]) { 177 | // 比较的数比前一个数小,则前一个往后移动 178 | arr[insertIndex + gap] = arr[insertIndex]; 179 | insertIndex -= gap; 180 | } 181 | // 对找到的位置插入值 182 | arr[insertIndex + gap] = currentInsertValue; 183 | } 184 | // System.out.println("增量为 " + gap + " 的这一轮排序后:" + Arrays.toString(arr)); 185 | } 186 | } 187 | 188 | /** 189 | * 移动法,大数据量测试速度 190 | */ 191 | @Test 192 | public void moveBulkDataSort() { 193 | int max = 80_000; 194 | // int max = 8; 195 | int[] arr = new int[max]; 196 | for (int i = 0; i < max; i++) { 197 | arr[i] = (int) (Math.random() * 80_000); 198 | } 199 | 200 | Instant startTime = Instant.now(); 201 | moveShellSort(arr); 202 | // System.out.println(Arrays.toString(arr)); 203 | Instant endTime = Instant.now(); 204 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/sparsearray/SparseArray.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.sparsearray; 2 | 3 | /** 4 | *
  5 |  *  稀疏数组:
  6 |  *      1. 二维数组转稀疏数组
  7 |  *      2. 稀疏数组转二维数组
  8 |  * 
9 | */ 10 | public class SparseArray { 11 | public static void main(String[] args) { 12 | // 创建原始二维数组 13 | // 0:没有棋子,1:黑棋,2:白棋 14 | // 棋盘大小 11 x 11 15 | int chessArr[][] = new int[11][11]; 16 | chessArr[1][2] = 1; 17 | chessArr[2][3] = 2; 18 | // 预览棋盘上的棋子位置 19 | System.out.println("预览原始数组"); 20 | printChessArray(chessArr); 21 | // 二维数组转稀疏数组 22 | int[][] sparseArr = chessToSparse(chessArr); 23 | // int[][] sparseArr = chessToSparse2(chessArr); 24 | System.out.println("二维数组转稀疏数组"); 25 | printChessArray(sparseArr); 26 | // 稀疏数组转二维数组 27 | int[][] chessArr2 = sparseToChess(sparseArr); 28 | System.out.println("稀疏数组转二维数组"); 29 | printChessArray(chessArr2); 30 | } 31 | 32 | /** 33 | * 二维数组转稀疏数组 34 | * 35 | * @param chessArr 36 | */ 37 | private static int[][] chessToSparse(int[][] chessArr) { 38 | // 1. 遍历数组得到有效棋子个数 39 | int sum = 0; 40 | for (int[] row : chessArr) { 41 | for (int chess : row) { 42 | if (chess != 0) { 43 | sum++; 44 | } 45 | } 46 | } 47 | // 2. 创建稀疏数组 48 | int[][] sparseArr = new int[sum + 1][3]; 49 | // 3. 将二维数据的有效数据存入到稀疏数组中(从第 2 行开始存储) 50 | int chessRow = chessArr.length; // 行: 棋盘大小 51 | int chessCol = 0; // 列: 棋盘大小 52 | int count = 0; // 记录当前是第几个非 0 的数据 53 | for (int i = 0; i < chessArr.length; i++) { 54 | int[] rows = chessArr[i]; 55 | if (chessCol == 0) { 56 | chessCol = rows.length; 57 | } 58 | for (int j = 0; j < rows.length; j++) { 59 | int chess = rows[j]; 60 | if (chess == 0) { 61 | continue; 62 | } 63 | count++; // 第一行是棋盘信息,所以先自增 64 | sparseArr[count][0] = i; 65 | sparseArr[count][1] = j; 66 | sparseArr[count][2] = chess; 67 | } 68 | } 69 | // 4. 补全第一行的棋盘大小和有效数据 70 | sparseArr[0][0] = chessRow; 71 | sparseArr[0][1] = chessCol; 72 | sparseArr[0][2] = sum; 73 | return sparseArr; 74 | } 75 | 76 | /** 77 | * 稀疏数组转二维数组 78 | * 79 | * @param sparseArr 80 | * @return 81 | */ 82 | private static int[][] sparseToChess(int[][] sparseArr) { 83 | // 1. 创建二维数组 84 | int[][] chessArr = new int[sparseArr[0][0]][sparseArr[0][1]]; 85 | // 2. 恢复有效数据到二维数组 86 | for (int i = 1; i < sparseArr.length; i++) { 87 | int[] rows = sparseArr[i]; 88 | chessArr[rows[0]][rows[1]] = rows[2]; 89 | } 90 | return chessArr; 91 | } 92 | 93 | 94 | /** 95 | * 打印棋盘上的棋子布局 96 | * 97 | * @param chessArr 98 | */ 99 | public static void printChessArray(int[][] chessArr) { 100 | for (int[] row : chessArr) { 101 | for (int data : row) { 102 | // 左对齐,使用两个空格补齐 2 位数 103 | System.out.printf("%-2d\t", data); 104 | } 105 | System.out.println(""); 106 | } 107 | } 108 | 109 | /** 110 | * 二维数组转稀疏数组; 代码紧凑版本 111 | * 112 | * @param chessArr 113 | */ 114 | private static int[][] chessToSparse2(int[][] chessArr) { 115 | // 1. 遍历数组得到有效棋子个数 116 | int sum = 0; 117 | for (int[] row : chessArr) { 118 | for (int chess : row) { 119 | if (chess != 0) { 120 | sum++; 121 | } 122 | } 123 | } 124 | // 2. 创建稀疏数组 125 | int[][] sparseArr = new int[sum + 1][3]; 126 | // 3. 将二维数据的有效数据存入到稀疏数组中(从第 2 行开始存储) 127 | int chessRow = chessArr.length; // 行: 棋盘大小 128 | int chessCol = 0; // 列: 棋盘大小 129 | int count = 0; // 记录当前是第几个非 0 的数据 130 | for (int i = 0; i < chessArr.length; i++) { 131 | if (chessCol == 0) { 132 | chessCol = chessArr[i].length; 133 | } 134 | for (int j = 0; j < chessCol; j++) { 135 | if (chessArr[i][j] != 0) { 136 | count++; // 第一行是棋盘信息,所以先自增 137 | sparseArr[count][0] = i; 138 | sparseArr[count][1] = j; 139 | sparseArr[count][2] = chessArr[i][j]; 140 | } 141 | } 142 | } 143 | // 4. 补全第一行的棋盘大小和有效数据 144 | sparseArr[0][0] = chessRow; 145 | sparseArr[0][1] = chessCol; 146 | sparseArr[0][2] = sum; 147 | return sparseArr; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/array/ArrayStack.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.array; 2 | 3 | /** 4 | * 数组模拟栈 5 | */ 6 | public class ArrayStack { 7 | int[] stack; // 数据存储 8 | int maxSize; // 栈最大数量 9 | int top = -1; // 栈顶位置 10 | 11 | public ArrayStack(int maxSize) { 12 | this.maxSize = maxSize; 13 | stack = new int[maxSize]; 14 | } 15 | 16 | /** 17 | * 是否已满 18 | * 19 | * @return 20 | */ 21 | public boolean isFull() { 22 | return maxSize - 1 == top; 23 | } 24 | 25 | /** 26 | * 是否为空 27 | * 28 | * @return 29 | */ 30 | public boolean isEmpty() { 31 | return top == -1; 32 | } 33 | 34 | /** 35 | * 入栈 36 | * 37 | * @param value 38 | */ 39 | public void push(int value) { 40 | if (isFull()) { 41 | System.out.println("栈已满"); 42 | return; 43 | } 44 | stack[++top] = value; 45 | } 46 | 47 | /** 48 | * 出栈 49 | * 50 | * @return 51 | */ 52 | public int pop() { 53 | if (isEmpty()) { 54 | throw new RuntimeException("栈中无数据"); 55 | } 56 | return stack[top--]; 57 | } 58 | 59 | /** 60 | * 显示栈中数据,从栈顶开始显示,也就是按出栈的顺序显示 61 | */ 62 | public void print() { 63 | if (isEmpty()) { 64 | System.out.println("栈中无数据"); 65 | return; 66 | } 67 | for (int i = top; i >= 0; i--) { 68 | System.out.printf("index=%d, value=%d \n", i, stack[i]); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/array/ArrayStackTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.array; 2 | 3 | import org.junit.Test; 4 | 5 | public class ArrayStackTest { 6 | @Test 7 | public void pushTest() { 8 | ArrayStack stack = new ArrayStack(4); 9 | stack.push(1); 10 | stack.push(2); 11 | stack.push(3); 12 | stack.push(4); 13 | stack.print(); 14 | stack.push(5); 15 | } 16 | 17 | @Test 18 | public void popTest() { 19 | ArrayStack stack = new ArrayStack(4); 20 | stack.push(1); 21 | stack.push(2); 22 | stack.print(); 23 | System.out.println("pop 数据:" + stack.pop()); 24 | stack.print(); 25 | System.out.println("pop 数据:" + stack.pop()); 26 | stack.print(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/calculator/ArrayStack.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.calculator; 2 | 3 | /** 4 | * 数组模拟栈 5 | */ 6 | public class ArrayStack { 7 | int[] stack; // 数据存储 8 | int maxSize; // 栈最大数量 9 | int top = -1; // 栈顶位置 10 | 11 | public ArrayStack(int maxSize) { 12 | this.maxSize = maxSize; 13 | stack = new int[maxSize]; 14 | } 15 | 16 | /** 17 | * 是否已满 18 | * 19 | * @return 20 | */ 21 | public boolean isFull() { 22 | return maxSize - 1 == top; 23 | } 24 | 25 | /** 26 | * 是否为空 27 | * 28 | * @return 29 | */ 30 | public boolean isEmpty() { 31 | return top == -1; 32 | } 33 | 34 | /** 35 | * 入栈 36 | * 37 | * @param value 38 | */ 39 | public void push(int value) { 40 | if (isFull()) { 41 | System.out.println("栈已满"); 42 | return; 43 | } 44 | stack[++top] = value; 45 | } 46 | 47 | /** 48 | * 出栈 49 | * 50 | * @return 51 | */ 52 | public int pop() { 53 | if (isEmpty()) { 54 | throw new RuntimeException("栈中无数据"); 55 | } 56 | return stack[top--]; 57 | } 58 | 59 | /** 60 | * 显示栈中数据,从栈顶开始显示,也就是按出栈的顺序显示 61 | */ 62 | public void print() { 63 | if (isEmpty()) { 64 | System.out.println("栈中无数据"); 65 | return; 66 | } 67 | for (int i = top; i >= 0; i--) { 68 | System.out.printf("index=%d, value=%d \n", i, stack[i]); 69 | } 70 | } 71 | 72 | /** 73 | * 偷看栈顶元素,不测试出队列 74 | * 75 | * @return 76 | */ 77 | public int peek() { 78 | if (isEmpty()) { 79 | throw new RuntimeException("栈为空"); 80 | } 81 | return stack[top]; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/calculator/Calculator.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.calculator; 2 | 3 | /** 4 | * 计算器代码实现 5 | */ 6 | public class Calculator { 7 | // 使用前面章节实现过的 数组模拟栈,来当我们 计算器中用来存储数据和符号的 容器 8 | private ArrayStack numStack = new ArrayStack(10); // 数组栈 9 | private ArrayStack operStack = new ArrayStack(10); // 符号栈 10 | 11 | public static void main(String[] args) { 12 | String expression = "30+2*6-2"; 13 | 14 | Calculator calculator = new Calculator(); 15 | // 扫描表达式 16 | calculator.scan(expression); 17 | // 剩余数据进行计算 18 | int res = calculator.nextCal(); 19 | System.out.printf("%s = %d \n", expression, res); 20 | } 21 | 22 | 23 | /** 24 | * 第一步:扫描表达式 25 | */ 26 | public void scan(String expression) { 27 | int index = 0; 28 | String keepNum = ""; // 用来保存数字,有可能是 = "1" 或则 "123" 的多位数 29 | while (true) { 30 | if (index == expression.length()) { 31 | break; 32 | } 33 | // 每次只截取一个数字 34 | // 要注意这里的 ch,使用 ch 做运算的时候要小心 35 | char ch = expression.substring(index, ++index).charAt(0); 36 | if (isOper(ch)) { 37 | // 符号栈为空,则直接入符号 38 | if (operStack.isEmpty()) { 39 | operStack.push(ch); 40 | continue; 41 | } 42 | // 当 当前操作符 的优先级 大于 栈顶符号:将 当前操作符入符号栈 43 | // 一定要大于,如果是同级的话,有可能前面一个也是 * 号,需要先在第一步进行计算 44 | if (priority(ch) > priority((char) operStack.peek())) { 45 | operStack.push(ch); 46 | continue; 47 | } 48 | // 小于栈顶操作符,则将栈顶符号取出,进行计算 49 | int num1 = numStack.pop(); 50 | int num2 = numStack.pop(); 51 | int oper = operStack.pop(); 52 | int res = cal(num1, num2, oper); 53 | // 将结果入数栈 54 | numStack.push(res); 55 | // 将当期操作符入符号栈 56 | operStack.push(ch); 57 | } else { 58 | // 是数字,直接入数栈 59 | // ch 被当成 int 的使用的话,需要特别注意 60 | // ASCII 码表中数字是从 48 开始的,这里的 ch 对应的数字是 ASCII 码表,所以要减掉 48 61 | // 当然也可以使用字符串解析的方式 Integer.valueOf(字符串) 来得到数字 62 | // numStack.push(ch - 48); 63 | keepNum += ch; 64 | // 已经是末尾了,则直接入栈 65 | if (index == expression.length()) { 66 | numStack.push(Integer.parseInt(keepNum)); 67 | continue; 68 | } 69 | // 需要往后多看一位,如果是符号,才能将当前的数入栈 70 | char tempCh = expression.substring(index, index + 1).charAt(0); 71 | if (isOper(tempCh)) { 72 | numStack.push(Integer.parseInt(keepNum)); 73 | keepNum = ""; 74 | } 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * 第 2 步:从栈中取出来数据和符号,然后计算 81 | * 82 | * @return 83 | */ 84 | private int nextCal() { 85 | System.out.println("符号栈中符号情况:"); 86 | operStack.print(); 87 | while (true) { 88 | // 当符号栈为空时,表示已经计算完了 89 | if (operStack.isEmpty()) { 90 | break; 91 | } 92 | int num1 = numStack.pop(); 93 | int num2 = numStack.pop(); 94 | int oper = operStack.pop(); 95 | int res = cal(num1, num2, oper); 96 | // 将结果入数栈 97 | numStack.push(res); 98 | } 99 | // 计算完成之后,数栈中只有一个数据了,就是结果 100 | System.out.println("栈中数据是否只有一个结果数字:"); 101 | numStack.print(); 102 | return numStack.pop(); 103 | } 104 | 105 | /** 106 | * 计算 107 | * 108 | * @param num1 依次从栈顶弹出来的数据 109 | * @param num2 110 | * @param oper 操作符 111 | * @return 112 | */ 113 | private int cal(int num1, int num2, int oper) { 114 | switch (oper) { 115 | case '+': 116 | return num1 + num2; 117 | case '-': 118 | // 注意顺序,在栈底的数据,是先进去的,如果是减法,则是前面的数字减后面的数字 119 | return num2 - num1; 120 | case '*': 121 | return num1 * num2; 122 | case '/': 123 | return num2 / num1; 124 | } 125 | // 由于前面校验过操作符,不会走到这里来的 126 | return 0; 127 | } 128 | 129 | /** 130 | * 计算操作符号优先级,暂时只支持 + - * / 131 | * 132 | * @param ch 133 | * @return 优先级越高,数值越大 134 | */ 135 | private int priority(char ch) { 136 | switch (ch) { 137 | case '+': 138 | case '-': 139 | return 0; 140 | case '*': 141 | case '/': 142 | return 1; 143 | default: 144 | return -1; 145 | } 146 | } 147 | 148 | /** 149 | * 是否是操作符 150 | * 151 | * @param ch 152 | * @return 153 | */ 154 | private boolean isOper(char ch) { 155 | switch (ch) { 156 | case '+': 157 | case '-': 158 | case '*': 159 | case '/': 160 | return true; 161 | } 162 | return false; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/calculator/InfixToSuffix.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.calculator; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Stack; 6 | 7 | /** 8 | * 中缀表达式转后缀表达式 9 | */ 10 | public class InfixToSuffix { 11 | public static void main(String[] args) { 12 | InfixToSuffix infixToSuffix = new InfixToSuffix(); 13 | // 目标:1+((2+3)*4)-5 转为 1 2 3 + 4 * + 5 - 14 | // 1. 将中缀表达式转成 List,方便在后续操作中获取数据 15 | String infixExpression = "1+((2+3)*4)-5"; 16 | List infixList = infixToSuffix.infix2List(infixExpression); 17 | System.out.println(infixList); // [1, +, (, (, 2, +, 3, ), *, 4, ), -, 5] 18 | // 2. 将中缀表达式转成后缀表达式 19 | ArrayList suffixList = infixToSuffix.infixList2SuffixList(infixList); 20 | System.out.println(suffixList); // [1, 2, 3, +, 4, *, +, 5, -] 21 | } 22 | 23 | 24 | /** 25 | * 将中缀表达式解析成单个元素的 List, 26 | * 27 | * @param infixExpression 28 | * @return 1+((2+3)*4)-5 -> [1,+,(,(,2,+,3,),*,4,),5] 29 | */ 30 | public List infix2List(String infixExpression) { 31 | ArrayList res = new ArrayList<>(); 32 | // 扫描并解析 33 | int index = 0; 34 | char ch = 0; 35 | String tempNum = ""; // 支持多位数 36 | while (index < infixExpression.length()) { 37 | ch = infixExpression.charAt(index++); 38 | // 如果不是数字,就是符号,直接添加到容器中 39 | // 0 = 48, 9 = 57 40 | if (!(ch >= 48 && ch <= 57)) { 41 | // 如果拼接的多位数还有值,则添加到容器中 42 | if (!tempNum.isEmpty()) { 43 | res.add(tempNum); 44 | tempNum = ""; 45 | } 46 | res.add(ch + ""); 47 | continue; 48 | } 49 | // 如果是数字,则考虑处理多位数 50 | tempNum += ch; 51 | // 如果已经是最后一个字符了,则将这个多位数添加到容器中 52 | if (index == infixExpression.length()) { 53 | res.add(tempNum); 54 | tempNum = ""; 55 | } 56 | } 57 | return res; 58 | } 59 | 60 | /** 61 | * 中缀表达式 List 转为后缀表达式 List 62 | * 63 | * @param infixList 64 | * @return 65 | */ 66 | public ArrayList infixList2SuffixList(List infixList) { 67 | // 符号栈 68 | Stack s1 = new Stack<>(); 69 | // 思路是使用栈来存储表达式元素 70 | // 仔细观察他的解析步骤,会发现:只是在入栈,并未出现出栈操作 71 | // 而且,最后的结果还要逆序,所以这里使用 list,直接顺序读取出来就是最后的结果了 72 | ArrayList s2 = new ArrayList<>(); 73 | 74 | for (String item : infixList) { 75 | // 如果是数字,则加入 s2 76 | if (item.matches("\\d+")) { 77 | s2.add(item); 78 | } 79 | // 如果是左括号,直接压入 s1 80 | else if (item.equals("(")) { 81 | s1.push(item); 82 | } 83 | // 如果是右括号 84 | // 则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到 左括号 为止,此时将这一对括号 丢弃 85 | else if (item.equals(")")) { 86 | // 如果不是左括号,则取出 s1 中的符号,添加到 s2 中 87 | while (!s1.peek().equals("(")) { 88 | s2.add(s1.pop()); 89 | } 90 | // 上面循环完之后,那么就是遇到了左括号 91 | // 则直接弹出这个左括号丢弃 92 | s1.pop(); 93 | } 94 | // 剩下的则是运算符 95 | else { 96 | // 如果 s1 为空,或则栈顶运算符为 (,则压入符号栈 s1 97 | // 如果优先级比栈顶运算符 高,则压入符号栈 s1,否则,否则将 s1 栈顶的运算符弹出,压入 s2 中 98 | // 上面两句话,转换成下面的描述 99 | // 上面如果 s1 栈顶符号优先级比 当前符号高,则弹出加入到 s2 中。 100 | // 因为:如果栈顶符号是 ( 返回优先级为 -1.比当前符号低,则不会走该方法 101 | while (!s1.isEmpty() && priority(s1.peek().charAt(0)) >= priority(item.charAt(0))) { 102 | s2.add(s1.pop()); 103 | } 104 | s1.push(item); 105 | } 106 | } 107 | // 将 s1 中的运算符依次弹出并加入 s2 中 108 | while (!s1.isEmpty()) { 109 | s2.add(s1.pop()); 110 | } 111 | return s2; 112 | } 113 | 114 | /** 115 | * 计算操作符号优先级,暂时只支持 + - * / 116 | * 117 | * @param ch 118 | * @return 优先级越高,数值越大 119 | */ 120 | private int priority(char ch) { 121 | switch (ch) { 122 | case '+': 123 | case '-': 124 | return 0; 125 | case '*': 126 | case '/': 127 | return 1; 128 | default: 129 | return -1; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/calculator/ReversePolishCalculator.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.calculator; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Stack; 6 | 7 | /** 8 | * 逆波兰计算器(后缀表达式)代码实现 9 | */ 10 | public class ReversePolishCalculator { 11 | 12 | /** 13 | * 计算一个后缀表达式的值 14 | * 15 | * @param postfixExpression 16 | * @return 17 | */ 18 | public int cal(String postfixExpression) { 19 | return start(convert(postfixExpression)); 20 | } 21 | 22 | /** 23 | * 将后缀表达式转换成 list 24 | * 25 | * @param postfixExpression 表达式中的每个元素都用空格隔开,是为了方便;这里重点不在于怎么去解析出每一个元素了 26 | * @return 27 | */ 28 | private List convert(String postfixExpression) { 29 | return Arrays.asList(postfixExpression.split(" ")); 30 | } 31 | 32 | /** 33 | * 计算 34 | * 35 | * @param postfixElements 36 | * @return 37 | */ 38 | public int start(List postfixElements) { 39 | /* 40 | 比如:`(3+4)x5-6` 对应的后缀表达式 `3 4 + 5 x 6 -` 41 | 1. 从左到右扫描,将 3、4 压入堆栈 42 | 2. 扫描到 `+` 运算符时 43 | 将弹出 4 和 3,计算 `3 + 4 = 7`,将 7 压入栈 44 | 3. 将 5 入栈 45 | 4. 扫描到 `x` 运算符时 46 | 将弹出 5 和 7 ,计算 `7 x 5 = 35`,将 35 入栈 47 | 5. 将 6 入栈 48 | 6. 扫描到 `-` 运算符时 49 | 将弹出 6 和 35,计算 `35 - 6 = 29`,将 29 压入栈 50 | 7. 扫描表达式结束,29 是表达式的值 51 | */ 52 | Stack stack = new Stack<>(); 53 | for (String el : postfixElements) { 54 | // 如果是数字则入栈 55 | if (el.matches("\\d+")) { 56 | stack.push(Integer.parseInt(el)); 57 | continue; 58 | } 59 | // 是运算符,则弹出两个数 60 | Integer num2 = stack.pop(); 61 | Integer num1 = stack.pop(); 62 | int res = cal(num1, num2, el.charAt(0)); 63 | stack.push(res); 64 | } 65 | return stack.pop(); 66 | } 67 | 68 | /** 69 | * 计算 70 | * 71 | * @param num1 72 | * @param num2 73 | * @param oper 操作符 74 | * @return 75 | */ 76 | private int cal(int num1, int num2, char oper) { 77 | switch (oper) { 78 | case '+': 79 | return num1 + num2; 80 | case '-': 81 | return num1 - num2; 82 | case '*': 83 | return num1 * num2; 84 | case '/': 85 | return num1 / num2; 86 | } 87 | throw new IllegalArgumentException("不支持的运算符:" + oper); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/calculator/ReversePolishCalculatorTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.calculator; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Stack; 9 | 10 | /** 11 | * 逆波兰计算器实现(后缀表达式) 12 | */ 13 | public class ReversePolishCalculatorTest { 14 | /** 15 | * 直接计算后缀表达式测试 16 | */ 17 | public void test1() { 18 | ReversePolishCalculator calculator = new ReversePolishCalculator(); 19 | 20 | // (3+4)*5-6 => 对应的后缀表达式 `3 4 + 5 * 6 -` 21 | String postfixExpression = "3 4 + 5 * 6 -"; 22 | System.out.println("(3+4)*5-6 = " + calculator.cal(postfixExpression)); 23 | 24 | // (30+4)*5-6 => 对应的后缀表达式 `30 4 + 5 * 6 -` 25 | postfixExpression = "30 4 + 5 * 6 -"; 26 | System.out.println("(30+4)*5-6 = " + calculator.cal(postfixExpression)); 27 | 28 | // 4*5-8+60+8/2 => 对应的后缀表达式 `4 5 * 8 - 60 + 8 2 / +` 29 | postfixExpression = "4 5 * 8 - 60 + 8 2 / +"; 30 | System.out.println("4*5-8+60+8/2 = " + calculator.cal(postfixExpression)); 31 | 32 | // (3+4)-(3-4)*10,对应后缀表达式为:3 4 + 10 3 4 - * - 33 | postfixExpression = "3 4 + 10 3 4 - * -"; 34 | System.out.println("(3+4)-(3-4)*10 = " + calculator.cal(postfixExpression)); 35 | } 36 | 37 | /** 38 | * 中缀表达式转后缀表达式,再给定计算器计算测试 39 | */ 40 | @Test 41 | public void test2() { 42 | InfixToSuffix infixToSuffix = new InfixToSuffix(); 43 | // 目标:1+((2+3)*4)-5 转为 1 2 3 + 4 * + 5 - 44 | // 1. 将中缀表达式转成 List,方便在后续操作中获取数据 45 | String infixExpression = "1+((2+3)*4)-5"; 46 | List infixList = infixToSuffix.infix2List(infixExpression); 47 | // 2. 将中缀表达式转成后缀表达式 48 | ArrayList suffixList = infixToSuffix.infixList2SuffixList(infixList); 49 | System.out.println(suffixList); // [1, 2, 3, +, 4, *, +, 5, -] 50 | 51 | ReversePolishCalculator calculator = new ReversePolishCalculator(); 52 | int res = calculator.start(suffixList); 53 | System.out.println(infixExpression + " = " + res); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/calculator/ReversePolishMultiCalc.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.calculator; 2 | 3 | /** 4 | *
  5 |  * 完整版的逆波兰计算器,功能包括
  6 |  * 支持 + - * / ( )
  7 |  * 多位数,支持小数,
  8 |  * 兼容处理, 过滤任何空白字符,包括空格、制表符、换页符
  9 |  *
 10 |  * 逆波兰计算器完整版考虑的因素较多,下面给出完整版代码供同学们学习,其基本思路和前面一样,也是使用到:中缀表达式转后缀表达式。
 11 |  * 
12 | */ 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import java.util.Stack; 18 | import java.util.regex.Pattern; 19 | 20 | public class ReversePolishMultiCalc { 21 | 22 | /** 23 | * 匹配 + - * / ( ) 运算符 24 | */ 25 | static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)"; 26 | 27 | static final String LEFT = "("; 28 | static final String RIGHT = ")"; 29 | static final String ADD = "+"; 30 | static final String MINUS = "-"; 31 | static final String TIMES = "*"; 32 | static final String DIVISION = "/"; 33 | 34 | /** 35 | * 加減 + - 36 | */ 37 | static final int LEVEL_01 = 1; 38 | /** 39 | * 乘除 * / 40 | */ 41 | static final int LEVEL_02 = 2; 42 | 43 | /** 44 | * 括号 45 | */ 46 | static final int LEVEL_HIGH = Integer.MAX_VALUE; 47 | 48 | 49 | static Stack stack = new Stack<>(); 50 | static List data = Collections.synchronizedList(new ArrayList()); 51 | 52 | /** 53 | * 去除所有空白符 54 | * 55 | * @param s 56 | * @return 57 | */ 58 | public static String replaceAllBlank(String s) { 59 | // \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v] 60 | return s.replaceAll("\\s+", ""); 61 | } 62 | 63 | /** 64 | * 判断是不是数字 int double long float 65 | * 66 | * @param s 67 | * @return 68 | */ 69 | public static boolean isNumber(String s) { 70 | Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$"); 71 | return pattern.matcher(s).matches(); 72 | } 73 | 74 | /** 75 | * 判断是不是运算符 76 | * 77 | * @param s 78 | * @return 79 | */ 80 | public static boolean isSymbol(String s) { 81 | return s.matches(SYMBOL); 82 | } 83 | 84 | /** 85 | * 匹配运算等级 86 | * 87 | * @param s 88 | * @return 89 | */ 90 | public static int calcLevel(String s) { 91 | if ("+".equals(s) || "-".equals(s)) { 92 | return LEVEL_01; 93 | } else if ("*".equals(s) || "/".equals(s)) { 94 | return LEVEL_02; 95 | } 96 | return LEVEL_HIGH; 97 | } 98 | 99 | /** 100 | * 匹配 101 | * 102 | * @param s 103 | * @throws Exception 104 | */ 105 | public static List doMatch(String s) throws Exception { 106 | if (s == null || "".equals(s.trim())) throw new RuntimeException("data is empty"); 107 | if (!isNumber(s.charAt(0) + "")) throw new RuntimeException("data illeagle,start not with a number"); 108 | 109 | s = replaceAllBlank(s); 110 | 111 | String each; 112 | int start = 0; 113 | 114 | for (int i = 0; i < s.length(); i++) { 115 | if (isSymbol(s.charAt(i) + "")) { 116 | each = s.charAt(i) + ""; 117 | //栈为空,(操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及是 ) 不能直接入栈 118 | if (stack.isEmpty() || LEFT.equals(each) 119 | || ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)) { 120 | stack.push(each); 121 | } else if (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) { 122 | //栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈 123 | while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) { 124 | if (calcLevel(stack.peek()) == LEVEL_HIGH) { 125 | break; 126 | } 127 | data.add(stack.pop()); 128 | } 129 | stack.push(each); 130 | } else if (RIGHT.equals(each)) { 131 | // ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈 132 | while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())) { 133 | if (LEVEL_HIGH == calcLevel(stack.peek())) { 134 | stack.pop(); 135 | break; 136 | } 137 | data.add(stack.pop()); 138 | } 139 | } 140 | start = i; //前一个运算符的位置 141 | } else if (i == s.length() - 1 || isSymbol(s.charAt(i + 1) + "")) { 142 | each = start == 0 ? s.substring(start, i + 1) : s.substring(start + 1, i + 1); 143 | if (isNumber(each)) { 144 | data.add(each); 145 | continue; 146 | } 147 | throw new RuntimeException("data not match number"); 148 | } 149 | } 150 | //如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个stack 添加到队列 151 | Collections.reverse(stack); 152 | data.addAll(new ArrayList<>(stack)); 153 | 154 | System.out.println(data); 155 | return data; 156 | } 157 | 158 | /** 159 | * 算出结果 160 | * 161 | * @param list 162 | * @return 163 | */ 164 | public static Double doCalc(List list) { 165 | Double d = 0d; 166 | if (list == null || list.isEmpty()) { 167 | return null; 168 | } 169 | if (list.size() == 1) { 170 | System.out.println(list); 171 | d = Double.valueOf(list.get(0)); 172 | return d; 173 | } 174 | ArrayList list1 = new ArrayList<>(); 175 | for (int i = 0; i < list.size(); i++) { 176 | list1.add(list.get(i)); 177 | if (isSymbol(list.get(i))) { 178 | Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i)); 179 | list1.remove(i); 180 | list1.remove(i - 1); 181 | list1.set(i - 2, d1 + ""); 182 | list1.addAll(list.subList(i + 1, list.size())); 183 | break; 184 | } 185 | } 186 | doCalc(list1); 187 | return d; 188 | } 189 | 190 | /** 191 | * 运算 192 | * 193 | * @param s1 194 | * @param s2 195 | * @param symbol 196 | * @return 197 | */ 198 | public static Double doTheMath(String s1, String s2, String symbol) { 199 | Double result; 200 | switch (symbol) { 201 | case ADD: 202 | result = Double.valueOf(s1) + Double.valueOf(s2); 203 | break; 204 | case MINUS: 205 | result = Double.valueOf(s1) - Double.valueOf(s2); 206 | break; 207 | case TIMES: 208 | result = Double.valueOf(s1) * Double.valueOf(s2); 209 | break; 210 | case DIVISION: 211 | result = Double.valueOf(s1) / Double.valueOf(s2); 212 | break; 213 | default: 214 | result = null; 215 | } 216 | return result; 217 | 218 | } 219 | 220 | public static void main(String[] args) { 221 | //String math = "9+(3-1)*3+10/2"; 222 | String math = "12.8 + (2 - 3.55)*4+10/5.0"; 223 | try { 224 | doCalc(doMatch(math)); 225 | } catch (Exception e) { 226 | e.printStackTrace(); 227 | } 228 | } 229 | 230 | } 231 | 232 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/linkedlist/LinkedListStack.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.linkedlist; 2 | 3 | /** 4 | * 链表实现栈; 单向链表 5 | */ 6 | public class LinkedListStack { 7 | int maxSize; // 最大支持数 8 | int size; // 当前栈中元素个数 9 | // 用来记录栈顶的元素 10 | Node top; 11 | 12 | public LinkedListStack(int maxSize) { 13 | this.maxSize = maxSize; 14 | } 15 | 16 | /** 17 | * 是否已满 18 | * 19 | * @return 20 | */ 21 | public boolean isFull() { 22 | return size == maxSize; 23 | } 24 | 25 | /** 26 | * 是否为空 27 | * 28 | * @return 29 | */ 30 | public boolean isEmpty() { 31 | return size == 0; 32 | } 33 | 34 | /** 35 | * 入栈 36 | * 37 | * @param value 38 | */ 39 | public void push(int value) { 40 | if (isFull()) { 41 | System.out.println("栈已满"); 42 | return; 43 | } 44 | // 要保证 top 是最后进来的 45 | Node temp = top; 46 | top = new Node(value); 47 | top.next = temp; 48 | size++; 49 | } 50 | 51 | 52 | /** 53 | * 出栈 54 | * 55 | * @return 56 | */ 57 | public int pop() { 58 | if (isEmpty()) { 59 | throw new RuntimeException("栈已空"); 60 | } 61 | // top 保存的是最后入栈的元素,直接从 top 取出即可 62 | Node temp = top; 63 | top = temp.next; 64 | size--; 65 | return temp.value; 66 | } 67 | 68 | /** 69 | * 显示栈中数据,从栈顶开始显示,也就是按出栈的顺序显示 70 | */ 71 | public void print() { 72 | if (isEmpty()) { 73 | System.out.println("栈已空"); 74 | return; 75 | } 76 | Node cur = top; 77 | while (cur != null) { 78 | System.out.println(cur); 79 | cur = cur.next; 80 | } 81 | } 82 | } 83 | 84 | class Node { 85 | int value; 86 | Node next; 87 | 88 | public Node(int value) { 89 | this.value = value; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "Node{" + 95 | "value=" + value + 96 | '}'; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/stack/linkedlist/LinkedListStackTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.stack.linkedlist; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 链表实现栈 7 | */ 8 | public class LinkedListStackTest { 9 | @Test 10 | public void pushTest() { 11 | LinkedListStack stack = new LinkedListStack(4); 12 | stack.push(1); 13 | stack.push(2); 14 | stack.push(3); 15 | stack.push(4); 16 | stack.print(); 17 | stack.push(5); 18 | } 19 | 20 | @Test 21 | public void popTest() { 22 | LinkedListStack stack = new LinkedListStack(4); 23 | stack.push(1); 24 | stack.push(2); 25 | stack.print(); 26 | System.out.println("pop 数据:" + stack.pop()); 27 | stack.print(); 28 | System.out.println("pop 数据:" + stack.pop()); 29 | stack.print(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/tree/ArrBinaryTreeTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.tree; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 顺序存储二叉树 7 | */ 8 | public class ArrBinaryTreeTest { 9 | /** 10 | * 前序遍历测试 11 | */ 12 | @Test 13 | public void preOrder() { 14 | int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7}; 15 | ArrBinaryTree tree = new ArrBinaryTree(arr); 16 | tree.preOrder(0); // 1,2,4,5,3,6,7 17 | } 18 | 19 | /** 20 | * 中序遍历测试 21 | */ 22 | @Test 23 | public void infixOrder() { 24 | int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7}; 25 | ArrBinaryTree tree = new ArrBinaryTree(arr); 26 | tree.infixOrder(0); // 4,2,5,1,6,3,7 27 | } 28 | 29 | /** 30 | * 后序遍历测试 31 | */ 32 | @Test 33 | public void postOrder() { 34 | int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7}; 35 | ArrBinaryTree tree = new ArrBinaryTree(arr); 36 | tree.postOrder(0); // 4,5,2,6,7,3,1 37 | } 38 | 39 | } 40 | 41 | class ArrBinaryTree { 42 | int[] arr; 43 | 44 | public ArrBinaryTree(int[] arr) { 45 | this.arr = arr; 46 | } 47 | 48 | /** 49 | * 前序遍历 50 | * 51 | * @param index 就是知识点中的 n,从哪一个节点开始遍历 52 | */ 53 | public void preOrder(int index) { 54 | /* 55 | 1. 顺序二叉树 通常只考虑 **完成二叉树** 56 | 2. 第 n 个元素的 **左子节点** 为 `2*n+1` 57 | 3. 第 n 个元素的 **右子节点** 为 `2*n+2` 58 | 4. 第 n 个元素的 **父节点** 为 `(n-1)/2` 59 | */ 60 | if (arr == null || arr.length == 0) { 61 | System.out.println("数组为空,不能前序遍历二叉树"); 62 | return; 63 | } 64 | // 1. 先输出当前节点(初始节点是 root 节点) 65 | System.out.println(arr[index]); 66 | // 2. 如果左子节点不为空,则递归继续前序遍历 67 | int left = 2 * index + 1; 68 | if (left < arr.length) { 69 | preOrder(left); 70 | } 71 | // 3. 如果右子节点不为空,则递归继续前序遍历 72 | int right = 2 * index + 2; 73 | if (right < arr.length) { 74 | preOrder(right); 75 | } 76 | } 77 | 78 | /** 79 | * 中序遍历:先遍历左子树,再输出父节点,再遍历右子树 80 | * 81 | * @param index 82 | */ 83 | public void infixOrder(int index) { 84 | if (arr == null || arr.length == 0) { 85 | System.out.println("数组为空,不能前序遍历二叉树"); 86 | return; 87 | } 88 | int left = 2 * index + 1; 89 | if (left < arr.length) { 90 | infixOrder(left); 91 | } 92 | System.out.println(arr[index]); 93 | int right = 2 * index + 2; 94 | if (right < arr.length) { 95 | infixOrder(right); 96 | } 97 | } 98 | 99 | /** 100 | * 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点 101 | * 102 | * @param index 103 | */ 104 | public void postOrder(int index) { 105 | if (arr == null || arr.length == 0) { 106 | System.out.println("数组为空,不能前序遍历二叉树"); 107 | return; 108 | } 109 | int left = 2 * index + 1; 110 | if (left < arr.length) { 111 | postOrder(left); 112 | } 113 | int right = 2 * index + 2; 114 | if (right < arr.length) { 115 | postOrder(right); 116 | } 117 | System.out.println(arr[index]); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/tree/HeapSortTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.tree; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 堆排序 11 | */ 12 | public class HeapSortTest { 13 | @Test 14 | public void processSortTest() { 15 | int[] arr = {4, 6, 8, 5, 9}; 16 | processSort(arr); 17 | } 18 | 19 | private void processSort(int[] arr) { 20 | // 第一次:从最后一个非叶子节点开始调整,从左到右,从上到下进行调整。 21 | // 参与比较的元素是:6,5,9 22 | int i = 1; 23 | int temp = arr[i]; // 6 24 | // 如果 i 的左比右大 25 | int k = i * 2 + 1; // i 的左节点 26 | 27 | // 要将这三个数(堆),调整为一个大顶堆 28 | // i 的左节点小于右节点 29 | if (arr[k] < arr[k + 1]) { 30 | k++; // 右边的大,则将 k 变成最大的那一个 31 | } 32 | // 如果左右中最大的一个数,比 i 大。则调整它 33 | if (arr[k] > temp) { 34 | arr[i] = arr[k]; 35 | arr[k] = temp; 36 | } 37 | System.out.println(Arrays.toString(arr)); // 4,9,8,5,6 38 | 39 | // 第二次调整:参与比较的元素是 4,9,5 40 | i = 0; 41 | temp = arr[i]; // 4 42 | k = i * 2 + 1; 43 | if (arr[k] < arr[k + 1]) { 44 | k++; // 右边的大,则将 k 变成最大的那一个 45 | } 46 | // 9 比 4 大,交换的是 9 和 4 47 | if (arr[k] > temp) { 48 | arr[i] = arr[k]; 49 | arr[k] = temp; 50 | } 51 | System.out.println(Arrays.toString(arr)); // 9,4,8,5,6 52 | 53 | // 上面调整导致了,第一次的堆:4,5,6 的混乱。这里要对他进行重新调整 54 | i = 1; 55 | temp = arr[i]; // 4 56 | k = i * 2 + 1; 57 | if (arr[k] < arr[k + 1]) { 58 | k++; // 右边的大,则将 k 变成最大的那一个 59 | } 60 | // 6 比 4 大,交换它 61 | if (arr[k] > temp) { 62 | arr[i] = arr[k]; 63 | arr[k] = temp; 64 | } 65 | System.out.println(Arrays.toString(arr)); // 9,6,8,5,4 66 | // 到这里就构造成了一个大顶堆 67 | } 68 | 69 | @Test 70 | public void sortTest() { 71 | int[] arr = {4, 6, 8, 5, 9}; 72 | sort(arr); 73 | int[] arr2 = {99, 4, 6, 8, 5, 9, -1, -2, 100}; 74 | sort(arr2); 75 | } 76 | 77 | 78 | private void sort(int[] arr) { 79 | // ===== 1. 构造初始堆 80 | // 从第一个非叶子节点开始调整 81 | // 4,9,8,5,6 82 | // adjustHeap(arr, arr.length / 2 - 1, arr.length); 83 | 84 | // 循环调整 85 | // 从第一个非叶子节点开始调整,自低向上 86 | for (int i = arr.length / 2 - 1; i >= 0; i--) { 87 | adjustHeap(arr, i, arr.length); 88 | } 89 | // 第一轮调整了 3 个堆后:结果为:9,6,8,5,4 90 | // System.out.println(Arrays.toString(arr)); 91 | 92 | // 2. 将堆顶元素与末尾元素进行交换,然后再重新调整 93 | int temp = 0; 94 | for (int j = arr.length - 1; j > 0; j--) { 95 | temp = arr[j]; // j 是末尾元素 96 | arr[j] = arr[0]; 97 | arr[0] = temp; 98 | // 这里是从第一个节点开始: 不是构建初始堆了 99 | // 如果 100 | adjustHeap(arr, 0, j); 101 | } 102 | // System.out.println(Arrays.toString(arr)); 103 | } 104 | 105 | /** 106 | * 调整堆 107 | * 108 | * @param arr 109 | * @param i 非叶子节点,以此节点为基础,将它、它的左、右,调整为一个大顶堆 110 | * @param length 111 | */ 112 | private void adjustHeap(int[] arr, int i, int length) { 113 | // 难点是将当前的堆调整之后,影响了它后面节点堆的混乱,如何继续对影响的堆进行调整 114 | // 所以第一步中:是额外循环的从 低向上调整的 115 | // 第三步中:就是本代码的,从上到下调整的;这个很重要,一定要明白 116 | int temp = arr[i]; 117 | // 从传入节点的左节点开始处理,下一次则是以该节点为顶堆的左节点进行调整 118 | for (int k = i * 2 + 1; k < length; k = k * 2 + 1) { 119 | // 要将这三个数(堆),调整为一个大顶堆 120 | // i 的左节点小于右节点 121 | // k+1 < length : 当调整长度为 2 时,也就是数组的前两个元素,其实它没有第三个节点了,就不能走这个判定 122 | if (k + 1 < length && arr[k] < arr[k + 1]) { 123 | k++; // 右边的大,则将 k 变成最大的那一个 124 | } 125 | // 如果左右中最大的一个数,比 i 大。则调整它 126 | if (arr[k] > temp) { 127 | arr[i] = arr[k]; 128 | i = k; // i 记录被调整后的索引。 129 | } else { 130 | break; 131 | // 由于初始堆,就已经是大顶堆了,每个子堆的顶,都是比他的左右两个大的 132 | // 当这里没有进行调整的话,那么就可以直接退出了 133 | // 如果上面进行了调整。那么在初始堆之后,每次都是从 0 节点开始 自左到右,自上而下调整的 134 | // 就会一层一层的往下进行调整 135 | } 136 | } 137 | arr[i] = temp; 138 | } 139 | 140 | /** 141 | * 大量数据排序时间测试 142 | */ 143 | @Test 144 | public void bulkDataSort() { 145 | int max = 800_000; 146 | // int max = 8; 147 | int[] arr = new int[max]; 148 | for (int i = 0; i < max; i++) { 149 | arr[i] = (int) (Math.random() * max); 150 | } 151 | if (arr.length < 10) { 152 | System.out.println("原始数组:" + Arrays.toString(arr)); 153 | } 154 | Instant startTime = Instant.now(); 155 | sort(arr); 156 | if (arr.length < 10) { 157 | System.out.println("排序后:" + Arrays.toString(arr)); 158 | } 159 | Instant endTime = Instant.now(); 160 | System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/cn/mrcode/study/dsalgtutorialdemo/datastructure/tree/ThreadedBinaryTreeTest.java: -------------------------------------------------------------------------------- 1 | package cn.mrcode.study.dsalgtutorialdemo.datastructure.tree; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * 线索化二叉树 7 | */ 8 | public class ThreadedBinaryTreeTest { 9 | class HeroNode { 10 | public int id; 11 | public String name; 12 | public HeroNode left; 13 | public HeroNode right; 14 | /** 15 | * 左节点的类型:0:左子树,1:前驱节点 16 | */ 17 | public int leftType; 18 | /** 19 | * 右节点的类型:0:右子树,1:后继节点 20 | */ 21 | public int rightType; 22 | 23 | public HeroNode(int id, String name) { 24 | this.id = id; 25 | this.name = name; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "HeroNode{" + 31 | "id=" + id + 32 | ", name='" + name + '\'' + 33 | '}'; 34 | } 35 | } 36 | 37 | class ThreadedBinaryTree { 38 | public HeroNode root; 39 | public HeroNode pre; // 保留上一个节点 40 | 41 | /** 42 | * 线索化二叉树:以 中序的方式线索化 43 | */ 44 | public void threadeNodes() { 45 | // 从 root 开始遍历,然后 线索化 46 | this.threadeNodes(root); 47 | } 48 | 49 | private void threadeNodes(HeroNode node) { 50 | if (node == null) { 51 | return; 52 | } 53 | // 中序遍历顺序:先左、自己、再右 54 | threadeNodes(node.left); 55 | // 难点就是在这里,如何线索化自己 56 | // 当自己的 left 节点为空,则设置为前驱节点 57 | if (node.left == null) { 58 | node.left = pre; 59 | node.leftType = 1; 60 | } 61 | 62 | // 因为要设置后继节点,只有回到自己的后继节点的时候,才能把自己设置为前一个的后继节点 63 | // 当前一个节点的 right 为空时,则需要自己是后继节点 64 | if (pre != null && pre.right == null) { 65 | pre.right = node; 66 | pre.rightType = 1; 67 | } 68 | 69 | // 数列: 1,3,6,8,10,14 70 | // 中序: 8,3,10,1,14,6 71 | // 这里最好结合图示的二叉树来看,容易理解 72 | // 因为中序遍历,先遍历左边,所以 8 是第一个输出的节点 73 | // 当 node = 8 时,pre 还没有被赋值过,则为空。这是正确的,因为 8 就是第一个节点 74 | // 当 8 处理完成之后,处理 3 时 75 | // 当 node = 3 时,pre 被赋值为 8 了。 76 | pre = node; 77 | threadeNodes(node.right); 78 | } 79 | 80 | /** 81 | * 遍历线索化二叉树 82 | */ 83 | public void threadedList() { 84 | // 前面线索化使用的是中序,这里也同样要用中序的方式 85 | // 但是不适合使用之前那种递归了 86 | HeroNode node = root; 87 | while (node != null) { 88 | // 中序:左、自己、右 89 | // 数列: 1,3,6,8,10,14 90 | // 中序: 8,3,10,1,14,6 91 | // 那么先找到左边的第一个线索化节点,也就是 8. 对照图示理解,比较容易 92 | while (node.leftType == 0) { 93 | node = node.left; 94 | } 95 | // 找到这个线索化节点之后,打印它 96 | System.out.println(node); 97 | 98 | // 如果该节点右子节点也是线索化节点,则打印它 99 | while (node.rightType == 1) { 100 | node = node.right; 101 | System.out.println(node); 102 | } 103 | 104 | // 到达这里,就说明遇到的不是一个 线索化节点了 105 | // 而且,按中序的顺序来看:这里应该处理右侧了 106 | node = node.right; 107 | } 108 | } 109 | 110 | public void preOrderThreadeNodes() { 111 | preOrderThreadeNodes(root); 112 | } 113 | 114 | /** 115 | * 前序线索化二叉树 116 | */ 117 | public void preOrderThreadeNodes(HeroNode node) { 118 | // 前序:自己、左(递归)、右(递归) 119 | // 数列: 1,3,6,8,10,14 120 | // 前序: 1,3,8,10,6,14 121 | 122 | if (node == null) { 123 | return; 124 | } 125 | 126 | System.out.println(node); 127 | // 当自己的 left 节点为空,则可以线索化 128 | if (node.left == null) { 129 | node.left = pre; 130 | node.leftType = 1; 131 | } 132 | // 当前一个节点 right 为空,则可以把自己设置为前一个节点的后继节点 133 | if (pre != null && pre.right == null) { 134 | pre.right = node; 135 | pre.rightType = 1; 136 | } 137 | 138 | // 因为是前序,因此 pre 保存的是自己 139 | // 到下一个节点的时候,下一个节点如果是线索化节点 ,才能将自己作为它的前驱节点 140 | pre = node; 141 | 142 | // 那么继续往左,查找符合可以线索化的节点 143 | // 因为先处理的自己,如果 left == null,就已经线索化了 144 | // 再往左的时候,就不能直接进入了 145 | // 需要判定,如果不是线索化节点,再进入 146 | // 比如:当前节点 8,前驱 left 被设置为了 3 147 | // 这里节点 8 的 left 就为 1 了,就不能继续递归,否则又回到了节点 3 上 148 | // 导致死循环了。 149 | if (node.leftType == 0) { 150 | preOrderThreadeNodes(node.left); 151 | } 152 | if (node.rightType == 0) { 153 | preOrderThreadeNodes(node.right); 154 | } 155 | } 156 | 157 | /** 158 | * 前序线索化二叉树遍历 159 | */ 160 | public void preOrderThreadeList() { 161 | HeroNode node = root; 162 | // 最后一个节点无后继节点,就会退出了 163 | // 前序:自己、左(递归)、右(递归) 164 | while (node != null) { 165 | // 先打印自己 166 | System.out.println(node); 167 | 168 | while (node.leftType == 0) { 169 | node = node.left; 170 | System.out.println(node); 171 | } 172 | while (node.rightType == 1) { 173 | node = node.right; 174 | System.out.println(node); 175 | } 176 | node = node.right; 177 | } 178 | } 179 | } 180 | 181 | @Test 182 | public void threadeNodesTest() { 183 | // 1,3,6,8,10,14 184 | HeroNode n1 = new HeroNode(1, "宋江"); 185 | HeroNode n3 = new HeroNode(3, "无用"); 186 | HeroNode n6 = new HeroNode(6, "卢俊"); 187 | HeroNode n8 = new HeroNode(8, "林冲2"); 188 | HeroNode n10 = new HeroNode(10, "林冲3"); 189 | HeroNode n14 = new HeroNode(14, "林冲4"); 190 | n1.left = n3; 191 | n1.right = n6; 192 | n3.left = n8; 193 | n3.right = n10; 194 | n6.left = n14; 195 | 196 | ThreadedBinaryTree tree = new ThreadedBinaryTree(); 197 | tree.root = n1; 198 | 199 | tree.threadeNodes(); 200 | 201 | // 验证: 202 | HeroNode left = n10.left; 203 | HeroNode right = n10.right; 204 | System.out.println("10 号节点的前驱节点:" + left.id); 205 | System.out.println("10 号节点的后继节点:" + right.id); 206 | } 207 | 208 | /** 209 | * 中序:线索化遍历测试 210 | */ 211 | @Test 212 | public void threadedListTest() { 213 | ThreadedBinaryTree tree = buildTree(); 214 | tree.threadeNodes(); 215 | tree.threadedList(); // 8,3,10,1,14,6 216 | } 217 | 218 | /** 219 | * 前序线索化 220 | */ 221 | @Test 222 | public void preOrderThreadedNodesTest() { 223 | // 1,3,6,8,10,14 224 | HeroNode n1 = new HeroNode(1, "宋江"); 225 | HeroNode n3 = new HeroNode(3, "无用"); 226 | HeroNode n6 = new HeroNode(6, "卢俊"); 227 | HeroNode n8 = new HeroNode(8, "林冲2"); 228 | HeroNode n10 = new HeroNode(10, "林冲3"); 229 | HeroNode n14 = new HeroNode(14, "林冲4"); 230 | n1.left = n3; 231 | n1.right = n6; 232 | n3.left = n8; 233 | n3.right = n10; 234 | n6.left = n14; 235 | 236 | ThreadedBinaryTree tree = new ThreadedBinaryTree(); 237 | tree.root = n1; 238 | 239 | tree.preOrderThreadeNodes(); 240 | 241 | // 验证: 前序顺序: 1,3,8,10,6,14 242 | HeroNode left = n10.left; 243 | HeroNode right = n10.right; 244 | System.out.println("10 号节点的前驱节点:" + left.id); // 8 245 | System.out.println("10 号节点的后继节点:" + right.id); // 6 246 | 247 | left = n6.left; 248 | right = n6.right; 249 | System.out.println("6 号节点的前驱节点:" + left.id); // 14, 普通节点 250 | System.out.println("6 号节点的后继节点:" + right.id); // 14,线索化节点 251 | } 252 | 253 | @Test 254 | public void preOrderThreadeListTest() { 255 | ThreadedBinaryTree tree = buildTree(); 256 | tree.preOrderThreadeNodes(); 257 | System.out.println("前序线索化遍历"); 258 | tree.preOrderThreadeList(); // 1,3,8,10,6,14 259 | } 260 | 261 | /** 262 | * 构建一颗数 263 | * 264 | * @return 265 | */ 266 | private ThreadedBinaryTree buildTree() { 267 | // 1,3,6,8,10,14 268 | HeroNode n1 = new HeroNode(1, "宋江"); 269 | HeroNode n3 = new HeroNode(3, "无用"); 270 | HeroNode n6 = new HeroNode(6, "卢俊"); 271 | HeroNode n8 = new HeroNode(8, "林冲2"); 272 | HeroNode n10 = new HeroNode(10, "林冲3"); 273 | HeroNode n14 = new HeroNode(14, "林冲4"); 274 | n1.left = n3; 275 | n1.right = n6; 276 | n3.left = n8; 277 | n3.right = n10; 278 | n6.left = n14; 279 | 280 | ThreadedBinaryTree tree = new ThreadedBinaryTree(); 281 | tree.root = n1; 282 | return tree; 283 | } 284 | } 285 | --------------------------------------------------------------------------------