diff --git "a/notes/algorithms/\351\200\211\346\213\251\346\216\222\345\272\217\347\256\227\346\263\225.md" "b/notes/algorithms/\351\200\211\346\213\251\346\216\222\345\272\217\347\256\227\346\263\225.md" index 7b247d7..82b1ae2 100644 --- "a/notes/algorithms/\351\200\211\346\213\251\346\216\222\345\272\217\347\256\227\346\263\225.md" +++ "b/notes/algorithms/\351\200\211\346\213\251\346\216\222\345\272\217\347\256\227\346\263\225.md" @@ -30,7 +30,7 @@ public class SelectionSort { for (int i = 0; i < n; i++) { int min = i; - for (int j = i+1; i < n; i++) { + for (int j = i+1; j < n; i++) { if (arr[j] < arr[min]) { min = j; } diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/huawei/Question01.java b/src/main/java/com/bruis/algorithminjava/algorithm/huawei/Question01.java deleted file mode 100644 index 6f1092a..0000000 --- a/src/main/java/com/bruis/algorithminjava/algorithm/huawei/Question01.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.bruis.algorithminjava.algorithm.huawei; - -import java.util.Scanner; - -/** - * @author LuoHaiYang - * - * 题目描述:输入一个int型的正整数,计算出该int型数据在内存中存储时1的个数。 - * - * 输入描述: 输入一个整数(int类型) - * - * 输出描述: 这个数转换成2进制后,输出1的个数 - * - * 实例1: - * - * 输入:5 - * 输出:2 - * - */ -public class Question01 { - - /** - * 笨办法 - * @param args - */ - public static void main(String[] args) { - - // 巧妙法,通过二进制位运算 - Scanner in = new Scanner(System.in); - - int count = in.nextInt(), result = 0; - while (count > 0) { - if ((count & 1) > 0) { - result++; - } - count = count >> 1; - } - System.out.println(result); -/* - 笨办法 - Scanner scanner = new Scanner(System.in); - int input = scanner.nextInt(); - char[] bytes = Integer.toBinaryString(input).toCharArray(); - int result = 0; - for (char c : bytes) { - if (c == '1') { - result++; - } - } - System.out.println(result); -*/ - } - -} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ContainsDuplicate_217.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ContainsDuplicate_217.java new file mode 100644 index 0000000..924d68c --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ContainsDuplicate_217.java @@ -0,0 +1,111 @@ +package com.bruis.algorithminjava.algorithm.leetcode; + +import java.util.*; + +/** + * 217: Contains Duplicate + * @Description + * @Author luohaiyang + * @Date 2022/6/24 + */ +public class ContainsDuplicate_217 { + + public static void main(String[] args) { + int[] test = {1, 2, 30, 22, 3, 4, 5, 1}; + System.out.println(containsDuplicate(test)); + } + + public static boolean containsDuplicate(int[] nums) { + // 数组类型题思考步骤: + // 1) 入参是否合法,是否越界等; + // 2) 是否是有序; + // 3) 是否有负数; + + // 题解思路: + // 1. 暴力破解法; O(n^2); + // 2. 桶排序? + // 2.1 需要Map数据结构; + // 2.2 不需要Map数据结构; + // 3. 排序之后,判断前后数字是否一样; + + return false; + } + + public static boolean setSolution(int[] nums) { + int n = nums.length; + if (n < 1) { + return false; + } + Set set = new HashSet(); + for (int x : nums) { + if (!set.add(x)) { + return true; + } + } + return false; + } + + /** + * 通过排序来进行筛选 + * @param nums + * @return + */ + public static boolean sortSolution(int[] nums) { + int n = nums.length; + if (n < 1) { + return false; + } + Arrays.sort(nums); + for (int i = 1; i < nums.length - 1; i++) { + if (nums[i] == nums[i+1]) { + return true; + } + } + return false; + } + + /** + * 基于桶排序 + * @param nums + * @return + */ + public static boolean bucketMapSolution(int[] nums) { + int n = nums.length; + if (n < 1) { + return false; + } + + Map duplicateMap = new HashMap<>(n); + + for (int i = 0; i < n; i++) { + if (!duplicateMap.containsKey(nums[i])) { + duplicateMap.put(nums[i], 1); + continue; + } + return true; + } + return false; + } + + /** + * 暴力解法 + * @param nums + * @return + */ + public static boolean violentSolution(int[] nums) { + if (nums.length < 1) { + return false; + } + + // 方法一:暴力解法 + int n = nums.length; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (i != j && nums[i] == nums[j]) { + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/LongestPalindromicSubstring_5.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/LongestPalindromicSubstring_5.java new file mode 100644 index 0000000..2c84640 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/LongestPalindromicSubstring_5.java @@ -0,0 +1,9 @@ +package com.bruis.algorithminjava.algorithm.leetcode; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/6/28 + */ +public class LongestPalindromicSubstring_5 { +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/MaximumSubarray_53.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/MaximumSubarray_53.java new file mode 100644 index 0000000..f59db25 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/MaximumSubarray_53.java @@ -0,0 +1,68 @@ +package com.bruis.algorithminjava.algorithm.leetcode; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/6/24 + */ +public class MaximumSubarray_53 { + + public static void main(String[] args) { + // 条件: + // 1. 连续数组(不可以排序); + + // 解题思路: + // 1. 暴力解法;(超时) + // 2. 正确解法-动态规划; + + // 有卡顿、纠结的点: + // 1. 算法起始条件、结束条件; + + int[] nusm = {-2,1,-3,4,-1,2,1,-5,4}; + System.out.println(violentSolution(nusm)); + } + + public static int violentSolution(int[] nums) { + int n = nums.length; + if (n < 2) { + return nums[0]; + } + int max = nums[0];; + for (int i = 0; i < n; i++) { + int subTotal = nums[i]; + for (int j = i + 1; j < n; j++) { + if (max < nums[j]) { + max = nums[j]; + } + subTotal += nums[j]; + if (max < subTotal) { + max = subTotal; + } + } + } + return max; + } + + public static int maxSubArray(int[] nums) { + int n = nums.length; + if (n < 2) { + return nums[0]; + } + int max = nums[0]; + int i = 0, j = i + 1; + while (j < n) { + if (max < nums[j]) { + max = nums[j]; + } + int totalMax = 0; + for (int k = i; k < j; k++) { + totalMax += nums[i] + nums[j]; + } + if (max < totalMax) { + max = totalMax; + } + j++; + } + return max; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSum.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSum.java new file mode 100644 index 0000000..c3dea2f --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSum.java @@ -0,0 +1,90 @@ +package com.bruis.algorithminjava.algorithm.leetcode; + +import java.util.HashMap; +import java.util.Map; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/28 + */ +public class TwoSum { + public int[] twoSum(int[] nums, int target) { + // 暴力破解法 +// return forceSolution(nums, target); + return optimizeSolution01(nums, target); + } + + /** + * 对optimizeSolution01进行优化,少进行一次for循环 + * 时间复杂度: O(n) + * 空间复杂度:O(n) + * @param nums + * @param target + * @return + */ + private int[] optimizeSolution02(int[] nums, int target) { + int n = nums.length; + Map arrayMap = new HashMap<>(); + for (int i = 0; i < n; i++) { + int num = nums[i]; + if (arrayMap.containsKey(target - num)) { + return new int[]{arrayMap.get(target - num), i}; + } + arrayMap.put(num, i); + } + return new int[0]; + } + + /** + * 借助jdk hashmap, + * 时间复杂度: O(n) + * 空间复杂度:O(n) + * @param nums + * @param target + * @return + */ + private int[] optimizeSolution01(int[] nums, int target) { + int n = nums.length; + Map arrayMap = new HashMap<>(); + for (int i = 0; i < n; i++) { + int num = nums[i]; + int targetVal = target - num; + arrayMap.put(targetVal, i); + } + for (int i = 0; i < n; i++) { + int num = nums[i]; + if (arrayMap.containsKey(num)) { + int index = arrayMap.get(num); + if (index > i) { + return new int[]{i, index}; + } else { + return new int[]{index, i}; + } + } + } + return new int[0]; + } + + /** + * 暴力破解法 + * 时间复杂度:o(n^2) + * 空间复杂度:O(n) + * @param nums + * @param target + * @return + */ + private int[] forceSolution(int[] nums, int target) { + int n = nums.length; + for (int i = 0; i < n; i++) { + int num = nums[i]; + int val = target - num; + for (int j = i + 1; j < n; j++) { + if (nums[j] == val) { + return new int[]{i, j}; + } + } + } + return new int[0]; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSumII.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSumII.java new file mode 100644 index 0000000..79147f1 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSumII.java @@ -0,0 +1,62 @@ +package com.bruis.algorithminjava.algorithm.leetcode; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/28 + */ +public class TwoSumII { + public int[] twoSum(int[] numbers, int target) { + return twoPointer(numbers, target); + } + + /** + * 双指针 + * 时间复杂度:O(n) + * 空间复杂度:O(1) + * @param numbers + * @param target + * @return + */ + private int[] twoPointer(int[] numbers, int target) { + int n = numbers.length; + if (n < 2) { + return numbers; + } + int i = 0, j = n - 1; + while (i < j) { + if (numbers[i] + numbers[j] == target) { + return new int[]{i + 1, j + 1}; + } + if (numbers[i] + numbers[j] > target) { + j--; + } else { + i++; + } + } + return new int[0]; + } + + /** + * 暴力法: + * 时间复杂度:O(n^2) + * 空间复杂度:O(1) + * @param numbers + * @param target + * @return + */ + private int[] forceSolution(int[] numbers, int target) { + int n = numbers.length; + if (n < 2) { + return numbers; + } + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (numbers[i] + numbers[j] == target) { + return new int[]{i + 1, j + 1}; + } + } + } + return new int[0]; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java index 389d4df..d75488f 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java @@ -12,34 +12,7 @@ * @author LuoHaiYang */ public class IsPalindrome { - public boolean isPalindrome(String s) { - if (s.equals("") || s == null) { - return false; - } - String lowerCase = s.toLowerCase(); - char[] chars = lowerCase.toCharArray(); - - int left = 0, right = chars.length - 1; - - // a 97 z 97+26=125 - - while (left <= right) { - while (chars[left] < 97 || chars[left] > 125) { - left++; - } - while (chars[right] < 97 || chars[right] > 125) { - right--; - } - if (chars[left] != chars[right]) { - return false; - } - left++; - right--; - } - return true; - } - - public boolean isPalindrome2(String str) { + public boolean isPalindrome(String str) { int head = 0, tail = str.length() - 1; char a, b; while(head < tail) { diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java index 07014d3..522d1f0 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java @@ -1,5 +1,7 @@ package com.bruis.algorithminjava.algorithm.leetcode.array; +import java.util.Arrays; + /** * 最大间距 * @@ -8,6 +10,102 @@ * @author LuoHaiYang */ public class MaximumGap { + + /** + * 基于桶排序 + * 时间复杂度:O(N) + * 空间复杂度:O(N) + * @param nums + * @return + */ + public int maximumGapOptimize2(int[] nums) { + if (nums.length < 2) return 0; + int len = nums.length; + + // 找出最大值和最小值 为了方便后面确定桶的数量 + int max = -1, min = Integer.MAX_VALUE; + for (int i = 0; i < len; i++) { + max = Math.max(nums[i], max); + min = Math.min(nums[i], min); + } + + // 排除nums全部为一样的数字,nums = [1,1,1,1,1,1]; + if (max - min == 0) return 0; + // 用于存放每个桶的最大值 + int[] bucketMin = new int[len - 1]; + // 用于存放每个桶的最小值 + int[] bucketMax = new int[len - 1]; + Arrays.fill(bucketMax, -1); + Arrays.fill(bucketMin, Integer.MAX_VALUE); + + // 确定桶的间距 + int interval = (int)Math.ceil((double)(max - min) / (len - 1)); + for (int i = 0; i < len; i++) { + // 找到每一个值所对应桶的索引 + int index = (nums[i] - min) / interval; + if (nums[i] == min || nums[i] == max) continue; + // 更新每个桶的数据 + bucketMax[index] = Math.max(bucketMax[index], nums[i]); + bucketMin[index] = Math.min(bucketMin[index], nums[i]); + } + + // maxGap 表示桶之间最大的差距 + int maxGap = 0; + // preMax 表示前一个桶的最大值 + int preMax = min; + for (int i = 0; i < len - 1; i++) { + // 表示某一个桶为空 + // 但凡某一个桶不为空,都会在前面的数据中更新掉bucketMax的值 + if (bucketMax[i] == -1) continue; + maxGap = Math.max(bucketMin[i] - preMax, maxGap); + preMax = bucketMax[i]; + } + // [1,10000000] + maxGap = Math.max(maxGap, max - preMax); + return maxGap; + } + + /** + * 基数排序: + * 时间复杂度:O(N) + * 空间复杂度:O(N) + * @param nums + * @return + */ + public int maximumGapOptimize(int[] nums) { + int n = nums.length; + if (n < 2) { + return 0; + } + long exp = 1; + int[] buf = new int[n]; + int maxVal = Arrays.stream(nums).max().getAsInt(); + + while (maxVal >= exp) { + int[] cnt = new int[10]; + for (int i = 0; i < n; i++) { + int digit = (nums[i] / (int) exp) % 10; + cnt[digit]++; + } + for (int i = 1; i < 10; i++) { + cnt[i] += cnt[i - 1]; + } + for (int i = n - 1; i >= 0; i--) { + int digit = (nums[i] / (int) exp) % 10; + buf[cnt[digit] - 1] = nums[i]; + cnt[digit]--; + } + System.arraycopy(buf, 0, nums, 0, n); + exp *= 10; + } + + int ret = 0; + for (int i = 1; i < n; i++) { + ret = Math.max(ret, nums[i] - nums[i - 1]); + } + return ret; + } + public int maximumGap(int[] nums) { if (nums == null || nums.length < 2) { return 0; @@ -54,7 +152,7 @@ private void quickSort3ways(int[] nums, int left, int right) { } private int max(int i, int j) { - return i > j ? i : j; + return Math.max(i, j); } private void swap(int[] nums, int i, int j) { @@ -66,6 +164,7 @@ private void swap(int[] nums, int i, int j) { public static void main(String[] args) { int[] test = {3,6,9,1,20,15,11,30,31}; MaximumGap maximumGap = new MaximumGap(); - System.out.println(maximumGap.maximumGap(test)); +// System.out.println(maximumGap.maximumGap(test)); + System.out.println(maximumGap.maximumGapOptimize2(test)); } } diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java index b6de14f..ef5cb71 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java @@ -8,22 +8,21 @@ */ public class BinarySearch { - public static int binarySearch(int[] arr, int n, int target) { - - // 在 [left, right]范围里寻找target - int left = 0, right = n - 1; - - while (left <= right) { - int mid = (right + left) / 2; - int nums = arr[mid]; - - if (nums == target) { + // 需要排序号的数组,时间复杂度是O(logN) + public static int binarySearch(int[] arr, int target) { + // [l...r] 左右闭区间 + int l = 0, r = arr.length - 1; + // l=r也要判断,例如这种场景:{5}, l=r=0,需要判断 + while (l <= r) { + // 不要(l+r)/2,防止int越界 + int mid = l + (r-l)/2; + if (arr[mid] == target) { return mid; - } else if (nums > target) { - left = mid + 1; + } + if (arr[mid] < target) { + l = mid + 1; } else { - // nums < target - right = mid - 1; + r = mid - 1; } } return -1; diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearchFloorAndCeil.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearchFloorAndCeil.java new file mode 100644 index 0000000..e592237 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearchFloorAndCeil.java @@ -0,0 +1,65 @@ +package com.bruis.algorithminjava.algorithm.sort; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/30 11:04 + * @Description : + */ +public class BinarySearchFloorAndCeil { + /* + 功能定义: + floor(v): 返回≤v的最大元素(不存在时返回最近小值) + ceil(v): 返回≥v的最小元素(不存在时返回最近大值) + 特殊处理: 当v=42不存在时,floor返回41,ceil返回43 + + 存在多个v时,floor(v)返回v的第一个索引,ceil(v)返回v的最后一个索引 + */ + + public int floor(int[] arr, int v) { + int lowerBound = lowerBound(arr, v); + if (lowerBound < arr.length && arr[lowerBound] == v) { + return lowerBound; + } + if (lowerBound - 1 >= 0) { + return lowerBound - 1; + } + return -1; + } + + public int ceil(int[] arr, int v) { + int upperBound = upperBound(arr, v); + if (upperBound - 1 >= 0 && arr[upperBound - 1] == v) { + return upperBound - 1; + } + if (upperBound < arr.length) { + return upperBound; + } + return -1; + } + + private int lowerBound(int[] arr, int v) { + int l = 0, r = arr.length - 1; + while (l <= r) { + int mid = l + (r - l) / 2; + if (arr[mid] >= v) { + r = mid - 1; + } else { + l = mid + 1; + } + } + return l; + } + + private int upperBound(int[] arr, int v) { + int l = 0, r = arr.length - 1; + while (l <= r) { + int mid = l + (r - l) / 2; + if (arr[mid] <= v) { + l = mid + 1; + } else { + r = mid - 1; + } + } + return l; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java index 64db417..08c7be4 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java @@ -1,5 +1,6 @@ package com.bruis.algorithminjava.algorithm.sort; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -50,6 +51,7 @@ public int[] doSort(int[] arr) { public static void main(String[] args) { BucketSort bucketSort = new BucketSort(10); - bucketSort.doSort(new int[]{4,1,3,2,6,9,9}); + int[] sort = bucketSort.doSort(new int[]{4, 1, 3, 2, 20, 6, 9, 9, 21, 19}); + System.out.println(Arrays.toString(sort)); } } diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/Heap.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Heap.java new file mode 100644 index 0000000..f593c19 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Heap.java @@ -0,0 +1,122 @@ +package com.bruis.algorithminjava.algorithm.sort; + +/** + * 此堆索引从0开始 + * + * @Description + * @Author luohaiyang + * @Date 2022/4/20 + */ +public class Heap { + private int[] data; + private int count; + private int capacity; + + /** + * + * + * + * a + 1 + b c + 2 3 + d e f g + 4 5 6 7 + q w r x + 8 9 10 11 + * + * + * + * + */ + + /** + * 初始化堆 + * @param capacity + */ + public Heap(int capacity) { + this.capacity = capacity; + data = new int[capacity+1]; + count = 0; + } + + public Heap(int[] data, int capacity) { + this.data = data; + heapify(capacity); + } + + /** + * 新增一个元素 + * @param value + */ + public void insert(int value) { + if (count + 1 > capacity) { + // 抛异常 + } + data[++count] = value; + shiftUp(count); + } + + /** + * 获取堆顶值 + * @return + */ + public int extractMax() { + if (count < 1) { + // 抛异常 + } + int max = data[1]; + swap(1, count--); + shiftDown(1); + return max; + } + + /** + * 堆化 + */ + public void heapify(int k) { + while (k/2 >= 1) { + shiftDown(k/2); + k--; + } + } + + public int size() { + return count; + } + + public boolean isEmpty() { + return count == 0; + } + + /** + * 上浮操作 + * @param k + */ + private void shiftUp(int k) { + while (k > 1 && data[k] > data[k/2]) { + swap(k, k/2); + k /= 2; + } + } + + /** + * 下层操作 + * @param k + */ + private void shiftDown(int k) { + while (count >= k * 2) { + int j = k * 2; + if (j+1 <= count && data[j] < data[j+1]) j++; + if (data[k] >= data[j]) break; + swap(k, j); + k = j; + } + } + + private void swap(int a, int b) { + int tmp = data[a]; + data[a] = data[b]; + data[b] = tmp; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/Sort2026.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Sort2026.java new file mode 100644 index 0000000..c25391c --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Sort2026.java @@ -0,0 +1,334 @@ +package com.bruis.algorithminjava.algorithm.sort; + +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/3 17:31 + * @Description : + */ +public class Sort2026 { + + public static void main(String[] args) { + int[] arrays = new int[]{10,9,11,10,10,10,33,2,3,1,0,5}; + twoWayQuickSort(arrays); + System.out.println(Arrays.toString(arrays)); + } + + // 1. 选择排序,时间复杂度O(n^2) + private static void selectionSort(int[] arrays) { + for (int i = 0; i < arrays.length; i++) { + int minIndex = i; + for (int j = i + 1; j < arrays.length; j++) { + if (arrays[j] < arrays[minIndex]) { + minIndex = j; + } + } + if (minIndex != i) { + int tmp = arrays[i]; + arrays[i] = arrays[minIndex]; + arrays[minIndex] = tmp; + } + } + } + + // 2. 冒泡算法,时间复杂度O(n^2) + private static void bubbleSort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + + for (int i = 0; i < arr.length - 1; i++) { + for (int j = 0; j < arr.length - i - 1; j++) { + swap(arr, j, j + 1); + } + } + } + + /** + * a,b,c,d,e,f,g + * ^ ^ + * 普通插入排序法 + * 时间最差复杂度: O(n^2) + */ + private static void insertSort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + for (int i = 1; i < arr.length; i++) { + for (int j = i; j > 0; j--) { + if (arr[j] < arr[j-1]) { + swap(arr, j, j - 1); + } else { + break; + } + } + } + } + + /** + * 不需要每次对比都交换元素 + * a,b,c,d,e,f,g + * e + * ^ + * 时间最差复杂度: O(n^2) + */ + private static void insertSortWithoutSwap(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + for (int i = 1; i < arr.length; i++) { + int j; + int e = arr[i]; + for(j = i; j > 0 && arr[j - 1] > e; j--) { + arr[j] = arr[j-1]; + } + arr[j] = e; + } + } + + /** + * 归并排序 + * + */ + private static void mergeSort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + mergeSortInner(arr, 0, arr.length - 1); + } + + private static void mergeSortInner(int[] arr, int left, int right) { + // 越界结束merge + if (left >= right) { + return; + } + // TODO 【优化】会数组溢出 + int mid = (left + right) / 2; + // 这样数组一定不会溢出 +// int mid = left + (right - left) / 2; + // 继续拆左侧元素 + mergeSortInner(arr, left, mid); + // 继续拆右侧元素 + mergeSortInner(arr, mid + 1, right); + + // 走到此处,左右分半(左侧和右侧都是已经排好序了),mid就是左侧最大值;mid+1就是右侧最小值 + if (arr[mid] > arr[mid + 1]) { + doMergeSort(arr, left, mid, right); + } + } + + private static void doMergeSort(int[] arr, int left, int mid, int right) { + // 新开辟一个辅助数组:[left + 1 ... right] + int[] aux = new int[right - left + 1]; + for (int i = left; i <= right; i++) { + aux[i - left] = arr[i]; + } + int i = left, j = mid + 1; + for (int k = left; k <= right; k++) { + if (i > mid) { + arr[k] = aux[j - left]; + j++; + } else if (j > right) { + arr[k] = aux[i - left]; + i++; + } else if (aux[i - left] < aux[j - left]) { // 【TODO】优化:相等时永远左边优先,这样排序会稳定得多 aux[i - left] <= aux[j - left + arr[k] = aux[i - left]; + i++; + } else { + arr[k] = aux[j - left]; + j++; + } + } + } + + /** + * 自底向上的归并排序算法 + * + */ + private static void mergeSortByBottom(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + int n = arr.length; + // size 表示当前要归并的子数组长度:1, 2, 4, 8... => size就是数组大小,每次要merge左侧也右侧的数组[size, size],这么好理解一点 + // 每轮merge的数组长度翻倍 + for (int size = 1; size < n; size += size) { + // 跳到下一对待merge数组 + // left + size < n,保证右侧总有数据可以merge => left + size 得到的就是右侧数组的第一个元素(mid+1),这个元素(mid+1)必须得有才能进行merge + for (int left = 0; left < n - size; left += size + size) { + int mid = left + size - 1; // => mid = left + size - 1,所以上面left + size = mid + 1 < n + // for的结束条件只能保证mid + 1 有值,但是不保证right边界不越界,所以得和n-1进行最小值比较 + int right = Math.min(left + size + size - 1, n - 1); + + if (arr[mid] > arr[mid + 1]) { + doMergeSort(arr, left, mid, right); + } + } + } + } + + // ============================ 普通快排 ============================ + + private void quickSort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + doQuickSort(arr, 0, arr.length - 1); + } + + private void doQuickSort(int[] arr, int left, int right) { + if (left >= right) { + return; + } + int p = partition(arr, left, right); + doQuickSort(arr, left, p - 1); + doQuickSort(arr, p + 1, right); + } + + private int partition(int[] arr, int left, int right) { + int p = arr[left]; + int j = left; + // arr[left] arr[left+1...j] < p, p < arr[j+1...i]; + for (int i = left + 1; i <= right; i++) { + if (arr[i] < p) { + swap(arr, ++j, i); + } + } + swap(arr, left, j); + return j; + } + + /** + * 取随机数,尽量将有序的数组打散 + * 生成[a,b]之间随机数,公式为:a + new Random().nextInt(b - a + 1) + */ + private int partitionWithPivot(int[] arr, int left, int right) { + int pivot = left + new Random().nextInt(right - left + 1); + swap(arr, left, pivot); + int p = arr[left]; + int j = left; + for (int i = left + 1; i <= right; i++) { + if (arr[i] < p) { + swap(arr, ++j, i); + } + } + swap(arr, left, j); + return j; + } + + // 问题分析:如果是对接近有序的数组进行排序,普通快排最坏情况下会退化成O(N^2) + // ============================ 普通快排 ============================ + + // ============================ 2路快排 ============================ + private static void twoWayQuickSort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + doTwoWayQuickSort(arr, 0, arr.length - 1); + } + + private static void doTwoWayQuickSort(int[] arr, int left, int right) { + if (left >= right) { + return; + } + int p = twoWayPartition(arr, left, right); + doTwoWayQuickSort(arr, left, p - 1); + doTwoWayQuickSort(arr, p + 1, right); + } + + /** + * 循环过程中: + * arr[left + 1...i - 1] <= p + * arr[j + 1...right] >= p + *

+ * 最后 swap(arr, left, j) 后: + * arr[left...j - 1] <= p + * arr[j] == p + * arr[j + 1...right] >= p + */ + private static int twoWayPartition(int[] arr, int left, int right) { + // 加随机数,避免partition极度不均匀 + int pivot = left + new Random().nextInt(right - left + 1); + swap(arr, left, pivot); + int p = arr[left], i = left + 1, j = right; + while(true) { + while(i <= right && arr[i] < p) { + i++; + } + // p = arr[left]了,没必要比较j 和 left + while(j >= left + 1 && arr[j] > p) { + j--; + } + if (i > j) break; + swap(arr, i++, j--); + } + swap(arr, left, j); + return j; + } + // ============================ 2路快排 ============================ + + // ============================ 3路快排 ============================ + private void threeWayQuickSort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + doThreeWayQuickSort(arr, 0, arr.length - 1); + } + + + /** + * 1) arr[l+1...lt] < p(arr[l]) + * 2) arr[lt+1...i-1] = p(arr[l]) + * 3) arr[gt...r] > p(arr[l]) + *

+ * [a, b, c, d, e, f, g, h, i, j, k] + * l + * lt + * i + * gt r + * while循环结束 + * [a, b, c, d, e, f, g, h, i, j, k] + * l + * lt + * i + * gt r + */ + private void doThreeWayQuickSort(int[] arr, int left, int right) { + if (left >= right) return; + // 不要每次递归都new Random() +// int pivot = left + new Random().nextInt(right - left + 1); + int pivot = ThreadLocalRandom.current().nextInt(left, right + 1); + swap(arr, pivot, left); + int p = arr[left], lt = left, i = left + 1, gt = right + 1; + while (i < gt) { + if (arr[i] < p) { + // lt区域扩大,i得递增 + swap(arr, ++lt, i++); + } else if (arr[i] > p) { + // gt区域扩大 + swap(arr, --gt, i); + } else { + i++; + } + } + swap(arr, left, lt); + // lt -> gt - 1 区间都是相等的值了 + doThreeWayQuickSort(arr, left, lt - 1); + doThreeWayQuickSort(arr, gt, right); + } + // ============================ 3路快排 ============================ + + // TODO 归并排序变种题==> 查找逆序对 + + // TODO 快速排序变种题==> 查找第n位数 + + + public static void swap(int[] arr, int sourceIndex, int targetIndex) { + int temp = arr[sourceIndex]; + arr[sourceIndex] = arr[targetIndex]; + arr[targetIndex] = temp; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/Test.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Test.java new file mode 100644 index 0000000..dc17c86 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Test.java @@ -0,0 +1,48 @@ +package com.bruis.algorithminjava.algorithm.sort; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/18 12:32 + * @Description : + */ +public class Test { + public static void main(String[] args) { + + } + + private void quickSort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; + } + doQuickSort(arr, 0, arr.length - 1); + } + + // arr[l+1...i-1] < v, arr[j...r] > v + // a, b, f, d, e, c, g, h + // i + // j + private void doQuickSort(int[] arr, int l, int r) { + if (l >= r) return; + int p = partition(arr, l, r); + doQuickSort(arr, l, p -1); + doQuickSort(arr, p + 1, r); + } + + private int partition(int[] arr, int l, int r) { + int v = arr[l], i = l + 1, j = r; + while (true) { + while (i <= r && arr[i] < v) i++; + while (j >= l + 1 && arr[j] > v) j--; + if (i > j) break; + swap(arr, i++, j--); + } + swap(arr, l, j); + return j; + } + + private void swap(int[] arr, int a, int b) { + int tmp = arr[a]; + arr[a] = arr[b]; + arr[b] = tmp; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapPrinter.java b/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapPrinter.java new file mode 100644 index 0000000..dee8d54 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapPrinter.java @@ -0,0 +1,50 @@ +package com.bruis.algorithminjava.datastructures.heap; + +/** + * 打印二叉堆 + * @Author : haiyang.luo + * @Date : 2026/6/20 21:23 + * @Description : + */ +public class HeapPrinter { + + public static void printHeap(int[] heap) { + if (heap == null || heap.length == 0) { + System.out.println("(empty)"); + return; + } + + int n = heap.length; + int height = (int) (Math.log(n) / Math.log(2)) + 1; + int maxWidth = (int) Math.pow(2, height) * 4; + + int index = 0; + + for (int level = 0; level < height; level++) { + int levelCount = (int) Math.pow(2, level); + int spaces = maxWidth / (int) Math.pow(2, level + 1); + + printSpaces(spaces); + + for (int i = 0; i < levelCount && index < n; i++) { + System.out.printf("%2d", heap[index++]); + printSpaces(spaces * 2 - 2); + } + + System.out.println(); + System.out.println(); + } + } + + private static void printSpaces(int count) { + for (int i = 0; i < count; i++) { + System.out.print(" "); + } + } + + public static void main(String[] args) { + int[] heap = {100, 10, 12, 60, 70, 50, 40, 10, 20, 30}; + + printHeap(heap); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapSort.java b/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapSort.java new file mode 100644 index 0000000..661c027 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapSort.java @@ -0,0 +1,47 @@ +package com.bruis.algorithminjava.datastructures.heap; + +import java.util.Random; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/25 23:30 + * @Description : + */ +public class HeapSort { + + // 随机数组大小 + private static final int DEFAULT_RANDOM_ARRAY_SIZE = 10; + // 随机范围 + private static final int DEFAULT_RANDOM_BOUND = 100; + + private int[] data; + + public HeapSort() {} + + public static int[] generateRandomIntArray() { + int[] array = new int[DEFAULT_RANDOM_ARRAY_SIZE]; + Random random = new Random(); + for (int i = 0; i < array.length; i++) { + array[i] = random.nextInt(DEFAULT_RANDOM_BOUND); + } + return array; + } + + // 时间复杂度o(nlogn) + public static void heapSort(int[] arr) { + MaxHeapHeapify maxHeapHeapify = new MaxHeapHeapify(arr); + for (int i = arr.length - 1; i >= 0; i--) { + arr[i] = maxHeapHeapify.extractMax(); + } + } + + public static void main(String[] args) { + int[] randomArray = generateRandomIntArray(); + heapSort(randomArray); + for (int i = 1; i < randomArray.length; i++) { + if (randomArray[i] < randomArray[i-1]) { + System.out.println("false"); + } + } + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapSortInPlace.java b/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapSortInPlace.java new file mode 100644 index 0000000..cf3ca81 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/heap/HeapSortInPlace.java @@ -0,0 +1,10 @@ +package com.bruis.algorithminjava.datastructures.heap; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/25 23:42 + * @Description : + */ +public class HeapSortInPlace { + +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/heap/IndexHeap.java b/src/main/java/com/bruis/algorithminjava/datastructures/heap/IndexHeap.java new file mode 100644 index 0000000..625cd0f --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/heap/IndexHeap.java @@ -0,0 +1,198 @@ +package com.bruis.algorithminjava.datastructures.heap; + +import java.util.Arrays; + +/** + * 索引堆里,只有indexes是堆化了的,而data和reverse数组都是普通的数组。 + * + * @Author : haiyang.luo + * @Date : 2026/6/26 00:15 + * @Description : + */ +public class IndexHeap { + + /** + * 真实数据。对外使用0开始的索引,内部转换为1开始的索引。 + */ + private int[] data; + // indexes进行了堆化!! data的索引,indexes[heapIndex]=dataIndex, data[dataIndex]=value + private int[] indexes; + // 索引数组的反向,reverse[dataIndex]=heapIndex + private int[] reverse; + private int count; + private int capacity; + + public IndexHeap(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("capacity must be greater than 0."); + } + this.capacity = capacity; + this.data = new int[capacity + 1]; + this.indexes = new int[capacity + 1]; + this.reverse = new int[capacity + 1]; + this.count = 0; + } + + public boolean isEmpty() { + return count == 0; + } + + public int size() { + return count; + } + + public boolean contains(int i) { + checkIndex(i); + return reverse[i + 1] != 0; + } + + public void insert(int i, int item) { + checkIndex(i); + if (count + 1 > capacity) { + throw new IllegalStateException("out of the capacity."); + } + if (contains(i)) { + throw new IllegalArgumentException("index already exists in heap."); + } + + int dataIndex = i + 1; + data[dataIndex] = item; + indexes[++count] = dataIndex; + reverse[dataIndex] = count; + shiftUp(count); + } + + public int getItem(int i) { + if (!contains(i)) { + throw new IllegalArgumentException("index does not exist in heap."); + } + return data[i + 1]; + } + + public int getMax() { + checkNotEmpty(); + return data[indexes[1]]; + } + + public int getMaxIndex() { + checkNotEmpty(); + return indexes[1] - 1; + } + + public int extractMax() { + checkNotEmpty(); + + int ret = data[indexes[1]]; + swapIndexes(1, count); + reverse[indexes[count]] = 0; + count--; + shiftDown(1); + return ret; + } + + /** + * 删除堆中最大值,并返回该最大值对应的外部原始索引。 + * 外部用户索引从0开始,索引堆内部索引从1开始。 + * + * @return 最大值对应的外部原始索引 + */ + public int extractMaxDataIndex() { + checkNotEmpty(); + + int ret = indexes[1] - 1; + swapIndexes(1, count); + reverse[indexes[count]] = 0; + count--; + shiftDown(1); + return ret; + } + + public void change(int i, int item) { + if (!contains(i)) { + throw new IllegalArgumentException("index does not exist in heap."); + } + + int dataIndex = i + 1; + data[dataIndex] = item; + int heapIndex = reverse[dataIndex]; + shiftUp(heapIndex); + // 要调用reverse重新取一遍heapIndex,在shiftUp中reverse变了 + heapIndex = reverse[dataIndex]; + // 为什么上浮了之后,还要下沉?因为 change(i, item) 可能是把值改大,也可能是把值改小。先试着上浮 + // 再从当前位置试着下沉,这样无论值变大还是变小,都能恢复堆性质。 + shiftDown(heapIndex); + } + + public int[] toArray() { + int[] arr = new int[count]; + for (int i = 0; i < count; i++) { + arr[i] = data[indexes[i + 1]]; + } + return arr; + } + + private void shiftUp(int k) { + while (k > 1 && data[indexes[k / 2]] < data[indexes[k]]) { + swapIndexes(k, k / 2); + k /= 2; + } + } + + private void shiftDown(int k) { + while (2 * k <= count) { + int j = 2 * k; + if (j + 1 <= count && data[indexes[j + 1]] > data[indexes[j]]) { + j++; + } + if (data[indexes[k]] >= data[indexes[j]]) { + break; + } + swapIndexes(k, j); + k = j; + } + } + + private void swapIndexes(int i, int j) { + int tmp = indexes[i]; + indexes[i] = indexes[j]; + indexes[j] = tmp; + + reverse[indexes[i]] = i; + reverse[indexes[j]] = j; + } + + private void checkIndex(int i) { + if (i < 0 || i >= capacity) { + throw new IllegalArgumentException("index is out of range."); + } + } + + private void checkNotEmpty() { + if (isEmpty()) { + throw new IllegalStateException("heap is empty."); + } + } + + public static void demoChangeByOriginalIndex() { + int[] scores = {62, 88, 71, 45, 93, 56}; + IndexHeap indexHeap = new IndexHeap(scores.length); + for (int i = 0; i < scores.length; i++) { + indexHeap.insert(i, scores[i]); + } + + System.out.println("原始数据: " + Arrays.toString(scores)); + System.out.println("当前最大值: " + indexHeap.getMax() + ", 外部索引: " + indexHeap.getMaxIndex()); + + indexHeap.change(3, 99); + System.out.println("把外部索引3的值改成99后:"); + System.out.println("当前最大值: " + indexHeap.getMax() + ", 外部索引: " + indexHeap.getMaxIndex()); + + indexHeap.change(3, 40); + System.out.println("再把外部索引3的值改成40后:"); + System.out.println("当前最大值: " + indexHeap.getMax() + ", 外部索引: " + indexHeap.getMaxIndex()); + } + + public static void main(String[] args) { + demoChangeByOriginalIndex(); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeap.java b/src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeap.java index 3fee523..c0f18e3 100644 --- a/src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeap.java +++ b/src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeap.java @@ -1,204 +1,61 @@ package com.bruis.algorithminjava.datastructures.heap; -import java.util.Random; - /** - * @author LuoHaiYang + * @Author : haiyang.luo + * @Date : 2026/6/25 19:16 + * @Description : */ public class MaxHeap { - /** - * 注意,由于数组元素默认值为0,所以堆中元素不可为0 - */ private int[] data; - - /** - * 记录数组中元素个数 - */ - private int size; - - /** - * 数组容量(初始化大小) - */ + // 堆中的元素 + private int count; + // 最大堆容量 private int capacity; + public MaxHeap() {} + public MaxHeap(int capacity) { - data = new int[capacity]; + data = new int[capacity + 1]; + count = 0; this.capacity = capacity; - size = 0; - } - public MaxHeap() { - data = new int[10]; - capacity = 10; - size = 0; - } - - // ================================ heapify(二叉堆化) ======================== - - public MaxHeap(int[] data) { - int n = data.length; - this.data = new int[n+1]; - capacity = n; - - for (int i = 0; i < n; i++) { - this.data[i+1] = data[i]; - } - size = n; - - // 这里需要注意,size / 2 得到的索引是二叉堆中最后一个非叶子节点。 - // 注意!!! 这里size / 2 是因为 data 从1开始的,所以最后一个非叶子节点为:size / 2 - // 如果是从0开始,则:size - 1 - for (int i = size / 2; i > 0; i++) { - siftDown(i); - } - } - - /** - * 返回堆中真实存在元素个数 - * @return - */ - public int size() { - return size; - } - - /** - * 返回堆中是否为空 - * @return - */ - public boolean isEmpty() { - return size == 0; } - /** - * 返回index索引其父亲节点 - * @param index - * @return - */ - private int parent(int index) { - if (index == 0) { - throw new IllegalArgumentException("index-0 doesn't have parent"); + private void shiftUp(int a) { + while (a > 1 && data[a] > data[a/2]) { + swap(a, a/2); + a /= 2; } - return (index - 1) / 2; } - /** - * 返回index索引的左子节点 - * @param index - * @return - */ - private int leftChild(int index) { - return index * 2 + 1; - } - - /** - * 返回index索引的右子节点 - * @param index - * @return - */ - private int rightChild(int index) { - return index * 2 + 2; - } - - // ================================ 上浮 siftUp ======================== - - /** - * 向堆中添加元素 - */ - public void add(int i) { - // 注意size和capacity的关系,判断是否容量已经满了 - // 向数组最后一位添加新元素 - data[size] = i; - siftUp(size++); - } - - /** - * 上浮过程 - * @param k - */ - private void siftUp(int k) { - // k 索引大于0,并且k索引元素值大于k父亲节点元素值 - while (k > 0 && data[parent(k)] < data[k]) { - // k和parent(k)互换元素 - swap(data, k, parent(k)); - // 向上移动,让k为parent(k)再进行判断 - k = parent(k); - } - } - - // ================================ 下浮 siftDown ======================== - - /** - * 获取堆中最大元素 - * @return - */ - public int findMax() { - if (size == 0) { - throw new IllegalArgumentException("Can't find Max value!"); + public void insert(int value) { + if (count + 1 > capacity) { + throw new IllegalStateException("out of the capacity."); } - return data[0]; + data[++count] = value; + shiftUp(count); } - /** - * 取出堆中最大元素 - * @return - */ - public int extractMax() { - int max = findMax(); - swap(data, 0, size - 1); - data[size - 1] = 0; - siftDown(0); - return max; + private void swap(int a, int b) { + int tmp = data[a]; + data[a] = data[b]; + data[b] = tmp; } - /** - * 下沉操作 - * @param k - */ - private void siftDown(int k) { - // 左子节点比数组元素小,则表示有子节点 - while (leftChild(k) < size()) { - int j = leftChild(k); - // 如果k的有右子节点 - if (j + 1 < size() && data[j + 1] > data[j]) { - // 所以让j为右子节点 - // j = rightChild(k); 同 - j++; - } - // 此时data[k] 比leftChild和rightChild中的最大值都要大 - if (data[k] > data[j]) { - break; - } - // leftChild和rightChild都比data[k]大,在互换之后,继续下一轮判断 - swap(data, k, j); - // 互换位置,继续下一轮判断 - k = j; + public int[] toArray() { + int[] arr = new int[count]; + for (int i = 0; i < count; i++) { + arr[i] = data[i + 1]; } - } - - // ================================ 替换操作 ======================== - - private void swap(int[] arr, int i, int j) { - int tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; + return arr; } public static void main(String[] args) { - int n = 100; - MaxHeap maxHeap = new MaxHeap(n); - Random random = new Random(); - for (int i = 0; i < n; i++) { - maxHeap.add(random.nextInt(1000)); - } - int[] result = new int[n]; - for (int i = 0; i < n; i++) { - result[i] = maxHeap.extractMax(); - } - // 测试是否是顺序的 - for (int j = 1; j < n; j++) { - if (result[j-1] < result[j]) { - throw new IllegalArgumentException("Error!"); - } + int[] heap = {100, 10, 12, 60, 70, 50, 40, 10, 20, 30}; + MaxHeap maxHeap = new MaxHeap(heap.length); + for (int i = 0; i < heap.length; i++) { + maxHeap.insert(heap[i]); } + HeapPrinter.printHeap(maxHeap.toArray()); } } diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeapHeapify.java b/src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeapHeapify.java new file mode 100644 index 0000000..1178b1b --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeapHeapify.java @@ -0,0 +1,76 @@ +package com.bruis.algorithminjava.datastructures.heap; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/20 21:00 + * @Description : + */ +public class MaxHeapHeapify { + + private int[] data; + private int count; + + public MaxHeapHeapify() {} + + public MaxHeapHeapify(int[] arr) { + data = new int[arr.length + 1]; + count = arr.length; + for (int i = 0; i < count; i++) { + data[i+1] = arr[i]; + } + heapify(); + } + + public int extractMax() { + if (count == 0) { + throw new IllegalStateException("count == 0"); + } + int max = data[1]; + swap(1, count); + count--; + shiftDown(1); + return max; + } + + + + private void heapify() { + for (int i = count/2; i >= 1; i--) { + shiftDown(i); + } + } + + // 将指定参数位置进行下层操作 + private void shiftDown(int a) { + // 探讨下,2 * a <= count有啥差别 + while (2 * a <= count) { + int b = 2 * a; + if (2 * a + 1 <= count && data[b] < data[b+1]) { + b += 1; + } + if (data[b] <= data[a]) break; + swap(a, b); + a = b; + } + } + + private void swap(int a, int b) { + int tmp = data[a]; + data[a] = data[b]; + data[b] = tmp; + } + + public int[] toArray() { + int[] arr = new int[count]; + for (int i = 0; i < count; i++) { + arr[i] = data[i + 1]; + } + return arr; + } + + public static void main(String[] args) { + int[] heap = {100, 10, 12, 60, 70, 50, 40, 10, 20, 30}; + MaxHeapHeapify maxHeap = new MaxHeapHeapify(heap); + HeapPrinter.printHeap(maxHeap.toArray()); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/list/HashMapLRUCache.java b/src/main/java/com/bruis/algorithminjava/datastructures/list/HashMapLRUCache.java new file mode 100644 index 0000000..a653c9d --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/list/HashMapLRUCache.java @@ -0,0 +1,181 @@ +package com.bruis.algorithminjava.datastructures.list; + +import java.util.HashMap; +import java.util.Map; + +/** + * 基于 HashMap + 双向链表实现的 LRU 缓存。 + * + * HashMap 用于 O(1) 定位节点,双向链表用于 O(1) 刷新访问顺序和淘汰尾部节点。 + * + * @Author : haiyang.luo + * @Date : 2026/6/30 10:30 + * @Description : + */ +public class HashMapLRUCache { + + private final int capacity; + private final Map> map; + private final Node dummyHead; + private final Node dummyTail; + + public HashMapLRUCache(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be greater than 0."); + } + this.capacity = capacity; + this.map = new HashMap<>(capacity); + this.dummyHead = new Node<>(); + this.dummyTail = new Node<>(); + dummyHead.next = dummyTail; + dummyTail.prev = dummyHead; + } + + public int getSize() { + return map.size(); + } + + public int getCapacity() { + return capacity; + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public boolean containsKey(K key) { + return map.containsKey(key); + } + + public V get(K key) { + Node node = map.get(key); + if (node == null) { + return null; + } + + moveToFirst(node); + return node.value; + } + + public void put(K key, V value) { + Node node = map.get(key); + if (node != null) { + node.value = value; + moveToFirst(node); + return; + } + + if (map.size() == capacity) { + Node removedNode = removeLast(); + map.remove(removedNode.key); + } + + Node newNode = new Node<>(key, value); + addFirst(newNode); + map.put(key, newNode); + } + + public V remove(K key) { + Node node = map.remove(key); + if (node == null) { + return null; + } + + removeNode(node); + return node.value; + } + + private void moveToFirst(Node node) { + removeNode(node); + addFirst(node); + } + + /* + dummyHead <-> node <-> A <-> B + + node.prev = dummyHead 相当于 dummyHead <- node + node.next = dummyHead.next 相当于 node -> A + 这样,链表就成了:dummyHead <- node -> A + + dummyHead.next.prev = node 相当于 node <- A + dummyHead.next = node 相当于 dummyHead -> node + 这样,链表就成了:dummyHead -> node <- A + + 最终上面四步合在一起就是:dummyHead <-> node <-> A + */ + private void addFirst(Node node) { + node.prev = dummyHead; + node.next = dummyHead.next; + dummyHead.next.prev = node; + dummyHead.next = node; + } + + /* + A <-> node <-> B + + node.prev.next = node.next 相当于:A -> B,断掉了 node -> B + node.next.prev = node.prev 相当于:A <- B,断掉了 A <- node + + node.prev = null + node.next = null + + 最终结果:A <-> B + */ + private void removeNode(Node node) { + node.prev.next = node.next; + node.next.prev = node.prev; + node.prev = null; + node.next = null; + } + + private Node removeLast() { + Node lastNode = dummyTail.prev; + removeNode(lastNode); + return lastNode; + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + Node cur = dummyHead.next; + while (cur != dummyTail) { + res.append(cur.key).append("=").append(cur.value).append(" -> "); + cur = cur.next; + } + res.append("NULL"); + return res.toString(); + } + + public static void main(String[] args) { + HashMapLRUCache lruCache = new HashMapLRUCache<>(3); + + lruCache.put(1, "A"); + lruCache.put(2, "B"); + lruCache.put(3, "C"); + System.out.println(lruCache); + + lruCache.get(1); + System.out.println(lruCache); + + lruCache.put(4, "D"); + System.out.println(lruCache); + + lruCache.put(1, "A1"); + System.out.println(lruCache); + } + + private static class Node { + private K key; + private V value; + private Node prev; + private Node next; + + public Node() { + } + + public Node(K key, V value) { + this.key = key; + this.value = value; + } + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedList.java b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedList.java new file mode 100644 index 0000000..5be78af --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedList.java @@ -0,0 +1,191 @@ +package com.bruis.algorithminjava.datastructures.list; + +import java.util.Objects; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/26 16:06 + * @Description : + */ +public class MyLinkedList { + + private Node head; + private int size; + + public MyLinkedList() { + head = null; + size = 0; + } + + // 往链表头添加节点 + public void addFirst(E e) { + head = new Node(e, head); + size++; + } + + // a->b->c->d->e + // 0 1 2 3 4 + // ^ + public void add(int index, E e) { + if (index < 0 || index > size) { + throw new IllegalArgumentException("Add failed. Illegal index."); + } + + if (index == 0) { + addFirst(e); + } else { + Node prev = head; + for (int i = 0; i < index - 1; i++) { + prev = prev.next; + } + Node node = new Node(e); + // node -> c + node.next = prev.next; + // b -> node => b -> node -> c + prev.next = node; +// prev.next = new Node(e, prev.next); + size++; + } + } + + // 往链表尾部添加节点 + public void addLast(E e) { + add(size, e); + } + + public E get(int index) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("Get failed. Illegal index."); + } + + Node cur = head; + for (int i = 0; i < index; i++) { + cur = cur.next; + } + return cur.e; + } + + public E getFirst() { + return get(0); + } + + public E getLast() { + return get(size - 1); + } + + public boolean contains(E e) { + Node cur = head; + while (cur != null) { + if (Objects.equals(cur.e, e)) { + return true; + } + cur = cur.next; + } + return false; + } + + public void set(int index, E e) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("Set failed. Illegal index."); + } + + Node cur = head; + for (int i = 0; i < index; i++) { + cur = cur.next; + } + cur.e = e; + } + + public E remove(int index) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("Remove failed. Illegal index."); + } + + Node delNode; + if (index == 0) { + delNode = head; + head = head.next; + } else { + Node prev = head; + for (int i = 0; i < index - 1; i++) { + prev = prev.next; + } + delNode = prev.next; + prev.next = delNode.next; + } + + delNode.next = null; + size--; + return delNode.e; + } + + public E removeFirst() { + return remove(0); + } + + public E removeLast() { + return remove(size - 1); + } + + public void removeElement(E e) { + if (isEmpty()) { + return; + } + + if (Objects.equals(head.e, e)) { + removeFirst(); + return; + } + + Node prev = head; + while (prev.next != null) { + if (Objects.equals(prev.next.e, e)) { + Node delNode = prev.next; + prev.next = delNode.next; + delNode.next = null; + size--; + return; + } + prev = prev.next; + } + } + + public int getSize() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + Node cur = head; + while (cur != null) { + res.append(cur.e).append(" -> "); + cur = cur.next; + } + res.append("NULL"); + return res.toString(); + } + + private class Node { + + public E e; + public Node next; + + public Node(E e, Node next) { + this.e = e; + this.next = next; + } + + public Node(E e) { + this(e, null); + } + + public Node() { + this(null, null); + } + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListLRUCache.java b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListLRUCache.java new file mode 100644 index 0000000..1de92d3 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListLRUCache.java @@ -0,0 +1,140 @@ +package com.bruis.algorithminjava.datastructures.list; + +import java.util.Objects; + +/** + * 基于 MyLinkedList 实现的 LRU 缓存。淘汰最久未使用的数据。 + * + * 链表头部保存最近使用的元素,链表尾部保存最久未使用的元素。 + * + * @Author : haiyang.luo + * @Date : 2026/6/30 10:00 + * @Description : + */ +public class MyLinkedListLRUCache { + + private final int capacity; + private final MyLinkedList> cache; + + public MyLinkedListLRUCache(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be greater than 0."); + } + this.capacity = capacity; + this.cache = new MyLinkedList<>(); + } + + public int getSize() { + return cache.getSize(); + } + + public int getCapacity() { + return capacity; + } + + public boolean isEmpty() { + return cache.isEmpty(); + } + + public boolean containsKey(K key) { + return findIndex(key) != -1; + } + + public V get(K key) { + int index = findIndex(key); + if (index == -1) { + return null; + } + + CacheEntry entry = moveToFirst(index); + return entry.getValue(); + } + + public void put(K key, V value) { + int index = findIndex(key); + if (index != -1) { + CacheEntry entry = cache.remove(index); + entry.setValue(value); + cache.addFirst(entry); + return; + } + + if (cache.getSize() == capacity) { + cache.removeLast(); + } + cache.addFirst(new CacheEntry<>(key, value)); + } + + public V remove(K key) { + int index = findIndex(key); + if (index == -1) { + return null; + } + return cache.remove(index).getValue(); + } + + private CacheEntry moveToFirst(int index) { + CacheEntry entry = cache.remove(index); + cache.addFirst(entry); + return entry; + } + + private int findIndex(K key) { + for (int i = 0; i < cache.getSize(); i++) { + if (Objects.equals(cache.get(i).getKey(), key)) { + return i; + } + } + return -1; + } + + @Override + public String toString() { + return cache.toString(); + } + + public static void main(String[] args) { + MyLinkedListLRUCache lruCache = new MyLinkedListLRUCache<>(3); + + lruCache.put(1, "A"); + lruCache.put(2, "B"); + lruCache.put(3, "C"); + System.out.println(lruCache); + + lruCache.get(1); + System.out.println(lruCache); + + lruCache.put(4, "D"); + System.out.println(lruCache); + + lruCache.put(1, "A1"); + System.out.println(lruCache); + } + + private static class CacheEntry { + private final K key; + private V value; + + public CacheEntry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public void setValue(V value) { + this.value = value; + } + + @Override + public String toString() { + return key + "=" + value; + } + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListQueue.java b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListQueue.java new file mode 100644 index 0000000..1a1c147 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListQueue.java @@ -0,0 +1,104 @@ +package com.bruis.algorithminjava.datastructures.list; + +import com.bruis.algorithminjava.datastructures.queue.Queue; + +/** + * 队列特性:先进先出,FIFO + * + * @Author : haiyang.luo + * @Date : 2026/6/26 20:58 + * @Description : + */ +public class MyLinkedListQueue implements Queue { + + private Node head, tail; + private int size; + + public MyLinkedListQueue() { + head = null; + tail = null; + size = 0; + } + + @Override + public int getSize() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public void enqueue(E e) { + if (null == tail) { + tail = new Node(e); + head = tail; + } else { + // head -> node + // ^ + // (tail) + + // head -> node -> node2 + // ^ + // (tail) + tail.next = new Node(e); + tail = tail.next; + } + size++; + } + + @Override + public E dequeue() { + if (isEmpty()) { + throw new IllegalArgumentException("Cannot dequeue from an empty queue."); + } + Node delNode = head; + head = head.next; + delNode.next = null; + if (head == null) { + tail = null; + } + size--; + return delNode.getE(); + } + + @Override + public E getFront() { + if(isEmpty()) { + throw new IllegalArgumentException("Queue is empty."); + } + return head.getE(); + } + + + + + + private class Node { + public E e; + public Node next; + + public Node() { + this(null, null); + } + + public Node(E e) { + this(e, null); + } + + public Node(E e, Node next) { + this.e = e; + this.next = next; + } + + public E getE() { + return e; + } + + public void setE(E e) { + this.e = e; + } + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListStack.java b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListStack.java new file mode 100644 index 0000000..cd782e3 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListStack.java @@ -0,0 +1,45 @@ +package com.bruis.algorithminjava.datastructures.list; + +import com.bruis.algorithminjava.datastructures.stack.Stack; + +/** + * + * 栈:先进后出,LIFO,Last In First Out + * + * @Author : haiyang.luo + * @Date : 2026/6/26 19:41 + * @Description : + */ +public class MyLinkedListStack implements Stack { + + private MyLinkedListWithDummyHead list; + + public MyLinkedListStack() { + list = new MyLinkedListWithDummyHead<>(); + } + + @Override + public int getSize() { + return list.getSize(); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + @Override + public void push(E e) { + list.addFirst(e); + } + + @Override + public E pop() { + return list.removeFirst(); + } + + @Override + public E peek() { + return list.getFirst(); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListWithDummyHead.java b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListWithDummyHead.java new file mode 100644 index 0000000..b357324 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/list/MyLinkedListWithDummyHead.java @@ -0,0 +1,204 @@ +package com.bruis.algorithminjava.datastructures.list; + +import java.util.Objects; + +/** + * @Author : haiyang.luo + * @Date : 2026/6/26 16:28 + * @Description : + */ +public class MyLinkedListWithDummyHead { + + private Node dummyHead; + private int size; + + public MyLinkedListWithDummyHead() { + dummyHead = new Node(); + size = 0; + } + + // ========================= 获取 ========================= + + public int getSize() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public boolean contains(E e) { + Node cur = dummyHead.next; + while (null != cur) { + if (Objects.equals(cur.getE(), e)) { + return true; + } + cur = cur.next; + } + return false; + } + + public E getFirst() { + return get(0); + } + + public E getLast() { + return get(size - 1); + } + + public E get(int index) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("Get failed. Illegal index."); + } + // 需要注意,从索引第一位开始遍历,所以需要:dummyHead.next + Node cur = dummyHead.next; + for (int i = 0; i < index; i++) { + cur = cur.next; + } + return cur.e; + } + + // ========================= 获取 ========================= + + // ========================= 添加 ========================= + + public void add(int index, E e) { + if (index < 0 || index > size) { + throw new IllegalArgumentException("Add failed. Illegal index."); + } + + Node prev = dummyHead; + for (int i = 0; i < index; i++) { + prev = prev.next; + } + + prev.next = new Node(e, prev.next); + size++; + } + + public void addFirst(E e) { + add(0, e); + } + + public void addLast(E e) { + add(size, e); + } + + // ========================= 添加 ========================= + + // ========================= 修改 ========================= + + public void set(E e, int index) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("Add failed. Illegal index."); + } + Node cur = dummyHead.next; + for (int i = 0; i < index; i++) { + cur = cur.next; + } + cur.setE(e); + } + + // ========================= 修改 ========================= + + // ========================= 删除 ========================= + + public E removeElement(int index) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("Remove failed. Illegal index."); + } + Node prev = dummyHead; + for (int i = 0; i < index; i++) { + prev = prev.next; + } + + Node delNode = prev.next; + prev.next = delNode.next; + delNode.next = null; + size--; + return delNode.getE(); + } + + public void removeElement(E e) { + Node prev = dummyHead; + while (null != prev.next) { + if (Objects.equals(prev.next.getE(), e)) { + break; + } + prev = prev.next; + } + if (prev.next == null) { + return; + } + Node delNode = prev.next; + prev.next = delNode.next; + delNode.next = null; + size--; + } + + public E removeFirst() { + return removeElement(0); + } + + public E removeLast() { + return removeElement(size - 1); + } + + // ========================= 删除 ========================= + + public void printList() { + System.out.println(this); + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + Node cur = dummyHead.next; + while (cur != null) { + res.append(cur.e).append(" -> "); + cur = cur.next; + } + res.append("NULL"); + return res.toString(); + } + + public static void main(String[] args) { + MyLinkedListWithDummyHead linkedList = new MyLinkedListWithDummyHead(); + Integer[] values = {23, 14, 7, 68, 45, 91, 32, 10, 56, 80}; + + for (Integer value : values) { + linkedList.addLast(value); + } + + System.out.println(linkedList.get(9)); + linkedList.set(99, 9); + + linkedList.printList(); + } + + private class Node { + public E e; + public Node next; + + public Node() { + this(null, null); + } + + public Node(E e) { + this(e, null); + } + + public Node(E e, Node next) { + this.e = e; + this.next = next; + } + + public void setE(E e) { + this.e = e; + } + + public E getE() { + return e; + } + } +}