• 七大排序扩展篇——Java


    目录

    双向选择排序

    思路:

    源代码: 

    Quick Sort2 —— 二路快排

    思路:

    源代码:

    Quick Sort3 —— 三路快排 

    思路:

    源代码:

    剑指offer 51、数组中的逆序对  

    思路:

    源代码:


    双向选择排序

    思路:

    1. 先定义min为首元素,max为尾元素,遍历数组;
    2. 如果遍历到的元素大于max,更新max;若元素小于min,更新min;
    3. 遍历一遍数组后,找到了最大值最小值下标;
    4. 将最小值与首部元素交换,最大值与尾部元素交换;
    5. 若最大值就是首部元素,在先将最小值与首部元素交换位置后,首部的最大值就被移动到最小值处,更新max=min,在进行与尾部元素交换。

    源代码: 

    1. /**
    2. * 双向选择排序
    3. * 每趟同时保存最大和最小下标,设首位为最小,末尾为最大,从两头往中间不断对比交换,一趟就能够将两个数据归位。
    4. **/
    5. public class doubleSelectionSort {
    6. public void DoubleSelection(int[] arr, int start, int end) {
    7. while (start < end) {
    8. int min = start;
    9. int max = end;
    10. for (int i = start; i <= end; i++) {
    11. // 有大于 max 的数,更新 max
    12. if (arr[max] < arr[i]) max = i;
    13. // 有小于 min 的数,更新 min
    14. if (arr[min] > arr[i]) min = i;
    15. }
    16. // 此时找到了最大值,最小值下标,交换最小值至首部,交换最大值至末尾
    17. swap(arr, arr[start], arr[min]);
    18. // 如果首部元素为最大值,首部元素先与最小值交换,首部最大值元素被交换到最小值,更新最大值下标
    19. if (start == max) max = min;
    20. swap(arr, arr[max], arr[end]);
    21. ++start;
    22. --end;
    23. }
    24. }
    25. // 交换
    26. private void swap(int[] arr, int left, int right) {
    27. int temp = arr[left];
    28. arr[left] = arr[right];
    29. arr[right] = temp;
    30. }
    31. }

    Quick Sort2 —— 二路快排

    当数组中存在大量重复元素时,我们的快速排序又退化为了O(n^2)。之前的快速排序没有讨论等于arr[l]的情况,其中等于arr[l]的包含在大于等于中。

    和单路快排不同的是 将数组中小于v和大于v的元素放在数组的两端,那么将引用新的索引 j 的记录大于v的边界位置

    思路:

    1. 在数组中随机找一个元素 v ,将元素与首元素换位置,此时随机元素位于数组首部;
    2. 定义两个变量,i 指向首元素下一个位置,j 指向尾元素。i++, j-- 遍历数组;
    3. 当 i 的元素小于 v 时,i++,直到碰到了某个元素 >= v。j同理,直到碰到某个元素 <= v;   
    4. 交换ij位置上的元素,继续i++,j-- 遍历数组;
    5. 将l和 j元素交换,此时j左边的元素都不大于j,右边的元素都不小于j;
    6. 此时就分别对j左边和j右边的元素进行二路快排。

    源代码:

    1. public class quickSortInternal2 {
    2. public void quickSort(int[] arr, int n) {
    3. quickSort2(arr, 0 , n - 1);
    4. }
    5. // 二路快排
    6. private void quickSort2(int[] arr, int l , int r) {
    7. if (l >= r) return;
    8. int p = partition2(arr, l , r);
    9. quickSort2(arr, l ,p - 1);
    10. quickSort2(arr, p + 1, r);
    11. }
    12. // 维护两个索引i,j,分别从前往后,从后往前进行排序,交换
    13. private static int partition2(int[] array, int l, int r) {
    14. // 随机选取待排序数组中的任意一个元素
    15. int randomIndex = (int) (Math.random() * (r-l+1) + l);
    16. // int randomIndex = (Math.abs(new Random().nextInt())%(r-l+1))+l;
    17. swap(array, l , randomIndex);
    18. int v = array[l];
    19. int i = l + 1;
    20. int j = r;
    21. while (true) {
    22. while (i <= r && array[i] < v) i++;
    23. while (j >= l + 1 && array[j] > v) j--;
    24. if (i > j) break;
    25. swap(array, i , j);
    26. i++;
    27. j--;
    28. }
    29. // 循环结束,j 下标为分区点位置
    30. swap(array, l , j);
    31. return j;
    32. }
    33. private static void swap(int[] array, int left, int right) {
    34. int temp = array[left];
    35. array[left] = array[right];
    36. array[right] = temp;
    37. }
    38. }

    Quick Sort3 —— 三路快排 

    三路快排则是将数组分成了小于v,等于v,大于v的三个部分,当递归处理的时候,遇到等于v的元素不用管,只需要处理小于v,大于v的元素就好了。

    思路:

    1. 在数组中随机找一个元素 v ,将元素与首元素换位置,此时随机元素位于数组首部;
    2. 定义下标,i为首元素下一个位置,lt为首元素,gt为尾元素下一个;
    3. 遍历数组,e<v时,把当前元素e和==v部分的第一个元素进行交换,此时e这个元素就是<v部分最后一个元素,相应的lt这个索引需要向后移动一位。然后i++;
    4. "e>v",只需要把元素"e"和“gt-1”位置上的元素进行交换,相应的gt--。但是索引i不需要移动,依然指向一个没有被处理的元素,这个没有被处理过的元素是刚才从"gt-1"位置换过来的;
    5. 遍历完,将l的元素和lt交换即为分区点。分别对分区点左边和右边的元素排序。

     源代码:

    1. public class quickSortInternal3 {
    2. private void quickSort3(int[] arr, int l, int r) {
    3. if (l >= r) return;
    4. // 随机选取待排序数组中的任意⼀一个元素
    5. int randomIndex = (int) (Math.random() * (r - l + 1) + l);
    6. swap(arr, l ,randomIndex);
    7. int v = arr[l];
    8. // 定义并初始化下标索引,使得三部分区间为空
    9. // arr[l+1...lt] < v
    10. int lt = l;
    11. // arr[lt+1,i] == v
    12. int i = l + 1;
    13. // arr[gt...r] > v
    14. int gt = r + 1;
    15. while (i < gt) {
    16. if (arr[i] < v) {
    17. swap(arr, i , lt + 1);
    18. i++;
    19. lt++;
    20. }else if (arr[i] > v) {
    21. swap(arr, i , gt - 1);
    22. gt--;
    23. }else { //arr[i] == v
    24. i++;
    25. }
    26. }
    27. // 循环走完只需要将l的元素和lt交换即为分区点
    28. swap(arr, l , lt);
    29. // 继续对
    30. quickSort3(arr, l , lt - 1);
    31. // 继续对 >v 部分进行快速排序
    32. quickSort3(arr, gt , r);
    33. }
    34. // 交换
    35. private void swap(int[] arr, int left, int right) {
    36. int temp = arr[left];
    37. arr[left] = arr[right];
    38. arr[right] = temp;
    39. }
    40. }

    剑指offer 51、数组中的逆序对  

    思路:

    利用归并排序思想,先分成小分,合并的时候,i遍历比较左区间与右区间元素的值的大小。左区间的元素 < 右区间元素,不构成逆序对;右区间的元素 < 左区间的元素,构成逆序对,此时逆序对的个数 = mid - i + 1。

    源代码:

    1. class Solution {
    2. public int reversePairs(int[] nums) {
    3. return reversePairsHelper(nums,0,nums.length - 1);
    4. }
    5. /**
    6. * 传入一个数组nums,就可以求出在nums[l,r]上的逆序对
    7. * @param nums
    8. * @param l
    9. * @param r
    10. * @return 此时nums[l,r]逆序对的个数
    11. */
    12. private int reversePairsHelper(int[] nums, int l, int r) {
    13. if (l >= r) {
    14. return 0;
    15. }
    16. int mid = l + ((r - l) >> 1);
    17. //先求出左区间的逆序对的个数
    18. int left = reversePairsHelper(nums,l,mid);
    19. //在求出右区间的逆序对的个数
    20. int right = reversePairsHelper(nums,mid + 1,r);
    21. if (nums[mid] > nums[mid + 1]) {
    22. //排序好的左右区间还存在逆序对
    23. return merge(nums,l,mid,r) + left + right;
    24. }
    25. return left + right;
    26. }
    27. /**
    28. * 合并nums两个有序区间[l,mid] [mid + 1,r]
    29. * 返回合并过程中逆序对的个数
    30. * @param nums
    31. * @param l
    32. * @param mid
    33. * @param r
    34. * @return
    35. */
    36. private int merge(int[] nums, int l, int mid, int r) {
    37. int[] aux = new int[r - l + 1];
    38. // 合并过程中产生的逆序对个数
    39. int ret = 0;
    40. for (int i = 0; i < aux.length; i++) {
    41. aux[i] = nums[i + l];
    42. }
    43. int i = l;
    44. int j = mid + 1;
    45. for (int k = l; k <= r; k++) {
    46. if (i > mid) {
    47. // 左区间已经合并完毕,放入右区间元素
    48. nums[k] = aux[j - l];
    49. j ++;
    50. }else if (j > r) {
    51. // 右区间已经合并完毕,放入左区间元素
    52. nums[k] = aux[i - l];
    53. i ++;
    54. }else if (aux[i - l] <= aux[j - l]) {
    55. // 左区间的元素 < 右区间元素,不构成逆序对
    56. nums[k] = aux[i - l];
    57. i ++;
    58. }else {
    59. // 右区间的元素 < 左区间的元素,构成逆序对
    60. // 此时逆序对的个数 = mid - i + 1
    61. ret += (mid - i) + 1;
    62. nums[k] = aux[j - l];
    63. j ++;
    64. }
    65. }
    66. return ret;
    67. }
    68. }
  • 相关阅读:
    组件之前的传值——中央事件总线bus
    基础会计学重点
    [计算机入门] 应用软件(办公类)
    Redis基础篇
    我在京东的第417天:陷入了情绪的泥沼
    一致性 hash 环
    Vitis HLS 加法器(整数)设计
    206、SpringBoot 整合 RabbitMQ 的自动配置类 和 对应的属性处理类 的知识点
    【软件测试】 初识软件测试
    【sciter】sciter 拖拽过程总结
  • 原文地址:https://blog.csdn.net/weixin_45699237/article/details/123810094