排序算法学习

前言

算法,其实算术题的解法,如同我们读书时做的数学题。一道关于排序的算术题,有几种解法就有几种思路。一般程序员可能一辈子都用不上排序算法,但是我们可以学习其中的解题思路,融会贯通后,可以对我们实际开发有指导作用。

题目

 数量为10的无序数组,将其按照升序排列。例如

选择排序

如果这样的数组放在面前,要你手工排序,一般人都是先浏览一遍,找出最小的19拿走,再浏览剩下的,再拿走最小的22,直到全部取走,到手的就是有序集合。其实这种做法就是选择排序法。

当然,程序实现上为了节省内存,会用交换代替取的。如

public class SelectSort {
    public static int[] sort(int[] sourceArray) {
        int[] array = Arrays.copyOf(sourceArray, sourceArray.length);
        int length = array.length;
        for (int i = 0; i < length-1; i++) {
            int min = i;
            for (int j = i + 1; j < length; j++) {
                if (array[min] > array[j]) {
                    min = j;
                }
            }
            swap(array,i,min);
        }
        return array;
    }

    static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

冒泡排序

冒泡排序形象点描述是用泡泡装着最右的元素,不断向左前进比较,泡泡里面的元素较大就交换,较小就继续前进,直到到达最左。

public class BubbleSort {
    public static int[] sort(int[] sourceArray) {
        int[] array = Arrays.copyOf(sourceArray, sourceArray.length);
        int length = array.length;
        for (int i = 0; i < length; i++) {
            for (int j = length - 1; j > i; j--) {
                if (array[j] < array[j - 1]) {
                    swap(array, j, j - 1);
                }
            }
        }
        return array;
    }
    static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

插入排序

插入排序的主要思路是将数组分成有序和无序两虚拟集合。

初始状态时,38是有序集合,19到66都是无序集合。

然后将无序集合的第一个元素19存起来,从右往左对比有序集合中的元素,如

和有序集合中的38比较,38较大,移动38到下一个位置,当19走到有序集合的尽头或者遇到比19小的元素时,插入19,如

现在19和38是有序集合的元素,其余的都是无序集合,循环上述操作,直到无序集合的最后一个元素。代码实现如下

public class InsertSort {
    public static int[] sort(int[] sourceArray) {
        int[] array = Arrays.copyOf(sourceArray, sourceArray.length);
        int length = array.length;
        for (int i = 1; i < length; i++) {
            int tmp = array[i];
            int index = i;
            for (int j = i - 1; j >= 0; j--) {
                if (tmp < array[j]) {
                    array[j + 1] = array[j];
                    index = j;
                } else {
                    break;
                }
            }
            array[index] = tmp;
        }
        return array;
    }
}

希尔排序

上面的插入排序是通过不断移动较大的元素后移以达到排序的目的,例如4要移动最前面,要移动5次前面的元素。如果要优化插入排序的话,可以从减少移动次数下手,所以希尔排序应运而生。

 希尔排序引入步长的概念,将数组分成数个虚拟数组,例如步长为10/2=5,那么分组后如下,同色的为一组

然后每一组使用插入排序法排序后,如

然后再缩小步长,例如步长为5/2向下取整2,分成

再对这两组进行插入排序,如

缩小步长,步长为2/1=1,这个时候就相当于普通的插入排序。

代码实现上,和插入排序相似,只不过再最外层加多一个缩小步长的循环,直到步长小于1,如

public class ShellSort {
    public static int[] sort(int[] sourceArray) {
        int[] array = Arrays.copyOf(sourceArray, sourceArray.length);
        int gap = array.length/2;
        while (gap >= 1) {
            for (int i = gap; i< array.length; i++) {
                int tmp = array[i];
                int index = i;
                for (int j = i-gap; j >= 0; j = j - gap) {
                    if (tmp < array[j]) {
                        array[j + gap] = array[j];
                        index = j;
                    } else {
                        break;
                    }
                }
                array[index] = tmp;
            }
            gap = gap / 2;
        }
        return array;
    }
}

快速排序

顾名思义,在一般情况下,快速排序是速度最快的排序。

它的解题思路是选一个基准值出来,例如数组第一个位置38,通过移动位置分成三个虚拟数组,左边是小于等于38的,中间是38,右边是大于等于38的。

首先先从右边遍历和对比大小,直到找到小于38的值停下,然后从左边遍历,直到找到大于38的停下,如

这个时候交换左右位置,如下

右边继续遍历,找到小于38的21值停下来,左边开始遍历,到21的位置时,发现左边等于右边,也停下来

这个时候,除了左右到达的位置,左边走过的路都是小于等于38的,右边走过的路都是都是大于等于38,那么只要将基准值和21交换就完成三个虚拟数组的划分了

之后,将左右数组重复上面步骤,最终会得到一个有序数组。

 

public class QuickSort {

    public static int[] sort(int[] sourceArray) {
        int[] array = Arrays.copyOf(sourceArray, sourceArray.length);
        quickSort(array, 0, sourceArray.length - 1);
        return array;
    }

    static void quickSort(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }
        int low = left;
        int high = right;
        int tmp = array[left];

        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            while (left < right && array[left] <= tmp) {
                left++;
            }
            if (left != right) {
                swap(array, left, right);
            }
        }
        swap(array, low, left);
        quickSort(array, low, left - 1);
        quickSort(array, left + 1, high);
    }

    static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

归并排序

归并排序常见于数据库排序,将扫描的数据生成多个磁盘文件,再归并排序。

它的主要思路比较简单粗暴,就是是将数组通过递归地方式拆分到单个元素,再通过不断两两合并成为有序数组。

我觉得它的精髓在于递归方式的运用,发散性地递归。

public class MergeSort {
    public static int[] sort(int[] sourceArray) {
        int[] array = Arrays.copyOf(sourceArray, sourceArray.length);
        int length = array.length;
        if (length == 1) {
            return array;
        }
        int middle = (int) Math.floor(length / 2);

        int[] leftArray = Arrays.copyOfRange(sourceArray, 0, middle);
        int[] rightArray = Arrays.copyOfRange(sourceArray, middle, length);

        return merge(sort(leftArray), sort(rightArray));
    }

    static int[] merge(int[] leftArray, int[] rightArray) {
        int[] array = new int[leftArray.length + rightArray.length];
        int i = 0, j = 0, k = 0;
        while (i < leftArray.length && j < rightArray.length) {
            if (leftArray[i] < rightArray[j]) {
                array[k++] = leftArray[i++];
            } else {
                array[k++] = rightArray[j++];
            }
        }
        while (i < leftArray.length) {
            array[k++] = leftArray[i++];
        }
        while (j < rightArray.length) {
            array[k++] = rightArray[j++];
        }
        return array;
    }
}

堆排序

每一个数组都可以看成一颗完全二叉树,特点是每一个非叶子节点i,左子节点在数组的2i+1位置上,右节点在2i+2位置。简单来说,将数组从上往下从左往右铺满,可以看到非叶子节点都是在数组的前面,如下

 

而堆也是完全二叉树的一种,分为最大堆和最小堆,最大堆的特点是每一个父节点都大于等于子节点,所以在根节点的元素在数组的第一个位置,同时也是最大值。

所以堆排序的解题思路就是,从最后一个非叶子节点遍历,比较子节点大小,交换最大值位置,就这样将数组调整成最大堆,拿走根节点放在数组的末尾,第二次将剩余的元素继续调整成堆,拿走根节点放在数组的次末尾,重复上述操作,直到剩余元素只有一个。

public class HeapSort {

    public static int[] sort(int[] sourceArray) {
        int[] array = Arrays.copyOf(sourceArray, sourceArray.length);

        for (int i = array.length - 1; i > 0; i--) {
            buildMaxHeap(array, i);
            swap(array, 0, i);
        }
        return array;
    }

    static void buildMaxHeap(int[] array, int lastLeaf) {
        //计算最后一个非叶子节点的位置
        int lastNonLeaf;
        if (lastLeaf % 2 == 1) {
            lastNonLeaf = (lastLeaf - 1) / 2;
        } else {
            lastNonLeaf = lastLeaf / 2 - 1;
        }
        for (int i = lastNonLeaf; i >= 0; i--) {
            int left = 2 * i + 1;
            int right = 2 * i + 2;
            if (array[left] > array[i]) {
                swap(array, left, i);
            }
            if (right <= lastLeaf && array[right] > array[i]) {
                swap(array, right, i);
            }
        }
    }

    static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

 

测试代码  https://github.com/mycaizilin/sort

posted @ 2019-05-17 22:15  Caizl  阅读(152)  评论(0编辑  收藏  举报