1. 排序

2. 选择排序

  1. public static void selectionSort(int[] arr) {
  2. if (arr == null || arr.length < 2) {
  3. return;
  4. }
  5. // 0 ~ N-1 找到最小值,在哪,放到0位置上
  6. // 1 ~ n-1 找到最小值,在哪,放到1 位置上
  7. // 2 ~ n-1 找到最小值,在哪,放到2 位置上
  8. for (int i = 0; i < arr.length - 1; i++) {
  9. int minIndex = i;
  10. for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标
  11. minIndex = arr[j] < arr[minIndex] ? j : minIndex;
  12. }
  13. swap(arr, i, minIndex);
  14. }
  15. }
  16. public static void swap(int[] arr, int i, int j) {
  17. int tmp = arr[i];
  18. arr[i] = arr[j];
  19. arr[j] = tmp;
  20. }

3. 冒泡排序

  1. public static void bubbleSort(int[] arr) {
  2. if (arr == null || arr.length < 2) {
  3. return;
  4. }
  5. // 0 ~ N-1
  6. // 0 ~ N-2
  7. // 0 ~ N-3
  8. for (int e = arr.length - 1; e > 0; e--) { // 0 ~ e
  9. for (int i = 0; i < e; i++) {
  10. if (arr[i] > arr[i + 1]) {
  11. swap(arr, i, i + 1);
  12. }
  13. }
  14. }
  15. }
  16. // 交换arr的i和j位置上的值
  17. public static void swap(int[] arr, int i, int j) {
  18. arr[i] = arr[i] ^ arr[j];
  19. arr[j] = arr[i] ^ arr[j];
  20. arr[i] = arr[i] ^ arr[j];
  21. }

4. 插入排序

  1. public static void insertionSort(int[] arr) {
  2. if (arr == null || arr.length < 2) {
  3. return;
  4. }
  5. // 不只1个数
  6. for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序
  7. for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
  8. swap(arr, j, j + 1);
  9. }
  10. }
  11. }
  12. // i和j是一个位置的话,会出错
  13. public static void swap(int[] arr, int i, int j) {
  14. arr[i] = arr[i] ^ arr[j];
  15. arr[j] = arr[i] ^ arr[j];
  16. arr[i] = arr[i] ^ arr[j];
  17. }

5. 归并排序

该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

  1. public static void mergeSort1(int[] arr) {
  2. if (arr == null || arr.length < 2) { // 为空 或 小于两个元素无法排序
  3. return;
  4. }
  5. process(arr, 0, arr.length - 1);
  6. }
  7. public static void process(int[] arr, int l, int r) {
  8. if (l == r) { // 只有一个元素
  9. return;
  10. }
  11. int mid = l + ((r - l) / 2); // 为了防止越界 相当于(l+r)/2
  12. process(arr, l, mid); // 递归分治
  13. process(arr, mid + 1, r); // 递归
  14. merge(arr, l, mid, r); // 合并子序列
  15. }
  16. public static void merge(int[] arr, int l, int m, int r) {
  17. int[] prearr = new int[r - l + 1]; // 开辟存储排序好的数组 左右组有多少个元素就开辟多少个位置
  18. int index = 0; // 存储数组指针
  19. int p1 = l; // 左组指针
  20. int p2 = m + 1; // 右组指针
  21. while (p1 <= m && p2 <= r) { // 防止左右指针越界
  22. prearr[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; // 比较大小
  23. }
  24. while (p1 <= m) { // 左指针未越界
  25. prearr[index++] = arr[p1++];
  26. }
  27. while (p2 <= r) { // 右指针未越界
  28. prearr[index++] = arr[p2++];
  29. }
  30. // 归并到原始数组
  31. for (int i = 0; i < prearr.length; i++) {
  32. arr[l + i] = prearr[i];
  33. }
  34. }
  35. //非递归
  36. public static void mergeSort2(int[] arr) {
  37. if (arr == null || arr.length < 2) { // 为空或小于2个元素
  38. return;
  39. }
  40. int step = 1; // 步长为 1 2 4 8 16 32 2的次方(即多少个为一组)
  41. int N = arr.length; // 长度
  42. while (step < N) { // 如果步长 超过 长度
  43. int L = 0; // 左指针
  44. while (L < N) {
  45. int M = 0; // 右指针重置
  46. if (N - L >= step) { // 右指针到左指针 大于等于步长
  47. M = L + step - 1; // 右指针赋值
  48. } else {
  49. M = N - 1; // 否则右指针为 数组长度-1 为了赋值数值溢出
  50. }
  51. if (M == N - 1) { // 如果左组的 右指针为数组中最后一个则 无右组比较 直接break
  52. break;
  53. }
  54. int R = 0; // 右组右指针
  55. if (N - 1 - M >= step) { // 右组是否能凑齐step个元素
  56. R = M + step;
  57. } else { // 无法凑齐 右指针到数组尾元素
  58. R = N - 1;
  59. }
  60. merge(arr, L, M, R); // 合并区间 L为左组左指针 M为左组右指针 M+1为右组左指针 R为右组右指针
  61. if (R == N - 1) { // 右组右指针到数组尾 结束本次步长
  62. break;
  63. } else { // 否则重新赋值左组左指针 进行一个大组的区间合并
  64. L = R + 1;
  65. }
  66. }
  67. if (step > N / 2) { // 当前步长不能凑齐左右两组 进行合并区间
  68. break;
  69. }
  70. step *= 2; // 步长增加
  71. }
  72. }
  73. // 非递归方法实现
  74. public static void mergeSort3(int[] arr) {
  75. if (arr == null || arr.length < 2) {
  76. return;
  77. }
  78. int N = arr.length;
  79. int mergeSize = 1;
  80. while (mergeSize < N) {
  81. int L = 0;
  82. while (L < N) {
  83. if (mergeSize >= N - L) { //当前步长大于剩下为合并的元素
  84. break;
  85. }
  86. int M = L + mergeSize - 1; //左组右指针
  87. int R = M + Math.min(mergeSize, N - M - 1); //右组右指针
  88. merge(arr, L, M, R);
  89. L = R + 1;
  90. }
  91. if (mergeSize > N / 2) {
  92. break;
  93. }
  94. mergeSize <<= 1;
  95. }
  96. }
  97. // for test
  98. public static int[] generateRandomArray(int maxSize, int maxValue) {
  99. int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
  100. for (int i = 0; i < arr.length; i++) {
  101. arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
  102. }
  103. return arr;
  104. }
  105. // for test
  106. public static int[] copyArray(int[] arr) {
  107. if (arr == null) {
  108. return null;
  109. }
  110. int[] res = new int[arr.length];
  111. for (int i = 0; i < arr.length; i++) {
  112. res[i] = arr[i];
  113. }
  114. return res;
  115. }
  116. // for test
  117. public static boolean isEqual(int[] arr1, int[] arr2) {
  118. if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
  119. return false;
  120. }
  121. if (arr1 == null && arr2 == null) {
  122. return true;
  123. }
  124. if (arr1.length != arr2.length) {
  125. return false;
  126. }
  127. for (int i = 0; i < arr1.length; i++) {
  128. if (arr1[i] != arr2[i]) {
  129. return false;
  130. }
  131. }
  132. return true;
  133. }
  134. // for test
  135. public static void printArray(int[] arr) {
  136. if (arr == null) {
  137. return;
  138. }
  139. for (int i = 0; i < arr.length; i++) {
  140. System.out.print(arr[i] + " ");
  141. }
  142. System.out.println();
  143. }
  144. // 对数器
  145. public static void main(String[] args) {
  146. int testTime = 500000;
  147. int maxSize = 100;
  148. int maxValue = 100;
  149. System.out.println("测试开始");
  150. for (int i = 0; i < testTime; i++) {
  151. int[] arr1 = generateRandomArray(maxSize, maxValue);
  152. int[] arr2 = copyArray(arr1);
  153. mergeSort1(arr1);
  154. mergeSort2(arr2);
  155. if (!isEqual(arr1, arr2)) {
  156. System.out.println("出错了!");
  157. printArray(arr1);
  158. printArray(arr2);
  159. break;
  160. }
  161. }
  162. System.out.println("测试结束");
  163. }

5.1. 求数组小和

给一个数组arr[L-R]范围既要排好序,也要返回每个元素在排序前比当前元素小的和

  1. public static int smallSum(int[] arr) {
  2. if (arr == null || arr.length < 2) {
  3. return 0;
  4. }
  5. return process(arr, 0, arr.length - 1);
  6. }
  7. private static int process(int[] arr, int l, int r) {
  8. if (l == r) {
  9. return 0;
  10. }
  11. int mid = l + ((r - l) >> 1);
  12. return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
  13. }
  14. private static int merge(int[] arr, int l, int mid, int r) {
  15. int[] arr2 = new int[r - l + 1];
  16. int i = 0;
  17. int p1 = l;
  18. int p2 = mid + 1;
  19. int ans = 0; // 当前归并最小和
  20. while (p1 <= mid && p2 <= r) {
  21. ans += arr[p1] < arr[p2] ? arr[p1] * (r - p2 + 1) : 0; // 如果先放入左组 则代表有r-p2+1个元素大于当前左组的元素 否则没有最小
  22. arr2[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; // 比较大小归并 如等于先放右组避免将相同值元素统计到r-p2+1中
  23. }
  24. while (p1 <= mid) {
  25. arr2[i++] = arr[p1++];
  26. }
  27. while (p2 <= r) {
  28. arr2[i++] = arr[p2++];
  29. }
  30. for (i = 0; i < arr2.length; i++) {
  31. arr[l + i] = arr2[i];
  32. }
  33. return ans;
  34. }
  35. // for test
  36. public static int comparator(int[] arr) {
  37. if (arr == null || arr.length < 2) {
  38. return 0;
  39. }
  40. int res = 0;
  41. for (int i = 1; i < arr.length; i++) {
  42. for (int j = 0; j < i; j++) {
  43. res += arr[j] < arr[i] ? arr[j] : 0;
  44. }
  45. }
  46. return res;
  47. }

5.2. 求数组中的逆序对数量

给定一个数组arr,求数组的降序对总数量

在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为降序对

  1. public static int reverPairNumber(int[] arr) {
  2. if (arr == null || arr.length == 0) {
  3. return 0;
  4. }
  5. return prcess(arr, 0, arr.length - 1);
  6. }
  7. public static int prcess(int[] arr, int l, int r) {
  8. if (l == r) {
  9. return 0;
  10. }
  11. int m = l + ((r - l) >> 1);
  12. return prcess(arr, l, m) + prcess(arr, m + 1, r) + merge(arr, l, m, r);
  13. }
  14. public static int merge(int[] arr, int l, int m, int r) {
  15. int ans = 0;
  16. int[] help = new int[r - l + 1];
  17. // 倒着遍历
  18. int i = help.length - 1; // 从尾部开始
  19. int p1 = m; // 左边边界
  20. int p2 = r; // 右组边界
  21. while (p1 >= l && p2 > m) {
  22. ans += arr[p1] > arr[p2] ? (p2 - m) : 0; // 如果左边指针数大于右边指针数则为降序对
  23. help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
  24. }
  25. while (p1 >= l) {
  26. help[i--] = arr[p1--];
  27. }
  28. while (p2 > m) { // 注意到m就停止
  29. help[i--] = arr[p2--];
  30. }
  31. for (int j = 0; j < help.length; j++) {
  32. arr[l + j] = help[j];
  33. }
  34. return ans;
  35. }

5.3. 493. 翻转对

在一个数组中,对于任何一个数num,求有多少个(后面的数*2)依然<num,返回总个数

  1. 比如:[3,1,7,0,2]
  2. 3的后面有:10
  3. 1的后面有:0
  4. 7的后面有:02
  5. 0的后面没有
  6. 2的后面没有
  7. 所以总共有5

5.4. 327. 区间和的个数

  1. public int countRangeSum(int[] nums, int lower, int upper) {
  2. if (nums == null || nums.length == 0) {
  3. return 0;
  4. }
  5. // 前缀和数组
  6. long[] sum = new long[nums.length];
  7. sum[0] = nums[0];
  8. for (int i = 1; i < nums.length; i++) {
  9. sum[i] = sum[i - 1] + nums[i];
  10. }
  11. // 原数组 已无用 传递前缀和数组
  12. return process(sum, 0, nums.length - 1, lower, upper);
  13. }
  14. private int process(long[] sum, int l, int r, int lower, int upper) {
  15. if (l == r) {
  16. // 只有一个数时 判断是否在lower和upper范围内 是则res+1
  17. return sum[l] >= lower && sum[l] <= upper ? 1 : 0;
  18. }
  19. int m = l + ((r - l) >> 1);
  20. // 递归+合并
  21. return process(sum, l, m, lower, upper) + process(sum, m + 1, r, lower, upper)
  22. + merge(sum, l, m, r, lower, upper);
  23. }
  24. private int merge(long[] sum, int l, int m, int r, int lower, int upper) {
  25. int ans = 0;
  26. int windowL = l;
  27. int windowR = l; // [windowL, windowR)
  28. for (int i = m + 1; i <= r; i++) { // 从右组开始遍历
  29. long min = sum[i] - upper;
  30. long max = sum[i] - lower;
  31. while (windowR <= m && sum[windowR] <= max) {
  32. windowR++; // 找到最大值下标边界
  33. }
  34. while (windowL <= m && sum[windowL] < min) {
  35. windowL++; // 最小值下标边界
  36. }
  37. ans += windowR - windowL; // 右组当前元素有多少个在范围内的
  38. }
  39. //归并
  40. long[] help = new long[r - l + 1];
  41. int i = 0;
  42. int p1 = l;
  43. int p2 = m + 1;
  44. while (p1 <= m && p2 <= r) {
  45. help[i++] = sum[p1] <= sum[p2] ? sum[p1++] : sum[p2++];
  46. }
  47. while (p1 <= m) {
  48. help[i++] = sum[p1++];
  49. }
  50. while (p2 <= r) {
  51. help[i++] = sum[p2++];
  52. }
  53. for (int j = 0; j < help.length; j++) {
  54. sum[l + j] = help[j];
  55. }
  56. return ans;
  57. }

6. 快速排序

  1. // 递归经典写法
  2. public static void QuickSort(int[] arr, int l, int r) {
  3. if (l >= r) {
  4. return;
  5. }
  6. int left = l;
  7. int right = r;
  8. int base = arr[r]; // 以最后一个为基准 如果以头为基准则应该先找大于基准 再找小于基准的
  9. while (left < right) {
  10. // 将小于等于base的放到左边 则应该left++
  11. while (arr[left] <= base && left < right) {
  12. left++;
  13. }
  14. // 将大于等于base的放到右边 则right--
  15. while (arr[right] >= base && left < right) {
  16. right--;
  17. }
  18. if (left < right) {
  19. // 交换
  20. int temp = arr[left];
  21. arr[left] = arr[right];
  22. arr[right] = temp;
  23. }
  24. }
  25. arr[r] = arr[left]; // 将小于基准的最后一个数 交换到base位置
  26. arr[left] = base; // 将base值 交换到小于基准的位置
  27. // 拆分大问题 递归
  28. QuickSort(arr, l, left - 1);
  29. QuickSort(arr, right + 1, r);
  30. }
  31. public static void swap(int[] arr, int i, int j) {
  32. int tmp = arr[i];
  33. arr[i] = arr[j];
  34. arr[j] = tmp;
  35. }

6.1. 荷兰国旗问题

荷兰国旗是由红白蓝3种颜色的条纹拼接而成,把这些条纹按照颜色排好,红色的在上半部分,白色的在中间部分,蓝色的在下半部分,我们把这类问题称作荷兰国旗问题。

其核心思想为 分区 小于基准放左边 等于基准的放中间 大于基准的放右边

  1. public static void swap(int[] arr, int i, int j) {
  2. int tmp = arr[i];
  3. arr[i] = arr[j];
  4. arr[j] = tmp;
  5. }
  6. // arr[L...R] 玩荷兰国旗问题的划分,以arr[R]做划分值
  7. // <arr[R] ==arr[R] > arr[R]
  8. public static int[] netherlandsFlag(int[] arr, int L, int R) {
  9. if (L > R) { // L...R L>R
  10. return new int[] { -1, -1 };
  11. }
  12. if (L == R) {
  13. return new int[] { L, R };
  14. }
  15. int less = L - 1; // < 区 右边界
  16. int more = R; // > 区 左边界
  17. int index = L;
  18. while (index < more) { // 当前位置,不能和 >区的左边界撞上
  19. if (arr[index] == arr[R]) {
  20. index++;
  21. } else if (arr[index] < arr[R]) {
  22. // swap(arr, less + 1, index);
  23. // less++;
  24. // index++;
  25. swap(arr, index++, ++less);
  26. } else { // >
  27. swap(arr, index, --more);
  28. }
  29. }
  30. swap(arr, more, R); // <[R] =[R] >[R]
  31. return new int[] { less + 1, more }; //返回的是等于区的开始节点与结束节点
  32. }

6.2. 快排1.0

1.0效率低下 每次是最差情况 只将基准放置在排序后的位置 O(N^2)

  1. public static void swap(int[] arr, int i, int j) {
  2. int tmp = arr[i];
  3. arr[i] = arr[j];
  4. arr[j] = tmp;
  5. }
  6. // arr[L..R]上,以arr[R]位置的数做划分值
  7. // <= X > X
  8. // <= X X
  9. public static int partition(int[] arr, int L, int R) {
  10. if (L > R) {
  11. return -1;
  12. }
  13. if (L == R) {
  14. return L;
  15. }
  16. int lessEqual = L - 1;
  17. int index = L;
  18. while (index < R) {
  19. if (arr[index] <= arr[R]) { //此为小于等于基准
  20. swap(arr, index, ++lessEqual);
  21. }
  22. index++;
  23. }
  24. swap(arr, ++lessEqual, R);
  25. return lessEqual; //返回小于等于区的边界下标(即大小-1)
  26. }
  27. public static void process1(int[] arr, int L, int R) {
  28. if (L >= R) {
  29. return;
  30. }
  31. // L..R partition arr[R] [ <=arr[R] arr[R] >arr[R] ]
  32. //只有两个区 小于等于区 和 大于区
  33. //1.0效率低下 每次是最差情况 只将基准放置在排序后的位置 O(N^2)
  34. int M = partition(arr, L, R);
  35. process1(arr, L, M - 1);
  36. process1(arr, M + 1, R);
  37. }
  38. public static void quickSort1(int[] arr) {
  39. if (arr == null || arr.length < 2) {
  40. return;
  41. }
  42. process1(arr, 0, arr.length - 1);
  43. }

6.3. 快排2.0

2.0优化每次排序 等于区的数,但最差情况仍有可能每次选中的基准只有1个(不重复)

  1. public static void swap(int[] arr, int i, int j) {
  2. int tmp = arr[i];
  3. arr[i] = arr[j];
  4. arr[j] = tmp;
  5. }
  6. // arr[L...R] 玩荷兰国旗问题的划分,以arr[R]做划分值
  7. // <arr[R] ==arr[R] > arr[R]
  8. public static int[] netherlandsFlag(int[] arr, int L, int R) {
  9. if (L > R) { // L...R L>R
  10. return new int[] { -1, -1 };
  11. }
  12. if (L == R) {
  13. return new int[] { L, R };
  14. }
  15. int less = L - 1; // < 区 右边界
  16. int more = R; // > 区 左边界
  17. int index = L;
  18. while (index < more) { // 当前位置,不能和 >区的左边界撞上
  19. if (arr[index] == arr[R]) {
  20. index++;
  21. } else if (arr[index] < arr[R]) {
  22. // swap(arr, less + 1, index);
  23. // less++;
  24. // index++;
  25. swap(arr, index++, ++less);
  26. } else { // >
  27. swap(arr, index, --more);
  28. }
  29. }
  30. swap(arr, more, R); // <[R] =[R] >[R]
  31. return new int[] { less + 1, more }; //返回的是等于区的开始节点与结束节点
  32. }
  33. // arr[L...R] 排有序,快排2.0方式
  34. public static void process2(int[] arr, int L, int R) {
  35. if (L >= R) {
  36. return;
  37. }
  38. // [ equalArea[0] , equalArea[0]]
  39. int[] equalArea = netherlandsFlag(arr, L, R);
  40. process2(arr, L, equalArea[0] - 1);
  41. process2(arr, equalArea[1] + 1, R);
  42. }
  43. public static void quickSort2(int[] arr) {
  44. if (arr == null || arr.length < 2) {
  45. return;
  46. }
  47. process2(arr, 0, arr.length - 1);
  48. }

6.4. 随机快排3.0

由于我们选择基准是最右的数,有可能会出现最差情况,即每次以右为基准时等于区只有它自身,我们可以在进行分区操作时 在l到r范围内 随机抽取一个数与r位置(基准位置)作交换,避免了每次命中最差情况。

  1. public static void swap(int[] arr, int i, int j) {
  2. int tmp = arr[i];
  3. arr[i] = arr[j];
  4. arr[j] = tmp;
  5. }
  6. // arr[L...R] 玩荷兰国旗问题的划分,以arr[R]做划分值
  7. // <arr[R] ==arr[R] > arr[R]
  8. public static int[] netherlandsFlag(int[] arr, int L, int R) {
  9. if (L > R) { // L...R L>R
  10. return new int[] { -1, -1 };
  11. }
  12. if (L == R) {
  13. return new int[] { L, R };
  14. }
  15. int less = L - 1; // < 区 右边界
  16. int more = R; // > 区 左边界
  17. int index = L;
  18. while (index < more) { // 当前位置,不能和 >区的左边界撞上
  19. if (arr[index] == arr[R]) {
  20. index++;
  21. } else if (arr[index] < arr[R]) {
  22. // swap(arr, less + 1, index);
  23. // less++;
  24. // index++;
  25. swap(arr, index++, ++less);
  26. } else { // >
  27. swap(arr, index, --more);
  28. }
  29. }
  30. swap(arr, more, R); // <[R] =[R] >[R]
  31. return new int[] { less + 1, more }; //返回的是等于区的开始节点与结束节点
  32. }
  33. public static void process3(int[] arr, int L, int R) {
  34. if (L >= R) {
  35. return;
  36. }
  37. swap(arr, L + (int) (Math.random() * (R - L + 1)), R);//在l到r范围内随机选一个数 交换到r位置,避免多次命中最差情况
  38. int[] equalArea = netherlandsFlag(arr, L, R);
  39. process3(arr, L, equalArea[0] - 1);
  40. process3(arr, equalArea[1] + 1, R);
  41. }
  42. public static void quickSort3(int[] arr) {
  43. if (arr == null || arr.length < 2) {
  44. return;
  45. }
  46. process3(arr, 0, arr.length - 1);
  47. }

7. 排序稳定性

稳定性指是同样大小的样本 再次进行排序之后不会改变相对次序

对于基础类型来说 稳定性没有意义

对于非基础类型来说 稳定性有重要的意义

有的排序算法可以实现成稳定的 而有的排序算法无论如何都实现不成稳定

12. 排序 - 图1