排序|插入、希尔、选择、堆、冒泡、快速、合并

排序

  • 插入(顺序+二分)、希尔、选择(普通+双向选择)、堆、冒泡、快速(递归+非递归、partition(hoare+挖坑法+前后遍历法)、合并(递归+非递归)
  • 升序为例

插入排序

选择无序区间的第一个元素插入到有序区间的合适位置

插入+二分查找:将顺序查找改进为二分查找,同时注意排序的稳定性
在这里插入图片描述

    //插入排序
    public static void insertSort(int[] nums) {
         for (int i = 1; i < nums.length; i++) {
             int v = nums[i];
             int j = i-1;
             //在 [0,i)找到应该插入的位置
             // j >= 0保证边界情况
             // v < nums[j]保证排序稳定性
             for (; j >= 0 && v < nums[j]; j--) {
                 nums[j+1] = nums[j]; //元素后移
             }
             nums[j+1] = v;
         }
    }
    //改进 插入+二分查找
    public static void bsInsertSort(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            int v = nums[i];
            int left = 0;
            int right = i;
            //二分查找 常规版 不具有稳定性
//            while (left <= right) {
//                int mid = (left + right) / 2;
//                if (v < nums[mid]) {
//                    right = mid - 1;
//                } else if (v > nums[mid]){
//                    left = mid + 1;
//                } else {
//                    break;
//                }
//            }
            //具有稳定性
            while (left < right) {
                int mid = (left + right) / 2;
                if (v >= nums[mid]) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            for (int j = i; j > left; j--) {
                nums[j] = nums[j-1];
            }

            nums[left] = v;
        }
    }

希尔排序

将距离为gap的数据分为同一组,然后对每一组的数据进行排序,gap不断减小,重新分组和排序。当gap=1时,即为插入排序
在这里插入图片描述

    //希尔排序
    public static void shellSort(int[] nums) {
        for (int gap = nums.length; gap > 1; gap /= 2) {
            shellSortHelper(nums, gap);
        }
        shellSortHelper(nums, 1);
    }

    private static void shellSortHelper(int[] nums, int gap) {
        for (int i = 0; i < nums.length; i++) {
            int v = nums[i];
            int j = i - gap;
            for (; j >= 0 && v < nums[j]; j -= gap) {
                nums[j+gap] = nums[j];
            }
            nums[j+gap] = v;
        }
    }

选择排序

从无序区间里选出一个最小的元素,放在有序区间的后面

双向选择排序:在一个无序区间里,同时找到最大值和最小值,将最小值放在最前面,最大值放在最后面
在这里插入图片描述

    //选择排序
    public static void selectSort(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            int minIndex = i;
            for (int j = i; j < nums.length; j++) {
                if (nums[minIndex] > nums[j]) {
                    minIndex = j;
                }
            }
            swap(i, minIndex, nums);
        }

    }
    //双向选择排序
    public static void opSelectSort(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        //[low, high]表示一个无序区间
        //在这个区间里找到最大值和最小值
        while (low <= high) {
            int min = low;
            int max = low;
            for (int i = low + 1; i <= high; i++) {
                if (nums[min] > nums[i]) {
                    min = i;
                }
                if (nums[max] < nums[i]) {
                    max = i;
                }
            }

            swap(min, low, nums); //将最小值移到最前面
            //如果最大值的位置刚好是最前面的位置,那么上一步的交换之后,最大值的位置就在最小值上
            if (max == low) {
                max = min;
            }

            swap(max, high, nums); //将最大值移到最后面
            low++;
            high--;
        }
    }

堆排序

通过堆来找到无序区间的最大值。排升序建大堆,排降序建小堆
在这里插入图片描述

    //堆排序
    public static void heapSort(int[] nums) {
        createHeap(nums);

        for (int i = 0; i < nums.length; i++) {
            swap(0, nums.length-1-i, nums);
            shiftDown(nums.length-1-i, 0, nums);
        }
    }

    //建堆
    private static void createHeap(int[] nums) {
        for (int i = (nums.length-1-1)/2; i >= 0; i--) { //从倒数第一个非叶子节点开始调整
            shiftDown(nums.length, i, nums);
        }
    }

    //向下调整(大根堆)
    private static void shiftDown(int size, int index, int[] nums) {
        int parent = index;
        int child = 2*parent+1;
        while (child < size) {
            //找到左右子树最大的那个
            if (child + 1 < size && nums[child] < nums[child+1]) {
                child = child+1;
            }
            //结点与子树比较,若小于子树则交换,否则退出循环,不必向下调整
            if (nums[parent] < nums[child]) {
                swap(parent, child, nums);
            } else {
                break;
            }
            parent = child;
            child = 2*parent+1;
        }
    }

冒泡排序

在无序区间,通过相邻数的比较,把较大的数冒泡到区间的最后,直至数组整体有序
在这里插入图片描述

    //冒泡排序
    public static void bubbleSort(int[] nums) {
        for (int i = 0; i < nums.length-1; i++) { //要比较多少趟
            boolean isSorted = true;
            for (int j = 0; j < nums.length-i-1; j++) { //在一趟中,相邻比较
                if (nums[j] > nums[j+1]) {
                    swap(j, j+1, nums);
                    isSorted = false;
                }
            }
            //如果这一趟中没有比较则说明数组已经有序
            if (isSorted) {
                break;
            }
        }
    }

快速排序

1.partition:选择一个基准值,从后往前找比基准值小的数,从前往后找比基准值大的数,两数交换,如循环,使得整个区间里,比基准值小的数放在其左边,比基准值大的数放在其右边

2.采用分治思想,对左右两个小区间按照同样方式处理

ps:如果基准值选择最左边的数,则先从后往前找,最后重复的元素一定比基准值小 ;如果基准值选择最右边的数,则先从前往后找,最后重复的元素一定比基准值大
在这里插入图片描述

    //快速排序
    public static void quickSort(int[] nums) {
        quickSortHelper(0, nums.length-1, nums);
    }

    private static void quickSortHelper(int left, int right, int[] nums) {
        if (left >= right) { 
            return;
        }
        int pivotIndex = partition(left, right, nums);
        quickSortHelper(left, pivotIndex-1, nums);
        quickSortHelper(pivotIndex+1, right, nums);
    }

    //一次快排
    private static int partition(int left, int right, int[] nums) {
        int pivot = nums[left];
        while (left < right) {
            while (left < right && nums[right] > pivot) {
                right--;
            }
            while (left < right && nums[left] < pivot) {
                left++;
            }
            swap(left, right, nums);
        }
        nums[left] = pivot;
        return left;
    }
    //非递归版本
    private static void quickSort2(int[] nums) {
        Stack<Integer> stack = new Stack<>();
        stack.push(nums.length - 1);
        stack.push(0);

        while (!stack.isEmpty()) {
            int left = stack.pop();
            int right = stack.pop();
            if (left >= right) {
                continue;
            }

            int pivotIndex = partition(left, right, nums);
            stack.push(right);
            stack.push(pivotIndex+1);

            stack.push(pivotIndex-1);
            stack.push(left);
        }
    }

partition的其他实现方式

    //挖坑法 不进行交换,而是赋值
    private static int partition2(int left, int right, int[] nums) {
        int pivot = nums[left];
        while (left < right) {
            while (left < right && nums[right] > pivot) {
                right--;
            }
            nums[left] = nums[right];
            while (left < right && nums[left] < pivot) {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
    //前后遍历法
    //定义基准值为nums[right],定义两个指针,初始时,front指向起始位置的前一个,rear指向起始位置
    //rear向后走,找到比基准值小的数,交换rear与++front,直至rear走到right结束
    //最后交换++front与right
    private static int partition3(int left, int right, int[] nums) {
        int front = left-1; //指大
        int pivot = nums[right];
        for (int rear = left; rear < right; rear++) { //rear找小
            if (nums[rear] < pivot) {
                swap(rear, ++front, nums);
            }
        }
        swap(++front, right, nums);
        return front;
    }

合并排序

先使子序列有序,再使子序列段间有序,最后把两个有序的子数组合并成一个有序的数组
在这里插入图片描述

    //合并排序
    public static void mergeSort(int[] nums) {
        mergeSortHelper(0, nums.length, nums); //没有-1
    }

    private static void mergeSortHelper(int low, int high, int[] nums) {
        if (low >= high || high - low == 1) { //区间为空区间或只有一个元素,不用排序
            return;
        }
        int mid = (low+high)/2;
        //递归对子区间归并排序
        mergeSortHelper(low, mid, nums);//[low, mid)
        mergeSortHelper(mid, high, nums);//[mid, high)
        merge(low, mid, high, nums);
    }

    //合并两个有序数组
    private static void merge(int low, int mid, int high, int[] nums) {
        int i = low;
        int j = mid;
        int t = high - low;
        int[] extra = new int[t];
        int k = 0;
        while (i < mid && j < high) {
            //保证稳定性
            if (nums[i] <= nums[j]) {
                extra[k++] = nums[i++];
            } else {
                extra[k++] = nums[j++];
            }
        }

        while (i < mid) {
            extra[k++] = nums[i++];
        }
        while (j < high) {
            extra[k++] = nums[j++];
        }

        for (int x = 0; x < t; x++) {
            
            nums[low+x] = extra[x];
        }
    }

    //非递归版本
    public static void mergeSort2(int[] nums) {
        for (int i = 1; i < nums.length; i *= 2) {
            for (int j = 0; j < nums.length; j += 2*i) {
                int low = j;
                int mid = j+i;
                if (mid >= nums.length) {
                    continue;
                }
                int high = mid + i;
                if (high > nums.length) {
                    high = nums.length;
                }
                merge(low, mid, high, nums);
            }
        }
    }

总结

时间复杂度 最好/平均/最差空间复杂度
插入排序-O(n)O(n^2)O(n^2)O(1)
希尔排序O(n)O(n^1.3)O(n^2)O(1)
选择排序O(n^2)O(1)
堆排序O(n*log(n))O(1)
冒泡排序-O(n)O(n^2)O(n^2)O(1)
快速排序O(n*log(n))O(n*log(n))O(n^2)O(log(n))/O(n)
合并排序-O(n*log(n))O(n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值