Java 算法-排序

排序算法分为内排和外排,区别是是否需要外存(什么意思??算了,先不care),内排不需要,外排需要,这里学习的是内排(以下说的排序都指内排)!

排序分为5大类

  • 插入排序(直接插入排序、希尔排序)
  • 选择排序(简单选择排序、堆排序)
  • 交换排序(冒泡排序、快速排序)
  • 归并排序
  • 基数排序

插入排序 - 直接插入排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)将第一个数和第二个数排序,然后构成一个有序序列。
(2)将第三个数插入进去,构成一个新的有序序列。
(3)对第四个数、第五个数……直到最后一个数,重复第二步。

/**
 * 直接插入排序
 * @param arrays
 */
private static void sort1(int[] arrays) {
    int length = arrays.length;
    for (int i = 1; i < length; i++) {
        // 待插入值
        int temp = arrays[i];
        // 从待插入元素的前一个开始比较
        int j = i - 1;
        // 如果待插入值大于arrays[j]的值
        while (j >= 0 && arrays[j] > temp) {
            // 那么arrays[j]的值往后挪一位,赋值给arrays[j + 1],此时arrays[j]还是原来arrays[j]的值,相当于位置j被占用了
            arrays[j + 1] = arrays[j];
            // 递减
            j --;
        }
        // 此时待插入的值要么在数组第一个位置,要么前面的数值比它小,把占用的位置赋值为待插入值
        arrays[j + 1] = temp;
        System.out.print("第" + i + "次排序:");
        print(arrays);
    }
}

打印结果:

第1次排序:12 35 (已排好序) 22 (等待插入值) 8 6 26 18 5 28 14 
第2次排序:12 22 35 (已排好序) 8 (等待插入值) 6 26 18 5 28 14 
第3次排序:8 12 22 35 (已排好序) 6 (等待插入值) 26 18 5 28 14 
第4次排序:6 8 12 22 35 (已排好序) 26 (等待插入值) 18 5 28 14 
第5次排序:6 8 12 22 26 35 (已排好序) 18 (等待插入值) 5 28 14 
第6次排序:6 8 12 18 22 26 35 (已排好序) 5 (等待插入值) 28 14 
第7次排序:5 6 8 12 18 22 26 35 (已排好序) 28 (等待插入值) 14 
第8次排序:5 6 8 12 18 22 26 28 35 (已排好序) 14 (等待插入值) 
第9次排序:5 6 8 12 14 18 22 26 28 35 (已排好序)

插入排序 - 希尔排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)数组的个数为length,取step=length/2,将下标差值为step的数分为一组,构成有序序列。

示例数组的个数length10,那么step=55组),则[12, 26]为一组,[35, 18]为一组,如此类推,然后利用直接插入排序为有序序列

(2)再取step=step/2 ,将下标差值为step的数分为一组,构成有序序列。

此时step=2, 将(1)排好序的数组分为2组,再利用直接插入排序为有序序列

(3)重复第二步,直到step=1执行简单插入排序。

/**
* 希尔排序
 * @param arrays
 */
private static void sort2(int[] arrays) {
    // 数组长度
    int length = arrays.length;
    // 差值,也就是组数
    int step = length / 2;
    // 如果差值不为0
    while (step != 0) {
        // 每组数据使用直接插入算法进行排序
        for (int i = 0; i < step; i++) {
            // 直接插入算法进行排序,每组数据的下标差值都是step
            for (int j = i + step; j < length; j = j + step) {
                // 待插入值
                int temp = arrays[j];
                // 从待插入元素的前一个开始比较
                int k = j - step;
                // 如果待插入值大于arrays[k]的值
                while (k >= 0 && arrays[k] > temp) {
                    // 那么arrays[k]的值往后挪一位,赋值给arrays[k + step],此时arrays[k]还是原来arrays[k]的值,相当于位置k被占用了
                    arrays[k + step] = arrays[k];
                    // 按step递减
                    k -= step;
                }
                // 此时待插入的值要么在数组第i个位置(小数组的首位),要么前面的数值比它小,把占用的位置赋值为待插入值
                arrays[k + step] = temp;
                System.out.print("有" + step + "个数组,第" + (i + 1) + "个小数组第" + (j / step) +"次排序:");
                print(arrays);
            }
        }
        // 差值每次除2,一直到差值为1
        step = step / 2;
    }
}

打印结果:

12A), 35, 22, 8, 6, 26A), 18, 5, 28, 14 (小数组使用直接插入算法排序)
有5个数组,第1个小数组第1次排序:12A35 22 8 6 26A18 5 28 145个数组,第2个小数组第1次排序:12 18 22 8 6 26 35 5 28 145个数组,第3个小数组第1次排序:12 18 5 8 6 26 35 22 28 145个数组,第4个小数组第1次排序:12 18 5 8 6 26 35 22 28 145个数组,第5个小数组第1次排序:12 18 5 8 6 26 35 22 28 14 
12A18 5A8 6A26 35A22 28A14 (小数组使用直接插入算法排序)
有2个数组,第1个小数组第1次排序:5A18 12A8 6A26 35A22 28A14A组前2个数据排序结果)
有2个数组,第1个小数组第2次排序:5A18 6A8 12A26 35A22 28A14A组前3个数据排序结果)
有2个数组,第1个小数组第3次排序:5A18 6A8 12A26 35A22 28A14A组前4个数据排序结果)
有2个数组,第1个小数组第4次排序:5A18 6A8 12A26 28A22 35A14A5个数据排序结果)
有2个数组,第2个小数组第1次排序:5 8 6 18 12 26 28 22 35 142个数组,第2个小数组第2次排序:5 8 6 18 12 26 28 22 35 142个数组,第2个小数组第3次排序:5 8 6 18 12 22 28 26 35 142个数组,第2个小数组第4次排序:5 8 6 14 12 18 28 22 35 261个数组,第1个小数组第1次排序:5 8 6 14 12 18 28 22 35 261个数组,第1个小数组第2次排序:5 6 8 14 12 18 28 22 35 261个数组,第1个小数组第3次排序:5 6 8 14 12 18 28 22 35 261个数组,第1个小数组第4次排序:5 6 8 12 14 18 28 22 35 261个数组,第1个小数组第5次排序:5 6 8 12 14 18 28 22 35 261个数组,第1个小数组第6次排序:5 6 8 12 14 18 28 22 35 261个数组,第1个小数组第7次排序:5 6 8 12 14 18 22 28 35 261个数组,第1个小数组第8次排序:5 6 8 12 14 18 22 28 35 261个数组,第1个小数组第9次排序:5 6 8 12 14 18 22 26 28 35 

选择排序 - 简单选择排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)遍历整个序列,将最小的数放在最前面。
(2)遍历剩下的序列,将最小的数放在最前面。
(3)重复第二步,直到只剩下一个数。

/**
 * 简单选择排序
 * @param arrays
 */
private static void sort3(int[] arrays) {
    int length = arrays.length;
    for (int i = 0; i < length; i++) {
        int temp = arrays[i];
        int position = i;
        // 选出剩下数组最小的数及其下标
        for (int j = i + 1; j < length; j++) {
            if (temp > arrays[j]) {
                temp = arrays[j];
                position = j;
            }
        }
        // 交换位置
        arrays[position] = arrays[i];
        arrays[i] = temp;
        System.out.print("第" + (i + 1) + "次排序:");
        print(arrays);
    }
}

打印结果:

取最小的放到前面
12 35 22 8 6 26 18 5 28 14 (5最小)
第1次排序:5 35 22 8 6 26 18 12 28 14 (6最小)
第2次排序:5 6 22 8 35 26 18 12 28 14 (8最小)
第3次排序:5 6 8 22 35 26 18 12 28 14 
第4次排序:5 6 8 12 35 26 18 22 28 14 
第5次排序:5 6 8 12 14 26 18 22 28 35 
第6次排序:5 6 8 12 14 18 26 22 28 35 
第7次排序:5 6 8 12 14 18 22 26 28 35 
第8次排序:5 6 8 12 14 18 22 26 28 35 
第9次排序:5 6 8 12 14 18 22 26 28 35 
第10次排序:5 6 8 12 14 18 22 26 28 35 

选择排序 - 堆排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)将序列构建成最大堆(最大堆的特性是,某个节点的值最多和其父节点的值一样大。这样,堆中的最大元素存放在根节点中;并且,在以某一个节点为根的子树中,各节点的值都不大于该子树根节点的值)。
(2)将根节点与最后一个节点交换,然后断开最后一个节点。
(3)重复第一、二步,直到所有节点断开。

构建堆的图:

调整成最大堆的图:

/**
* 堆排序
 * @param arrays
 */
private static void heapSort(int[] arrays) {
    int length = arrays.length;
    for (int i = length - 1; i > 0; i--) {
        // 建立最大堆
        buildMaxHeap(arrays, i);
        // 此时,数组最大的数值是第一个,交换位置,把最大的放到剩下数组的最后,不参与下一次的排序
        swap(arrays, i, 0);
        System.out.print("第" + (length - i) + "次排序:");
        print(arrays);
    }
}

/**
 * 建立最大堆
 * @param arrays
 * @param lastIndex
 */
private static void buildMaxHeap(int[] arrays, int lastIndex) {
    // i 为父节点
    for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
        // i * 2 + 1 为左子节点,暂时设为较大节点
        int biggerIndex = i * 2 + 1;
        // 如果左子节点存在
        if (biggerIndex <= lastIndex) {
            // biggerIndex + 1 为右节点,如果右节点存在
            if (biggerIndex < lastIndex) {
                // 如果左节点比右节点小
                if (arrays[biggerIndex] < arrays[biggerIndex + 1]) {
                    // 右节点为较大节点
                    biggerIndex += 1;
                }
            }
            // 如果父节点比较大的子节点小
            if (arrays[i] < arrays[biggerIndex]) {
                // 交换位置
                System.out.println(i + "<->" + biggerIndex);
                swap(arrays, i, biggerIndex);
            }
        }
    }
}

/**
 * 交换
 * @param arrays
 * @param i
 * @param j
 */
private static void swap(int[] arrays, int i, int j) {
    if (i == j) {
        return;
    }
    int temp = arrays[i];
    arrays[i] = arrays[j];
    arrays[j] = temp;
}

打印结果:

4<->9
3<->8
2<->5
0<->1
第1次排序:6 12 26 28 14 22 18 5 8 35 
1<->3
0<->1
第2次排序:8 6 26 12 14 22 18 5 28 35 
1<->4
0<->2
第3次排序:5 14 8 12 6 22 18 26 28 35 
2<->5
0<->2
第4次排序:18 14 5 12 6 8 22 26 28 35 
2<->5
第5次排序:5 14 8 12 6 18 22 26 28 35 
0<->1
第6次排序:6 5 8 12 14 18 22 26 28 35 
1<->3
0<->1
第7次排序:5 6 8 12 14 18 22 26 28 35 
0<->2
第8次排序:5 6 8 12 14 18 22 26 28 35 
0<->1
第9次排序:5 6 8 12 14 18 22 26 28 35 

交换排序 - 冒泡排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)将序列中所有元素两两比较,将最大的放在最后面。
(2)将剩余序列中所有元素两两比较,将最大的放在最后面。
(3)重复第二步,直到只剩下一个数。

/**
 * 下冒泡排序
 * @param arrays
 */
private static void bubbleSort(int[] arrays) {
    int length = arrays.length;
    for (int i = 0; i < length; i++) {
        for (int j = 0; j < length - i - 1; j ++) {
            if (arrays[j] > arrays[j + 1]) {
                swap(arrays, j, j + 1);
            }
        }
        System.out.print("第" + (i + 1) + "次排序:");
        print(arrays);
    }
}

打印结果:

第1次排序:12 22 8 6 26 18 5 28 14 35 
第2次排序:12 8 6 22 18 5 26 14 28 35 
第3次排序:8 6 12 18 5 22 14 26 28 35 
第4次排序:6 8 12 5 18 14 22 26 28 35 
第5次排序:6 8 5 12 14 18 22 26 28 35 
第6次排序:6 5 8 12 14 18 22 26 28 35 
第7次排序:5 6 8 12 14 18 22 26 28 35 
第8次排序:5 6 8 12 14 18 22 26 28 35 
第9次排序:5 6 8 12 14 18 22 26 28 35 
第10次排序:5 6 8 12 14 18 22 26 28 35 
/**
 * 上冒泡排序
 * @param arrays
 */
private static void bubbleSort(int[] arrays) {
    int length = arrays.length;
    for (int i = 0; i < length; i++) {
        for (int j = length - 1; j > i; j--) {
            if (arrays[j] < arrays[j - 1]) {
                swap(arrays, j, j - 1);
            }
        }
        System.out.print("第" + (i + 1) + "次排序:");
        print(arrays);
    }
}

打印结果:

第1次排序:5 12 35 22 8 6 26 18 14 28 
第2次排序:5 6 12 35 22 8 14 26 18 28 
第3次排序:5 6 8 12 35 22 14 18 26 28 
第4次排序:5 6 8 12 14 35 22 18 26 28 
第5次排序:5 6 8 12 14 18 35 22 26 28 
第6次排序:5 6 8 12 14 18 22 35 26 28 
第7次排序:5 6 8 12 14 18 22 26 35 28 
第8次排序:5 6 8 12 14 18 22 26 28 35 
第9次排序:5 6 8 12 14 18 22 26 28 35 
第10次排序:5 6 8 12 14 18 22 26 28 35

交换排序 - 快速排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)数组中选择一个基准点,一般为数组第一个数。
(2)设两个指示标志(low指向起始位置,high指向末尾位置),首先从high向前开始递减,如果发现有元素比该基准点的值小,就交换low和high位置的值;然后从low向后开始递增,发现有元素大于基准点的值,就交换low和high位置的值。
(3)重复第二步,直到low等于high,设相等值i。
(4)然后后把数组分为2数组,arrays[low, i-1]和arrays[i+1, high],继续(1)-(4)的步骤。

(1)-(3)步骤图:

/**
 * 快速排序
 * @param arrays
 */
private static void quickSort(int[] arrays) {
    quickSort(arrays, 0, arrays.length - 1);
}

/**
 * 快速排序
 * @param arrays
 * @param low
 * @param high
 */
private static void quickSort(int[] arrays, int low, int high) {
    if (low >= high) {
        return;
    }
    int base = arrays[low];
    int i = low, j = high;
    while (i != j) {
        while (arrays[j] > base && i != j) {
            j --;
        }
        while (arrays[i] < base && i != j) {
            i ++;
        }
        swap(arrays, i, j);
    }
    System.out.print("i=" + i + ":");
    print(arrays);
    quickSort(arrays, low, i - 1);
    quickSort(arrays, i + 1, high);
}

打印结果:

i=3:5 6 8 12 22 26 18 35 28 14 
i=0:5 6 8 12 22 26 18 35 28 14 
i=1:5 6 8 12 22 26 18 35 28 14 
i=6:5 6 8 12 14 18 22 35 28 26 
i=4:5 6 8 12 14 18 22 35 28 26 
i=9:5 6 8 12 14 18 22 26 28 35 
i=7:5 6 8 12 14 18 22 26 28 35

归并排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)分解,就是把整个集合的元素一直除2化分,一直化为到没有两个元素开始合并

(2)合并的时候,两个指标i,j,把值比较小的放入temp数组中,然后放入那个值的那一边的指标自加,继续比较。一直把值合部排完合并为一个数组,然后把temp数组复制回到原来数组就可以进入下一个递归。

图解:

因为合并的过程比较复杂,所以详细图解合并过程:

/**
 * 归并排序
 * @param arrays
 */
private static void mergeSort(int[] arrays) {
    mergeSort(arrays, 0, arrays.length - 1);
}

/**
 * 归并排序
 * @param arrays
 * @param start
 * @param end
 */
private static void mergeSort(int[] arrays, int start, int end) {
    if (start >= end) {
        return;
    }
    int middle = (start + end) / 2;
    // 分解 middle 前面的数组(包括middle)
    mergeSort(arrays, start, middle);
    // 分解 middle 后面的数组
    mergeSort(arrays, middle + 1, end);
    // 合并
    merge(arrays, start, middle, end);
    System.out.println(start + " -> " + end);
    print(arrays);
}

/**
 * 合并
 * @param arrays
 * @param start
 * @param middle
 * @param end
 */
private static void merge(int[] arrays, int start, int middle, int end) {
    // 临时数组的个数 = 2个数组的个数和
    int[] tempArrays = new int[end - start + 1];
    // 分解的左边数组第一个
    int i = start;
    // 分解的右边数组第一个
    int j = middle + 1;
    // 从0开始赋值
    int k = 0;
    // 左右数组进行比较,按顺序依次放入临时数组,从而组成有序序列
    while (i <= middle && j <= end) {
        // 比较 左边数组第i个数值和右边数组第j个数值
        if (arrays[i] < arrays[j]) {
            // 如果 左边数组第i个数值 < 右边数组第j个数值,左边数组第i个数值放入临时数组,i+1,j不变,继续比较
            tempArrays[k] = arrays[i];
            i ++;
        } else {
            // 如果 左边数组第i个数值 > 右边数组第j个数值,右边数组第j个数值放入临时数组,j+1,i不变,继续比较
            tempArrays[k] = arrays[j];
            j ++;
        }
        // 每次k+1
        k ++;
    }
    // 把左边剩下数组的放入临时数组
    // 如左边数组最后2个数值都比右边的数组所有数值大,上面循环因j=end+1跳出循环,
    // 因为每一边的数组都是已经排好序的了,所以直接放入临时文件即可
    while (i <= middle) {
        tempArrays[k] = arrays[i];
        k ++;
        i ++;
    }
    // 把右边剩下数组的放入临时数组
    // 如右边数组最后2个数值都比左边的数组所有数值大,上面循环因i=middle+1跳出循环,
    // 因为每一边的数组都是已经排好序的了,所以直接放入临时文件即可
    while (j <= end) {
        tempArrays[k] = arrays[j];
        k ++;
        j ++;
    }
    // 临时数组的值覆盖原来数组对应的值
    for (int m = 0; m < tempArrays.length; m++) {
        arrays[start + m] = tempArrays[m];
    }
}

打印结果:

0 -> 1
12 35 22 8 6 26 18 5 28 14 
0 -> 2
12 22 35 8 6 26 18 5 28 14 
3 -> 4
12 22 35 6 8 26 18 5 28 14 
0 -> 4
6 8 12 22 35 26 18 5 28 14 
5 -> 6
6 8 12 22 35 18 26 5 28 14 
5 -> 7
6 8 12 22 35 5 18 26 28 14 
8 -> 9
6 8 12 22 35 5 18 26 14 28 
5 -> 9
6 8 12 22 35 5 14 18 26 28 
0 -> 9
5 6 8 12 14 18 22 26 28 35 

基数排序

示例数组:

int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};

(1)将所有的数的个位数取出,按照个位数进行排序,构成一个序列。
(2)将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。
(3)如此类推,直至最大数的最高位。

解释:(如示例数组)

个位排序:
0:
1:
2:12, 22
3:
4:14
5:35, 5
6:6, 26
7:
8:8, 18, 28
9:
个位数排序结果:{12, 22, 14, 35, 5, 6, 26, 8, 18, 28};

十位数排序:
0:5, 6, 8
1:12, 14, 18
2:22, 26, 28
3:35
4:
5:
6:
7:
8:
9:
十位数排序结果:{5, 6, 8, 12, 14, 18, 22, 26, 28, 35};

/**
 * 基数排序
 * @param arrays
 */
private static void radixSort(int[] arrays) {
    int length = arrays.length;
    // 取出数组中最大数
    int max = arrays[0];
    for (int i = 1; i < length; i++) {
        if (arrays[i] > max) {
            max = arrays[i];
        }
    }
    // 计算最大数的位数
    int count = 0;
    while (max != 0) {
        max = max / 10;
        count ++;
    }
    // 临时保存数值的二维数组
    int[][] tempArrays = new int[10][length];
    // 记录有多少个同时在tempArrays[i]中
    int[] recordArrays = new int[10];
    // 从最低位开始
    for (int i = 0; i < count; i++) {
        // 每个数值取出第i位数据保存到tempArrays数组中
        // 如i=0,123的个位数是3,此时recordArrays[3]=0,所以保存在tempArrays[3][0],recordArrays[3]+1=1
        // 再来一个数为223,个位数还是3,此时recordArrays[3]=1,所以保存在tempArrays[3][1],recordArrays[3]+1=2
        for (int j = 0; j < length; j++) {
            // 取出第i位数值
            int number = arrays[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10,  i);
            // 保存在二维数组对应的位置
            tempArrays[number][recordArrays[number]] = arrays[j];
            // 对应的记录数+1
            recordArrays[number] ++;
        }
        // 把临时保存的数值排列到arrays数组中,重组
        int index = 0;
        // 从0开始排序
        for (int m = 0; m < 10; m++) {
            if (recordArrays[m] == 0) continue;
            // 如recordArrays[3]=2,tempArrays[3][0]和tempArrays[3][1]必定有值,赋值给arrays数组
            for (int k = 0; k < recordArrays[m]; k++) {
                arrays[index] = tempArrays[m][k];
                // 从0递增,最后肯定是length-1
                index ++;
            }
            // 清除记录数,下次循环用到
            recordArrays[m] = 0;
        }
        System.out.print("第" + (i + 1) + "位数排序:");
        print(arrays);
    }
}

打印结果:

第1位数排序:12 22 14 35 5 6 26 8 18 28 
第2位数排序:5 6 8 12 14 18 22 26 28 35 

时间、空间复杂度、稳定程度表

最后附上各种排序时间、空间复杂度、稳定程度表

排序方法最差时间分析平均时间复杂度稳定度空间复杂度
直接插入排序O(n²)O(n²)稳定O(1)
希尔排序O(n²)O(n¹³)不稳定O(1)
选择排序O(n²)O(n²)不稳定O(1)
堆排序O(n*log₂n)O(n*log₂n)不稳定O(1)
冒泡排序O(n²)O(n²)稳定O(1)
快速排序O(n²)O(n*log₂n)不稳定O(log₂n)
归并排序O(n*log₂n)O(n*log₂n)稳定不一定
基数排序O(d(r+n))O(d(r+n))稳定O(rd+n)
注:基数排序的复杂度中,r代表关键字的基数,d代表长度,r代表关键字的个数

比较

快速排序是所有排序算法中实际性能最好的,然而快速排序在最坏情况下的时间性能不如堆排序和归并排序。这一点可以通过对快速排序进行改进来避免,一种通过随机选择枢轴元素的随机快速排序,可以使得出现最坏情况出现的几率非常小,在实际的运用中可以认为不存在。在堆排序和归并排序的比较中,当n 较大时,归并排序所需时间较少,然而它需要较多的辅助存储空间。

小结

如果要求排序速度快的话,选择快速排序

如果只需要取最大或最小的几个,选择选择排序堆排序

如果要求使用内存小和速度快的话,选择归并排序

如果把一个有序序列的数组和无序序列数组合并成有序数组,选择插入排序希尔排序

如果待排序的数组数量很大的话,而且内存充足,选择基数排序

参考资料

一遍记住Java常用的八种排序算法与代码实现
Java 八种排序算法比较实践

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值