快速排序与归并排序

发布于:2025-04-03 ⋅ 阅读:(51) ⋅ 点赞:(0)

快速排序基本实现

双指针应用

快速排序里的递归过程本质上就是二叉树的前序递归调用

快速排序基本思想是 :通过一个标记pivot元素将n个元素的序列划分为左右两个子序列left和right,其中left中的元素都比pivot小,right的都比pivot的大。这样处理完之后pivot就在一个固定的位置了。

然后再次对pivot左右两侧递归执行快速排序。

上述过程是递归执行的,在将所有子区间排好序之后,整个序列就有序了。

// 理解变量是理解程序的关键
// i 指针 指的是 比pivot小的元素 位置
// i的左侧 到j  比pivot大的元素
// 最后,i+1的位置与pivot的元素互换位置
// 能够保证i+1 的左边比当前元素小;右边比当前元素大。
// 但是左边 右边 区域的元素是无序的哦

public static void quickSort(int[] arr, int left, int right) {
    if (left < right) {
        int pivot = arr[right];
        int i = left - 1;
        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) {
                i++;
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        //哨兵移动到位置pivotIndex上
        int pivotIndex = i + 1;
        int temp = arr[pivotIndex];
        arr[pivotIndex] = arr[right];
        arr[right] = temp;

        quickSort(arr, left, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, right);
    }
}
// 以 中间值作为 参照物
// 确保中间值的左侧 值均小于右侧值
void quickSort(int[] array, int start, int end) {
    if (start >= end) {
        return;
    }
    //这里就是一个对撞的双指针操作
    int left = start, right = end;
    int pivot = array[(start + end) / 2];

    while (left <= right) {
        while (left <= right && array[left] < pivot) {
            left++;
        }
        while (left <= right && array[right] > pivot) {
            right--;
        }
        if (left <= right) {
            int temp = array[left];
            array[left] = array[right];
            array[right] = temp;
            left++;
            right--;
        }
    }
    //先处理元素再分别递归处理两侧分支,与二叉树的前序遍历非常像
    quickSort(array, start, right);
    quickSort(array, left, end);   
}

复杂度分析

快速排序的时间复杂度计算比较麻烦一些。从原理来看,如果我们选择的pivot每次都正好在中间,效率是最高的,但是这是无法保证的,因此我们需要从最好、最坏和中间情况来分析

  • 最坏情况就是如果每次选择的恰好都是low结点作为pivot,如果元素恰好都是逆序的,此时时间复杂度为O(n^2)

  • 如果元素恰好都是有序的,则时间复杂度为O(n)

  • 折中的情况是每次选择的都是中间结点,此时序列每次都是长度相等的序列,此时的时间复杂度为(O(nlogn))

数组中第K大的数字

数组中的第K个最大元素。给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

在快排中,pivot的元素位置是不是能够反映元素是第几大。

总共6个元素,比方当前pivot 索引为2

说明他是 第 6-2 + 1 大的元素,即第5大。

要想找第4大,比当前pivot大的元素? 在他的右侧啦!反之。

int quickselect(int[] nums, int l, int r, int k) {
    if (l == r) return nums[k];
    int x = nums[l], i = l - 1, j = r + 1;
    while (i < j) {
        do i++; while (nums[i] < x);
        do j--; while (nums[j] > x);
        if (i < j){
            int tmp = nums[i];
            nums[i] = nums[j];
            nums[j] = tmp;
        }
    }
    if (k <= j) return quickselect(nums, l, j, k);
    else return quickselect(nums, j + 1, r, k);
}
public int findKthLargest(int[] _nums, int k) {
    int n = _nums.length;
    return quickselect(_nums, 0, n - 1, n - k);
}

归并排序

思想:将大的序列先视为若干个小数组,分成比较小的结构,利用归并的思想实现排序的方法。经典的分治策略。分——将问题先分解成小的问题,治——将小问题的答案逐步汇总。

  static void mergeSort(int[] array, int start, int end, int temp[]) {
        if (start >= end) {
            return;
        }
        mergeSort(array, start, (start + end) / 2, temp);
        mergeSort(array, (start + end) / 2 + 1, end, temp);
        merge(array, start, end, temp);
    }

     /**
 * 合并两个已排序的子数组 [start...middle] 和 [middle+1...end] 到原数组中
 * @param array 原数组,包含两个待合并的子数组
 * @param start 合并区间的起始索引
 * @param end 合并区间的结束索引
 * @param temp 临时数组,用于存储合并结果,避免覆盖原数组数据
 */
static void merge(int[] array, int start, int end, int[] temp) {
    // 计算中间点,将区间分为左右两个子数组
    int middle = (start + end) / 2;
    // 左子数组的遍历指针,初始指向左子数组的起始位置(start)
    int left = start;
    // 右子数组的遍历指针,初始指向右子数组的起始位置(middle+1)
    int right = middle + 1;
    // temp数组的填充指针,初始指向合并区间的起始位置(start)
    int index = start;

    // 遍历左右子数组,按升序将元素放入temp
    while (left <= middle && right <= end) {
        // 比较左右子数组当前元素,较小者优先放入temp
        if (array[left] < array[right]) {
            temp[index++] = array[left++];  // 左子数组元素较小,移动左指针
        } else {
            temp[index++] = array[right++]; // 右子数组元素较小,移动右指针
        }
    }

    // 处理左子数组的剩余元素(如果有)
    while (left <= middle) {
        temp[index++] = array[left++];
    }

    // 处理右子数组的剩余元素(如果有)
    while (right <= end) {
        temp[index++] = array[right++];
    }

    // 将合并后的数据从temp复制回原数组的[start...end]区间
    for (int i = start; i <= end; i++) {
        array[i] = temp[i];
    }
}

// 测试方法
   public static void main(String[] args) {
        int[] array = {6, 3, 2, 1, 4, 5, 8, 7};
        int[] temp = new int[array.length];
        mergeSort(array, 0, array.length - 1, temp);
        System.out.println(Arrays.toString(array));
    }


网站公告

今日签到

点亮在社区的每一天
去签到