排序——归并与快速排序

版权声明:本文为博主原创文章,转载注明出处即可。 https://blog.csdn.net/bskfnvjtlyzmv867/article/details/82497352

I. 归并排序

归并排序思想

  1. 将数组一分为二(折半);
  2. 分别将两部分数组进行排序;
  3. 将排序好的两部分数组进行合并成新的有序数组。

动态图演示

归并排序

算法实现

利用分治算法,自顶向下的进行递归排序。动态图演示则为自下往上的排序演示。

public static void main(String[] args) {
    int[] array = {53, 34, 32, 56, 62, 121, 55, 41};
    sort(array, 0, array.length - 1);
    ArrayUtils.printArray(array);
}

private static void sort(int[] array, int start, int end) {
    // 递归结束
    if (start >= end) {
        return;
    }
    int mid = (end + start) / 2;
    sort(array, start, mid);  // 左半排序
    sort(array, mid + 1, end); // 右半排序
    merge(array, start, mid + 1, end); // 归并
}

/**
  * 两个有序的数组(start->mid - 1, mid->end)原地归并
  * @param array 数组
  * @param start 开始
  * @param mid 中间
  * @param end 结束
  */
public static void merge(int[] array, int start, int mid, int end) {
    int[] copyArray = new int[end - start + 1];
    for (int i = 0; i < copyArray.length; i++) {
        copyArray[i] = array[i + start];
    }
    // 定义两个数组的指针,向后遍历比较
    int leftIndex = start, rightIndex = mid;
    for (int i = start; i <= end; i++) {
        if (leftIndex >= mid) {
            // 如果左边搞完
            array[i] = copyArray[rightIndex - start];
            rightIndex++;
        } else if (rightIndex > end) {
            // 如果右边边搞完
            array[i] = copyArray[leftIndex - start];
            leftIndex++;
        } else if (copyArray[leftIndex - start] <= copyArray[rightIndex - start]) {
            // 如果左边比右边小
            array[i] = copyArray[leftIndex - start];
            leftIndex++;
        } else if (copyArray[leftIndex - start] > copyArray[rightIndex - start]) {
            // 如果右边比左边小
            array[i] = copyArray[rightIndex - start];
            rightIndex++;
        }
    }
}

算法评价

排序方法 平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 稳定性
归并排序 O ( n l o g 2 n ) O ( n l o g 2 n ) O ( n l o g 2 n ) O ( n ) 稳定

- 归并排序的时间复杂度都是 O ( n l o g 2 n ) ,但其需要额外的空间开销;
- 和选择排序一样,归并排序不受输入序列的影响,但表现比选择排序好的多;
- 归并排序并不会影响相同大小数据之间的顺序,所以稳定。

II. 快速排序

快速排序思想

  1. 将数组分为两部分,使得前一部分的最大值不大于后一部分的最小值(切分处的元素已经排定);
  2. 将两部分数组进行分别排序;
  3. 前一部分、切分元素与后一部分合并成有序数组。

选定切分位置:

  1. 随意的选取左边数组的第一个元素作为切分元素;
  2. 定义首尾指针,头指针从左向右遍历,寻找比切分元素大的元素,尾指针从右向左遍历,寻找比切分元素小的元素,将两个元素进行交换,使得大的在后,小的在前;
  3. 不断的进行首尾指针移动,直到首尾相遇,这样就能保证小的元素在头指针左侧,大的元素在尾指针右侧;
  4. 将首位的切分元素与头指针指向的元素位置进行交换,这样快速排序的第一步便完成。

动态图演示

快速排序

算法实现

public static void main(String[] args) {
    int[] array = {53, 34, 32, 56, 62, 121, 55, 41};
    sort(array, 0, array.length - 1);
    ArrayUtils.printArray(array);
}

public static void sort(int[] array, int start, int end) {
    if (start >= end) {
        return;
    }
    int mid = partition(array, start, end);
    sort(array, start, mid - 1);
    sort(array, mid + 1, end);
}

/**
  * 寻找切分元素,将数组分成左右两部分
  * @param array 数组
  * @param start 开始
  * @param end 结束
  * @return 切分元素索引
  */
private static int partition(int[] array, int start, int end) {
    // 定义首尾指针位置
    int leftIndex = start, rightIndex = end + 1;

    while (true) {
        // 寻找左边大于切分元素
        while (array[++leftIndex] <= array[start]) {
            if (leftIndex == end) {
                break;
            }
        }
        // 寻找右边小于切分元素
        while (array[--rightIndex] >= array[start]) {
            if (rightIndex == start) {
                break;
            }
        }
        // 寻找到的进行替换
        if (leftIndex >= rightIndex) {
            break;
        }
        int temp = array[leftIndex];
        array[leftIndex] = array[rightIndex];
        array[rightIndex] = temp;
    }

    // 最终交换切分位置
    int temp = array[start];
    array[start] = array[rightIndex];
    array[rightIndex] = temp;

    return rightIndex;
}

算法分析

排序方法 平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 稳定性
快速排序 O ( n l o g 2 n ) O ( n 2 ) O ( n l o g 2 n ) O ( n l o g 2 n ) 不稳定

- 快速排序的时间复杂度与归并排序相当,但是最坏时间复杂度仍然是 O ( n 2 )
- 空间复杂度由于需要交换位置,需要额外开辟空间;
- 快速排序由于寻找切分元素会进行交换位置,切分元素最后也会交换位置,破坏了稳定性;
- 快速排序的切分需要尽可能的将数组切成差不多长的两部分,才能保证快速。否则,每一只切一个元素,导致一个大数组需要切很多次。解决该问题,一般排序前将数组重新混洗,或者寻找切分元素时,随机确定一个起始切分元素。

算法改进

如果数组中存在大量重复元素,我们可以进行三向切分,每次分为小于切分元素、等于切分元素和大于切分元素三部分。对应的一次排布操作如下:

  • 维护三个指针,i、ltgt ,分别代表遍历指针、小于切分元素部分的边界和大于切分元素部分的边界;
  • 初始 lt 为数组的起始,gt 为数组的末尾,i 为数组的第二位,切分元素也定义为数组的起始元素;
  • i 开始向右遍历,当该元素小于切分元素,该元素与 lt 所在元素位置交换, lti 都加 1;
  • 当该元素大于切分元素,该元素与 gt 所在元素位置交换,gt 减 1;
  • 当该元素与切分元素相等,则直接 i 加 1。

三项切分

改进实现

/**
  * 三向切分的快速排序
  * @param array
  * @param start
  * @param end
  */
public static void sort3way(int[] array, int start, int end) {
    // 递归条件
    if (start >= end) {
        return;
    }

    // 定义三个指针和切分元素
    int lt = start, i = start + 1, gt = end;
    int div = array[start];

    while (i <= gt) {
        if (array[i] < div) {
            int temp = array[i];
            array[i] = array[lt];
            array[lt] = temp;
            i++;
            lt++;
        } else if (array[i] > div) {
            int temp = array[i];
            array[i] = array[gt];
            array[gt] = temp;
            gt--;
        } else {
            i++;
        }
    }

    sort(array, start, lt - 1);
    sort(array, gt + 1, end);
}

参考文章

猜你喜欢

转载自blog.csdn.net/bskfnvjtlyzmv867/article/details/82497352