算法基础——排序算法

排序是什么?

排序是对随机一组数字进行从大到小(或从小到大)的排列

排序算法的稳定性

对于两个相邻且相同的元素,排序过后其元素位置不会改变

冒泡排序

依次比较相邻两个元素,若前者大于后者(从小到大排序),则交换两者位置,即将大的依次往后移

public static void BubbleSort(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;
            }
        }
    }
}

比较次数(与数组数量n有关,与数组初始排列情况无关)

  • 固定情况——第零轮比较n-1次,第一轮比较n-2次,第二轮比较n-3次…第n-1轮比较1次,即(n-1)+(n-2)+…+1 = n2/2,[0,n-1]共n个数前后相加等于n,再除2算出多少个n

交换次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——数据刚好从小到大排序,则无需交换
  • 最坏情况——数据刚好从大到小排序,则每次比较都需要交换,也为n2/2

时间复杂度(比较次数+最坏情况的交换次数)

  • O( [ n2/2 ] ) + O( [ n2/2 ] ) = O(n2)

空间复杂度

  • 在数组内部操作,故为n

稳定性

  • array[i] > array[j] 表示大于才会交换,故冒泡排序是稳定的
  • 若改为array[i] >= array[j] 则不稳定

选择排序

从数组中依次选择最小的放到前面,即将最小的往前移(从小到大排序,先假定第一个是最小的,然后依次和后面的比较,若有更小的则交换)

public static void SelectSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        int min = i;
        for (int j = i + 1; j < array.length; j++) {
            if (array[j] < array[i]) {
                min = j;
            }
        }
        if (min != i) {
            int temp = array[i];
            array[i] = array[min];
            array[min] = temp;
        }
    }
}

比较次数(与数组数量n有关,与数组初始排列情况无关)

  • 固定情况——第零轮比较n-1次,第一轮比较n-2次,第二轮比较n-3次…第n-1轮比较1次,即(n-1)+(n-2)+…+1 = n2/2,[0,n-1]共n个数前后相加等于n,再除2算出多少个n

交换次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——数据刚好从小到大排序,则无需交换
  • 最坏情况——数据刚好从大到小排序,则前后互换,共n个元素则交换n/2次

时间复杂度(比较次数+最坏情况的交换次数)

  • O( [n2/2] ) + O( n/2 ) = O(n2)

空间复杂度

  • 在数组内部操作,故为n

稳定性

  • array[j] < array[i] 表示小于才会交换,故选择排序是稳定的
  • 若改为array[j] <= array[i] 则不稳定

插入排序

将元素依次和其前面的元素进行比较(也可从下标1开始),若小于则将元素往前移(从小到大排序),即将元素插入到合适的位置(元素左边是有序的,右边是无序的)

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

比较次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——数据刚好从小到大排序,则每轮比较一次即可,共n次
  • 最坏情况——数据刚好从大到小排序,则第零轮比较0次,第一轮比较1次,第二轮比较2次…第n-1轮比较n-1次,即0+1+…+(n-1) = n(n-1)/2,[0,n-1]共n个数前后相加等于n-1,再除2算出多少个n-1

交换次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——数据刚好从小到大排序,则无需交换
  • 最坏情况——数据刚好从大到小排序,则每次比较都需要交换,也为n(n-1)/2

时间复杂度(最坏情况比较次数+交换次数)

  • O( [n(n-1)/2] ) + O( [n(n-1)/2] ) = O(n2)

空间复杂度

  • 在数组内部操作,故为n

稳定性

  • array[j] < array[j - 1] 表示小于才会交换,故选择排序是稳定的
  • 若改为 array[j] <= array[j - 1] 则不稳定

归并排序

利用递归将数组平分为子数组,直到不能再分时,对子数组进行两两合并,对比两个子数组,依次将较小的元素取出归位,从而完成排序

public static void MergeSort(int[] array) {
    Sort(array, 0, array.length - 1);
}
private static void Sort(int[] array, int form, int to) {
    if (form >= to) {
        return;
    }
    int middle = form + (to - form) / 2;
    Sort(array, form, middle);
    Sort(array, middle + 1, to);
    Merge(array, form, middle, to);
}
private static void Merge(int[] array, int form, int middle, int to) {
    int[] extArray = new int[array.length];
    int i = form;
    int j = middle + 1;
    for (int k = form; k <= to; k++) {
        extArray[k] = array[k];
    }
    for (int k = form; k <= to; k++) {
        if (i > middle) {
            array[k] = extArray[j++];
        } else if (j > to) {
            array[k] = extArray[i++];
        } else if (extArray[j] < extArray[i]) {
            array[k] = extArray[j++];
        } else {
            array[k] = extArray[i++];
        }
    }
}

分割次数(与数组数量n及数组初始排列情况无关)

  • 分割不需要花费时间,可当作数组本就是分割好的

比较次数(与数组数量n有关,与数组初始排列情况无关)

  • 在每层合并时,依次比较子数组的首位数据大小,故比较次数和子数组长度有关,数组共n个,每行子数组总数也是n个,故每行的比较次数为O(n),n个元素可平分为log2n行

时间复杂度(分割次数+比较次数)

  • O( [ n(log2n) ] ) = O(nlogn)

空间复杂度

  • 使用了辅助数组,在辅助数组和内部数组操作,故为2n

稳定性

  • extArray[j] < extArray[i] 表示小于才会交换,故归并排序是稳定的
  • 若改为 extArray[j] <= extArray[i] 则不稳定

快速排序

选择第一个为基准数base,j从右往左找到小于base的数,i从左往右找到大于base的数,交换它们,若i和j相遇,说明已经找完,则将base换到中间,即左边全部小于base,右边全部大于base,然后递归处理左右两边

public static void QuickSort(int[] array) {
    Sort(array, 0, array.length - 1);
}
private static void Sort(int[] array, int from, int to) {
    if (from > to) {
        return;
    }
    int i = from;
    int j = to;
    int base = array[from];
    while (i < j) {
        while (i < j) {
            if (array[j] < base) {
                break;
            }
            j--;
        }
        while (i < j) {
            if (array[i] > base) {
                break;
            }
            i++;
        }
        int temp = array[j];
        array[j] = array[i];
        array[i] = temp;
    }
    array[from] = array[i];
    array[i] = base;
    Sort(array, from, i - 1);
    Sort(array, i + 1, to);
}

比较次数(与数组数量n有关,与数组初始排列情况无关)

  • 最好情况——如果每次选择的基准数都能平分子序列,则比较次数和子数组长度有关,数组共n个,每行子数组总数也是n个,故每层的比较次数为O(n),n个元素可平分为log2n层
  • 最坏情况——如果数据刚好从大到小排序,则第一个元素比较n-1次,第二个元素比较n-2次,第n个元素比较1次,即(n-1)+(n-2)+…+1 = n(n-1)/2,n-1个数前后相加等于n,再除2算出多少个n

交换次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——数据刚好从小到大排序,则无需交换
  • 最坏情况——数据刚好从大到小排序,需要将每个元素定位,共n个元素交换n次

时间复杂度(比较次数+交换次数)

  • 最好情况——O(nlogn)
  • 最坏情况——O( [n(n-1)/2] ) + O(n) = O(n2)

空间复杂度

  • 在数组内部操作,故为n

稳定性

  • array[j] < base 表示找到小于的才会左右替换,故快速排序是稳定的
  • 若改为 array[j] <= base 则不稳定

堆排序

堆排序分为两步(分别对应2个循环)

1.构建最大堆(用于从小到大排序)

  • 从右往左从下往上调整所有父节点(非叶节点),数组从0开始,最后一个节点为arr.length - 1,除以2即是最后一个父节点
  • 对比父节点和它的左右节点,从左右节点选择大的与父节点比较,若大于则交换
  • 交换后递归子树,因为交换后可能导致子树不符合最大堆

2.排序

  • 依次将最后一个节点(叶节点)和根节点交换(即将最大的放在最后)
  • 从根节点往下调整节点(交换后不符合最大堆,需重新构建),此次构建不包括最后节点(传入参数为arr.length - 1)
public static void heapSort(int[] arr) {
    for (int i = (arr.length - 1) / 2; i >= 0; i--) {
        buildMaxHeap(arr, arr.length, i);
    }
    for (int i = arr.length - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        buildMaxHeap(arr, i, 0);
    }
}
private static void buildMaxHeap(int[] arr, int size, int parent) {
    int leftChild = 2 * parent + 1;
    int rightChild = 2 * parent + 2;
    int max = parent;
    if (leftChild < size && arr[leftChild] > arr[max]) {
        max = leftChild;
    }
    if (rightChild < size && arr[rightChild] > arr[max]) {
        max = rightChild;
    }
    if (max != parent) {
        int temp = arr[parent];
        arr[parent] = arr[max];
        arr[max] = temp;
        buildMaxHeap(arr, size, max);
    }
}

构建堆时的比较次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——已是最大堆,[0, (n - 1) / 2] 共 (n - 1) / 2 + 1个父节点,要跟其左右节点比较,故乘2等于n+1次
  • 最坏情况——每层比较都会触发交换并调用递归,倒数第一层(叶节点)比较0次,倒数第二层比较2次(与左右比较),倒数第三层比较4次(自己2次+倒数第二层2次)…倒数第log2n-1层(顺数第二层)比较n次,倒数第log2n层(根节点)比较2n次,故总比较次数为0+2+4+…n+2n = log2n/2*2n = nlog2n(n个数平分共log2n层,前后两两相加为2n,除以2计算多少个2n)

构建堆时的交换次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——已是最大堆,每次比较无需交换
  • 最坏情况——每层比较都会触发交换并调用递归,交换次数是比较次数的一半,即nlog2n/2

排序堆时的比较次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——交换头尾元素后不可能还是最大堆,故没有最好情况
  • 最坏情况——交换头尾元素后,每层比较都会触发交换并调用递归,比较次数同上即nlog2n

排序堆时的交换次数(与数组数量n及数组初始排列情况有关)

  • 最好情况——交换头尾元素后不可能还是最大堆,故没有最好情况
  • 最坏情况——交换头尾元素后,每层比较都会触发交换并调用递归,头尾交换+父子交换次数等于比较次数,即nlog2n

时间复杂度(最坏情况比较次数+交换次数)

  • 最坏情况——O( [nlog2n] ) + O( [nlog2n/2] ) + O( [nlog2n] ) + O( [nlog2n] )= O(nlogn)

空间复杂度

  • 在数组内部操作,故为n

稳定性

  • arr[leftChild] > arr[max] 表示找到大于的才会父子交换,故堆排序是稳定的
  • 若改为 arr[leftChild] >= arr[max] 则不稳定

Tips:上面的循环已是优化过的,若看不出复杂度,可看下面的,这样是遍历所有节点

public static void heapSort(int[] arr) {
    for (int i = arr.length - 1; i >= 0; i--) {
        buildMaxHeap(arr, arr.length, i);
    }
    for (int i = arr.length - 1; i >= 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        buildMaxHeap(arr, i, 0);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值