├── .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
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
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
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
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