基于比较的常见排序方式

几种常见的基于比较的排序方式

排序简介

在这里插入图片描述
这些排序都给了示例图,可能没有视频那么生动,但图片也有视频没有的好处,那就是读者可以仔细斟酌每一处的细节,接下来让我们来感受排序算法的魅力吧!

交换排序
1.冒泡排序
思想:
两个相邻元素两两比较,依次向后,一趟确定一个元素位置。
说明:
冒泡排序有两层循环,第一层循坏是确定比较趟数,第二层循环确定元素位置,每确定一个元素的位置,检索数组长度减少一位。
示例:
这个数组其实第六趟已经有序了,但是程序还是会继续比较下去,因而可以设置一个状态值来判断数组是否已经有序,读者可以自行编写代码,这里不再展开。
在这里插入图片描述
代码:

public void bubbleSort(int[] array){
    for (int i = 0; i < array.length - 1; i++) {
        for (int j = 0; j < array.length - 1 - i; j++) {
            if(array[j] > array[j + 1]){
                int temp = array[j];
                array[j]= array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

2.快速排序
思想:
以一个数为基准,把数组分为两段(比基准数小的为一段,比基准大的数为一段),此时基准数是有序的,再以这两段数组为新数组,递归下去,直到所有的基准数有序,也就是数组有序了。
说明:
快速排序对基准数的选择非常重要,如果每次基准数都能把数组对半分割,那么快速排序的时间复杂度为 O(N * log N) ,反过来,如果基准数每次都是在分割数组中最大的数,或者最小的数,那么数组分割效率非常低,此时的时间复杂度为 O( N ^ 2 ) 。
示例: 可以看到第三趟和第四躺一模一样,这是因为第三躺的基准数为数组中最小的数,因而排序时效率非常低,此时只能等待第二趟排序的右半排序来排序,由此可见基准数选取的重要性!这里强烈建议读者自己来走一遍代码,这样才能更加熟悉快速排序的排序时的代码流程。
在这里插入图片描述
代码:

public int partition(int[] array, int low, int high) {
    int pivot = array[low];
    while (low < high) {
        while (low < high && array[high] >= pivot) {
            high--;
        }
        array[low] = array[high];
        
        while (low < high && array[low] <= pivot) {
            low++;
        }
        array[high] = array[low];
    }
    array[low] = pivot;
    return low;
}

public void quick(int[] array, int start, int end) {
    if (start >= end) {
        return;
    }

    int pivot = partition(array, start, end);
    quick(array, start, pivot - 1);
    quick(array, pivot + 1, end);
}

public void quickSort(int[] array) {
    quick(array, 0, array.length - 1);
}

插入排序
1.直接插入排序
思想:
每次保证数组检索到当前是有序的,直到检索完整个数组。
说明:
直接插入排序相对于其它的排序,变得智能了,而且就算当前的数组已经有序,它也能快速的跳出,因为它总能保证当前检索位置的前面所有元素全是有序的,因而它只要比较当前位置的前面一位就可以跳出本躺排序。
示例: 可以看到第六趟和第七躺一模一样,因为第六躺的时候第七个位置已经有序了,但这其实无关紧要,因为当前位置的前面所有元素一定是有序的,遇到这种情况也只是与前一个的元素比较一次罢了。
在这里插入图片描述
代码:

public void directInsertSort(int[] array){
    for (int i = 1; i < array.length; i++) {
        int j = i - 1;
        int temp = array[i];
        for (; j >= 0 && array[j] > temp; j--) {
            array[j + 1] = array[j];
        }
        array[j + 1] = temp;
    }
}

2.希尔排序
思想:
希尔排序是直接插入排序的优化版,与直接插入排序一样的思想一样,都是每次保证当前检索的数组有序。但是希尔排序是把数组分为多段,且每个数组的元素都是跳跃式的,这样的做法使得移动元素的速度大大提高。当当前的多段数组全部有序时,再次将数组分段,注意,每次分段数组的段数应该越来越少,最后一次是一段(全排序)。
说明:
希尔排序在排序大量的元素时,很有优势,移动元素速度很快,弥补了直接插入排序的不足,但是在排序少量的元素时,显得不尽人意。有时排序少量元素也很快(例如排为升序时,较小的元素基本在最后面,那么这些元素也能很快的排到前面去)。另外,关于 gap 的分段方式也直接影响希尔排序的效率(但是到目前为止,仍然没有一个很好的方式来规定 gap 的分段方式,这里以 gap = gap / 3 + 1 为例,但这仍然是一个不好的分段方式)。

示例: 注意,希尔排序分段数组时,在 gap位置从后向前取元素,而且有些元素很有可能多次被选取,这么做的目的就是让后面的数组中较小的元素能尽快排到前面去!可能说起来可能有点抽象,我很建议读者去跟着代码走一遍!这样才能体会到希尔排序的魅力!这里从过程来看,希尔排序确实有点难尽人意,但不要就此觉得希尔排序不堪一击,当排序的数据非常多时,希尔排序一定会让你惊掉下巴,因为它移动元素的速度实在太快了!

在这里插入图片描述
在这里插入图片描述
代码:

/**
 * @param array 排序的数组
 * @param gap   每段数组元素的间隔 也可以理解成组数
 */
public void shell(int[] array, int gap){
    for (int i = gap; i < array.length; i++) {
        int temp = array[i];
        int j = i - gap;
        for (; j >= 0 && array[j] > temp; j -= gap) {
            array[j + gap] = array[j];
        }
        array[j + gap] = temp;
    }
}

public void shellSort(int[] array){
    int gap = array.length;
    while(gap > 1){
        gap = gap / 3 + 1;
        shell(array,gap);
    }
}

选择排序
1.简单选择排序:
思想:
第一个元素和第一个元素后面的所有的元素比较,确定第一个位置是有序的数,依次向后,直到每个位置都有序为止。
说明:
简单选择排序第一层循坏是确定比较趟数,第二层循坏是确定元素位置,每确定一个位置检索数组长度减一。
示例: 简单选择排序的比较趟数是确定的,可以看到第六躺和第七躺是一模一样的,因为第六躺时,正好第七个位置也有序了,但程序仍然会检索下去,此外第九躺和第十躺也是一样的(当然,这肯定可以优化,读者可自行展开)。
在这里插入图片描述
代码:

public void selectSort(int[] array){
    for (int i = 0; i < array.length; i++) {
        for (int j = i + 1; j < array.length; j++) {
            if(array[i] > array[j]){
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
}

2.堆排序
思想:
堆排序利用了堆的自组织这个特点,让堆的堆顶元素与堆尾元素互换,然后堆尾元素有序,再让堆自组织长度减一,然后堆又自组织,再堆顶元素与堆尾元素互换,直到堆自组织长度为0,此时所有元素有序。
说明: 堆是一个极其精巧的数据结构,逻辑上是一个自组织的二叉树,底层物理上是一个优先级队列,二叉树父子节点之间总是存在 child = 2 * parent + 1; 或是 child = 2 * parent + 2; 这两种关系,利用这个来组织父子节点之间的数据,从而组织整个二叉树。
示例: 不要觉得堆排序繁琐,堆排序每次都是对半检索,检索的速度非常快,而且堆在逻辑上是一棵完全二叉树,不存在单分支树的情况,因而堆排序的效率是固定的,排序速度十分快!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码:

//建立大堆 从小到大排序
public void createHeap(int[] array) {
    for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
        siftDown(array, i, array.length);
    }
}

//堆的自组织
public void siftDown(int[] array, int root, int len) {
    int parent = root;
    int child = 2 * parent + 1;
    while (child < len) {
        if (child + 1 < len && array[child] < array[child + 1]) {
        // 这里的child一定是兄弟节点中最大的元素。
            child++;
        }

        if (array[child] > array[parent]) {
            int temp = array[child];
            array[child] = array[parent];
            array[parent] = temp;
            parent = child;
            child = 2 * parent + 1;
        } else {
            break;
        }
    }
}

public void heapSort(int[] array) {
    createHeap(array);
    int end = array.length - 1;
    while (end > 0) {
        int temp = array[end];
        array[end] = array[0];
        array[0] = temp;
        siftDown(array, 0, end);
        end--;
    }
}

归并排序
思想:
归并排序利用递归把数组分为长度为1和长度为2的片段,再回溯合并,使子片段依次有序,回溯大片段也有序.
说明:
归并排序让子数组全部有序,再对两个数组依次检索元素,合并成新数组,依次合并,直到所有数组全部合并完成。
示例:在这里插入图片描述
代码:

public void mergeSortInternal(int[] array, int low, int high){
    if(low >= high){
        return;
    }
    
    int mid = (low + high) / 2;
    mergeSortInternal(array, low, mid);
    mergeSortInternal(array, mid + 1, high);
    //合并
    merge(array, low, mid, high);
}

public void merge(int[] array, int low, int mid, int high){
    int s1 = low;
    int e1 = mid;
    int s2 = mid + 1;
    int e2 = high;
    int[] temp = new int[high - low + 1];
    int k = 0;
    while(s1 <= e1 && s2 <= e2){
        if(array[s1] <= array[s2]){
            temp[k++] = array[s1++];
        }else{
            temp[k++] = array[s2++];
        }
    }

    while(s1 <= e1){
        temp[k++] = array[s1++];
    }

    while(s2 <= e2){
        temp[k++] = array[s2++];
    }

    for (int i = 0; i < temp.length; i++) {
        array[i + low] = temp[i];
    }
}

public void mergeSort(int[] array){
    mergeSortInternal(array, 0 ,array.length - 1);
}

版权声明:本文为m0_53120156原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。