几种基础排序算法总结

157 阅读7分钟
原文链接: www.jianshu.com

稳定的排序

算法 时间复杂度 空间复杂度
冒泡排序 最差、平均都是O(n^2),最好是O(n) 1
插入排序 最差、平均都是O(n^2),最好是O(n) 1

不稳定的排序

算法 时间复杂度 空间复杂度
选择排序 最差、平均都是O(n^2) 1
希尔排序 O(nlog n) 1
快速排序 平均是O(nlog n),最坏情况下是O(n^2) O(log n)

冒泡排序

/**
 * 冒泡排序原理:从头部开始,两两对比,并交换调整更大(小)值在数组中的位置,把未有序部分中最大(小)值移动到有序部分的头部。
 * 就像“冒泡”一样,把无序部分中的最大(小)值,移动到有序部分。
 *
 * @author Affy
 * @date 2018-09-20
 */
public class BubbleSort {

    public static void bubbleSort(int[] source) {
        int unSortIndex = 0;
        int sortHeadIndex = source.length - 1;
        System.out.print("before sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }

        boolean exchange = false;
        int times = 0;
        for (int high = sortHeadIndex; high > unSortIndex; high--) {
            exchange = false;
            times++;
            //两两,把更大的值往后“冒”,直到有序序列的头部位置
            for (int low = 0; low < sortHeadIndex; low++) {
                if (source[low] > source[low + 1]) {
                    int temp = source[low];
                    source[low] = source[low + 1];
                    source[low + 1] = temp;
                    exchange = true;
                }
            }
            //如果存在某一次排序没有发生交换,证明无序区中所有元素都有序,可以提前终止算法了
            if (!exchange) {
                break;
            }
        }

        System.out.print("\n第" + times + "趟完成排序" + "\nafter sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }
    }

    public static void main(String[] args) {
        int[] source = {44, 5, 9, 7, 3, 10, 4, 6, 1, 7, 12, 51};
        bubbleSort(source);
    }
}

  1. 算法的最好时间复杂度
    若文件的初始状态是有序的,则一次扫描就可完成排序,则比较次数C=n-1,移动次数M=0;也就是时间复杂度为O(n^2)。
  2. 算法的最坏时间复杂度
    若文件的初始状态是逆序,则需要进行n-1次排序。每次排序要进行n-i次关键字的比较(1<= i <= n-1),每次比较都必须移动记录三次来达到交换记录位置。此时比较次数C=n(n-1)/2 = O(n^2 ),交换次数M=3n(n-1)/2 = O( n^2 )。也就是时间复杂度为O(n^2)。

选择排序

/**
 * 每趟排序,从未排序序列中选择最小(大)值,放到有序序列的末尾
 *
 * @author Affy
 * @date 2018-09-21
 */
public class SelectSort {

    public static void selectSort(int[] source) {
        System.out.print("before sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }

        for (int low = 0; low < source.length - 1; low++) {
            //每次遍历,找到未排序队列中的最小值,并标记其下标
            int minimumIndex = low;
            for (int high = low + 1; high < source.length; high++) {
                if (source[minimumIndex] > source[high]) {
                    minimumIndex = high;
                }
            }
            if (minimumIndex != low) {
                int temp = source[low];
                source[low] = source[minimumIndex];
                source[minimumIndex] = temp;
            }
        }

        System.out.print("\nafter sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }
    }

    public static void main(String[] args) {
        int[] source = {44, 5, 9, 7, 3, 10, 4, 6, 1, 7, 12, 51};
        selectSort(source);
    }
}

选择排序的交换操作介于0和(n-1)次之间;比较操作为n(n-1)/2次之间;赋值操作介于0和(n-1)次之间;平均复杂度为O(n^2)

插入排序

/**
 * <li>从第一个元素开始,该元素可以认为已经被排序</li>
 * <li>取出下一个元素,在有序序列中从后往前扫描</li>
 * <li>如果取出的新元素比当前元素小,则将当前元素移动到下一位置</li>
 * <li>重复上一步,直到新元素小于或等于前一个元素,记录该位置,并将新元素插入到该位置</li>
 *
 * @author Affy
 * @date 2018-09-21
 */
public class InsertSort {
    public static void insertSourt(int[] source) {
        System.out.print("before sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }

        for (int low = 1; low < source.length - 1; low++) {
            //取出有序队列末尾的后一个元素,从后往前遍历。如果比当前元素小,则将当前元素移动一个位置(其实就是交换两元素)
            for (int high = low + 1; high > 0 && source[high] < source[high - 1]; high--) {
                int temp = source[high];
                source[high] = source[high-1];
                source[high-1] = temp;
            }
        }

        System.out.print("\nafter sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }
    }

    public static void main(String[] args) {
        int[] source = {44, 5, 9, 7, 3, 10, 4, 6, 1, 7, 12, 51};
        insertSourt(source);
    }
}

平均来说,插入排序算法复杂度为O(n^2)。因此不适合对于数据量比较大的排序应用。但是,如果量级小于千时,那么插入排序还是一个不错的选择。

希尔排序

shell排序分组
/**
 * 先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。
 * 先在各组内进行直接插入排序;
 * 然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<;…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
 * 该方法实质上是一种分组插入方法。
 *
 * @author Affy
 * @date 2018-09-25
 */
public class ShellSort {
    public static void shellSort(int[] source, int distance) {
        System.out.print("\nbefore sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }
        int high, low;
        //从第一个间隔开始,依次对source[d]...source[n]进行处理,这样就能确保覆盖到每个元素
        for (high = distance; high < source.length; high++) {
            //将位置high挖空
            int temp = source[high];
            int times = 0;
            for (low = high - distance; low >= 0 && source[low] > temp; low -= distance) {
                //后移
                source[low + distance] = source[low];
            }
            //循环最后low被减掉了distance,最终需要加回来
            source[low + distance] = temp;
        }

        System.out.print("\nafter sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }
    }

    public static void main(String[] args) {
        int[] source = {44, 5, 9, 7, 3, 10, 4, 6, 1, 7, 12, 51};
        int increment = source.length;
        do {
            increment /= 2;
            shellSort(source, increment);
        } while (increment > 0)
    }
}

快速排序

1 2 3 4 5 6 7 8 总时序.jpg

以上图片来源于网络

/**
 * 快排采用了分治法的策略进行排序
 * <p>分治法:将原问题分解为若干规模更小但结构与原问题相似的子问题,递归地解这些子问题,然后将这些子问题的解组合为原问题的解</p>
 * <p>
 * 快排基本思想:
 * <ol>
 * <li>分解。选取基准,使得基准左边为小于或等于基准值,右边大于或等于基准值</li>
 * <li>求解。递归调用快排对左右子区间R[low,pivotPos-1]和R[pivotPos+1,high]进行排序</li>
 * <li>组合。上一步做完之后,其实左右子区间已经有序,故无需做什么</li>
 * </ol>
 * </p>
 *
 * @author Affy
 * @date 2018-09-26
 */
public class QuickSort {
    private static void quickSort(int[] source, int low, int high) {
        int pivot;//基准位置的记录

        if (low < high) {
            int i = low;
            int j = high;
            pivot = source[i];
            while (i < j) {
                //从高处往左遍历,直到找到第一个关键字小于pivot的记录source[j]
                while (j > 0 && source[j] > pivot) {
                    j--;
                }
                //此时source[j]小于基准位置的值,需要移动source[j]到pivot的左边。可以通过交换source[j]和source[i](也就是pivot)
                //来达到这一目的
                if (i < j) {
                    source[i] = source[j];
                    source[j] = pivot;//这一步其实可以省略,因为后面source[j]还是会被source[i]替换
                    i++;//i向后移动一位
                }
                //接着从i开始,从低处往右遍历,直到找到第一个关键字大于pivot的记录source[i]。
                while (i < high && source[i] < pivot) {
                    i++;
                }
                //此时可交换source[i]和source[j](也就是pivot)
                if (i < j) {
                    source[j] = source[i];
                    source[i] = pivot;//这一步可以省略
                    j--;//j往前移动一位
                }
            }
            //当i == j时,i就是需要的基准位置,它的左边小于它,右边大于它
            source[i] = pivot;
            quickSort(source, low, i - 1);
            quickSort(source, i + 1, high);
        }
    }

    public static void main(String[] args) {
        int[] source = {44, 5, 9, 7, 3, 10, 4, 6, 1, 7, 12, 51};
        System.out.print("before sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }
        quickSort(source, 0, source.length - 1);
        System.out.print("\nafter sort: ");
        for (int i = 0; i < source.length; i++) {
            System.out.printf("%d ", source[i]);
        }
    }
}