排序算法总结

本文将给出六大经典排序的实现。
简单排序算法:冒泡,插入,选择
改进排序算法:快排,归并,堆排

以下排序用到的交换函数

void swap(int &A, int &B) {
    int temp = A; A = B; B = temp;
}

1. 冒泡排序

2个相邻的元素相互比较,不满足顺序则交换;每遍历一次数组,使一个元素处于最终位置。
时间复杂度 O(n2) 空间复杂度 O(1)

void BubbleSort(int nums[], int left, int right) {
    if (nums == NULL || right-left+1 <= 0)
        return;
    for (int i = left; i < right; i++) {
        for (int j = i+1; j <= right-(i-left); j++) {
            if (nums[j] < nums[j-1]) {
                swap(nums[j], nums[j-1]);
            }
        }
    }
}

2. 插入排序

将一个元素插入到已经有序的数组中,从后向前比较,将大于它的元素后移一步,找到属于它的位置,最后插入。
时间复杂度 O(n2) 空间复杂度 O(1)

void InsertSort(int nums[], int left, int right) {
    if (nums == NULL || right-left+1 <= 0)
        return;
    for (int i = left; i < right; i++) {
        int j = i+1;
        int temp = nums[j];
        for (; j > left; j--) {
            if (temp >= nums[j-1])
                break;
            nums[j] = nums[j-1];
        }
        nums[j] = temp;
    }
}

3. 选择排序

首先选出现有数组中的最小值,然后交换到现有数组的最前面,完成 1 个元素的排序。对后序数组选出最小值,重复以上操作。
时间复杂度 O(n2) 空间复杂度 O(1)

由于选择排序的交换次数少 O(n) ,因此关键字或者数据量大的数据数组,适用于选择排序。

void SelectSort(int nums[], int left, int right) {
    if (nums == NULL || right-left+1 <= 0)
        return;
    for (int i = left; i < right; i++) {
        int min_value = nums[i];
        int min = i;
        for (int j = i+1; j <= right; j++) {
            if (nums[j] < min_value) {
                min_value = nums[j];
                min = j;
            }
        }
        swap(nums[i], nums[min]);
    }
}

4. 归并排序

将数组平均分为2个部分,分别进行排序,然后将2个数组合并
时间复杂度 O(nlogn) 空间复杂度 O(n)

void Merge(int nums[], int left, int mid, int right);
void MergeSort(int nums[], int left, int right) {
    if (nums == NULL || right <= left)
        return;
    int mid = (left+right) >> 1;
    MergeSort(nums, left, mid);
    MergeSort(nums, mid+1, right);
    Merge(nums, left, mid, right);
}
void Merge(int nums[], int left, int mid, int right) {
    int len = right - left + 1;
    int* temp = new int[len];
    int i = left, j = mid+1;
    int k = 0;
    while(i <= mid && j <= right) {
        if (nums[i] <= nums[j])
            temp[k++] = nums[i++];
        else
            temp[k++] = nums[j++];
    } 
    while (i <= mid) {
        temp[k++] = nums[i++];
    }
    while (j <= right) {
        temp[k++] = nums[j++];
    }
    // copy
    for (k = 0; k < len; k++) {
        nums[left+k] = temp[k];
    }
    delete []temp;
}

5. 快速排序

随机在数组中选择一个元素,作为划分元素,将数组中比划分元素小的交换到左侧,比划分元素大的交换到右侧,最后将划分元素交换到左右区间的连接处,则划分元素处于了最终位置;递归地对左右区间进行快速排序。

时间复杂度 O(nlogn) 空间复杂度 O(logn)O(n)
空间主要消耗在调用函数所需的栈空间。

void QuickSort(int a[], int l, int r) {
    // 直接交换法
    if (a == NULL || l >= r)
        return;
    int pivot = a[l]; //选最左侧为基元
    int i = l;
    int j = r+1;
    while (i < j) {
        while (i < r && a[++i] < pivot); // 边界是最右(有无等号均可)
        while (a[--j] > pivot); // a[j]向左不会穿过a[l](不能有等号)
        if (i >= j)
            break;
        Swap(a[i], a[j]);
    }
    Swap(a[j], a[l]); //右指针是基元交换的位置
    QuickSort(a, l, j-1);
    QuickSort(a, j+1, r);
}
void QuickSort2(int a[], int l, int r) {
    // 挖坑填坑法
    if(l >= r)
        return;
    int i = l;
    int j = r;
    int pivot = a[i]; // 左边先挖坑
    while (i < j) {
        while (i < j && a[j] > pivot)
            j--;
        if (i < j) {
            a[i++] = a[j]; // 填左坑,挖右坑
        }
        while (i < j && a[i] < pivot)
            i++;
        if (i < j) {
            a[j--] = a[i]; // 填右坑,挖左坑
        }
    }
    a[j] = pivot; // 因为最先挖的左坑,最后填右坑
    QuickSort2(a, l, j-1);
    QuickSort2(a, j+1, r);
}

6. 堆排序

将数组进行原位建堆,逐个将堆顶元素(最大值)交换到数组尾部,然后修复堆。
时间复杂度 O(nlogn) 空间复杂度 O(1)

void FixHeap(int nums[], int i, int n) {
    if (nums == NULL || i < 0 || n <= 0)
        return;
    int temp = nums[i]; // 破坏元素
    int j = 2*i + 1; // i 节点的左子结点
    while (j < n) {
        if (j+1 < n && nums[j+1] > nums[j]) // 找出较大的那个子节点
            j++;
        if (temp > nums[j])
            break;
        nums[i] = nums[j];
        i = j; // 向下继续修复
        j = 2*i + 1;
    }
    nums[i] = temp;
}
void HeapSort(int nums[], int left, int right) {
    if (nums == NULL || right-left+1 <= 0)
        return;
    // 为了简单描述,假设数组是从下标0开始排序
    int n = right-left+1;
    // 建堆,从n/2-1 ~ 0修复堆,n/2 ~ n-1 是叶节点
    for (int i = n >> 1 - 1; i >= 0; i--) {
        FixHeap(nums, i, n);
    }
    for (int i = n-1; i > 0; i--) {
        swap(nums[0], nums[i]);
        FixHeap(nums, 0, i-1);
    }
}

7. 其他排序算法

8. 如何选择排序算法

参考《大话数据结构》:

从算法的简单性将排序算法分为:
- 简单算法:冒泡、插入、选择
- 改进算法:希尔、堆排序、归并排序、快排

  • 时间复杂度来看
    • 从平均情况,后三种改进算法要胜过希尔排序,远胜简单算法
    • 从最好情况(已经有序),冒泡和插入排序最优,改进算法最差
    • 从最差情况(逆序),堆排序和归并排序最优,远胜快排和简单算法
  • 空间复杂度来看
    • 简单算法都不占用额外空间。
    • 堆排序是在数组上原位建堆,O(1);
    • 快排因为递归栈占用,O(lgn)~O(n)递归深度;
    • 归并,需要辅助数组O(n);
      所以如果要求考虑内存空间,不能选快排和归并
  • 稳定性来看
    归并排序最好。当然简单排序中的冒泡和插入也是稳定的,但同时考虑时间,则应选归并。
  • 待排序的个数
    待排序个数n越小,采用简单算法越合适;反之,n越大,选择改进算法。这也是快排中,对于小数组使用插入排序完成的原因。
  • 待排序数据的关键字本身信息量的大小
    如果信息量大,则交换的代价大。此时选择排序最优,先大量比较然后一次交换,O(n)次交换。
    对于待排序个数不大,而关键字信息量大的情况,简单算法占优。

这里写图片描述
原图链接:http://blog.csdn.net/ithomer/article/details/5636226
注意:快速排序的空间复杂度是因为递归造成的栈空间占用。(O(lg n)~O(n))
归并排序的空间复杂度是O(n+lg n)=O(n)

9. 源码下载

https://github.com/qzxin/sort-algorithm

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值