常用排序算法总结

冒泡排序

  原理:扫描整个数组,每次比较相邻的两个数,逆序交换,第 i i i次扫描就可以找到第 i i i大的数, n − 1 n-1 n1次扫描后,就可以完成排序

时间复杂度:O( n 2 n^2 n2)

空间复杂度:O(1)

稳定性:稳定

void bubble(vector<int> &vi)
{
    for(int i = vi.size() - 1; i > 0; i--)
    {
        for(int j = 0; j < i; j++)
        {
            if(vi[j] > vi[j+1])
            {
                swap(vi[j], vi[j+1]);
            }
        }
    }
}

选择排序

  原理:从第一个数开始,和之后的每个数一一比较,选择最小的,第 i i i轮比较可以找到第 i i i小的数, n − 1 n-1 n1轮后,就可以完成排序

时间复杂度:O( n 2 n^2 n2)

空间复杂度:O(1)

稳定性:不稳定

void selection(vector<int> &vi)
{
    for(int i = 0; i < vi.size(); i++)
    {
        for(int j = i + 1; j < vi.size(); j++)
        {
            if(vi[i] > vi[j]) swap(vi[i], vi[j]);
        }
    }
}

插入排序

  原理:将第1个数看作一个已经排序好的数组,从第2个数开始,在这个排序好的数组中找到自己的位置插入, n − 1 n-1 n1次操作后,即可把后面 n − 1 n-1 n1个数插入,完成排序

时间复杂度:O( n 2 n^2 n2)

空间复杂度:O(1)

稳定性:稳定

void insert(vector<int> &vi)
{
    for(int i = 1; i < vi.size(); i++)
    {
        for(int j = i; j > 0; j--)
        {
            if(vi[j] < vi[j-1]) swap(vi[j], vi[j-1]);
            else break;
        }
    }
}

快速排序

  原理:将数组中的一个数 x x x指定为基准数(一般为第一个数),其余数与 x x x比较,比 x x x小的放在左边,比 x x x大的放在右边,对分开的这两部分,重复以上过程,直到每个部分都只有一个数。

时间复杂度:平均O( n log ⁡ n n\log{n} nlogn),最差O( n 2 n^2 n2)

  最差情况是每次选择的基准数都是这一段的最大或最小值,选择第一个数为基准数时,最坏情况即为排序好的数组,退化为冒泡排序

空间复杂度:O( log ⁡ n \log{n} logn),最差O(n)

  虽然每次划分用的空间是O(1),但是递归调用在栈区用了O( log ⁡ n \log{n} logn)的空间,其中最差情况时,会递归n层

  不知道用迭代实现,空间复杂度会不会减小。看了几篇网上的实现,要借助额外的数组,空间复杂度没有降低

稳定性:不稳定

void quick(vector<int> &vi,int left, int right)
{
    //范围[left, right]
    if(left >= right) return;
    //哨兵,即基准数
    //第一位做哨兵方便划分
    int guard = vi[left];
    int low = left, high = right;
    /*
    每轮循环:
        high指针从右向左扫描,大于等于基准值,扫描下一位;小于基准值,指针留在这里
        low指针从左向右扫描,小于等于基准值,扫描下一位;大于基准值,指针留在这里
        两个指针还未相遇,交换各自指向的值,进行下一轮循环
        两个指针相遇了,即就是到了基准值分割的边界,有两种情况:
            high指针左移遇到了low,说明没有再遇到比基准值小的值,low所在的值必然小于等于基准值
            low指针右移遇到了high,说明high停留在了比基准小的位置
        两种情况下两个指针相遇的值,都小于等于基准值,即属于左边
    所以这一步,将基准值换到相遇位置,相遇位置的值换到开头,完成划分
    */
    while(low < high)
    {
        //顺序不能交换,因为最后要把相遇位置的值交换到最左端
        while(low < high && vi[high] >= guard) high--;
        while(low < high && vi[low] <= guard) low++;
        if(low < high) swap(vi[low], vi[high]);
    }
    vi[left] = vi[low];
    vi[low] = guard;

    quick(vi,left,low-1);
    quick(vi,low+1,right);
}

归并排序

  原理:用分治的思想,先将数组从中间分割,并重复这一过程直到剩下长度为1的数组,之后两两合并排序恢复为原始长度的数组

时间复杂度:O( n log ⁡ n n\log{n} nlogn)

  每次分开,单个数组长度变为原来一半, l o g n logn logn 次后成为一个个长度为1的数组。排序恢复的过程中,因为是序列比较,数组内所有数都会参与,复杂度为 O(n),总时间复杂度即为 O( n log ⁡ n n\log{n} nlogn)

空间复杂度:O( n n n)

  每次合并的过程中,需要额外开辟一个数组存放新的排序

稳定性:稳定

void merge(const vector<int>::iterator &begin,const vector<int>::iterator &end,const vector<int>::iterator &mid)
{
    vector<int> tmp;
    auto p1 = begin,p2 = mid;
    //将两个序列合并后的结果放入tmp
    while(p1 != mid || p2 != end)
    {
        if(p1 != mid && p2 != end)
        {
            if(*p1 <= *p2)
            {
                tmp.push_back(*p1);
                p1++;
            }
            else
            {
                tmp.push_back(*p2);
                p2++;
            }
        }
        else if(p1 != mid)
        {
            tmp.push_back(*p1);
            p1++;
        }
        else
        {
            tmp.push_back(*p2);
            p2++;
        }
    }
    //将tmp的内容替换回原数组
    p1 = begin;
    for(int &val:tmp)
    {
        *p1 = val;
        p1++;
    }
}
void mergeSort(const vector<int>::iterator &begin,const vector<int>::iterator &end,int len)
{
    if(len>1)
    {
        int midlen = len >> 1;
        const auto mid = begin+midlen;
        //分成两部分
        if(len & 1)
        {           
            mergeSort(begin,mid,midlen);
            mergeSort(mid,end,midlen+1);
        }
        else
        {
            mergeSort(begin,mid,midlen);
            mergeSort(mid,end,midlen);
        }
        //将两部分合并
        ::merge(begin,end,mid);
    }
    return;
}

堆排序

  原理:先从数组上构造一个大顶堆,此时堆顶元素为最大值,将其与堆的末尾元素(即数组末尾)交换,从堆内去除末尾元素,调整堆使其满足大顶堆性质,重复以上过程直到堆内元素只剩一个

  大顶堆:满足根节点值大于子节点的平衡二叉树,类似还有小顶堆

时间复杂度:O( n log ⁡ n n\log{n} nlogn)

  建堆时,从最后一个非叶子结点开始,将当前子树的最大元素交换到当前子树的根节点,且保持它的子树满足最大堆(即如果交换过去的值小于它的叶节点,要继续递归交换下去,直到叶子节点均小于根节点),一共是 n / 2 n/2 n/2 个子树,子树最深交换 l o g n logn logn 层(即整个堆的根节点是最小值时)

  排序维护堆时,交换 n − 1 n-1 n1 次,交换后调整堆的过程最多 l o g n logn logn 次交换

空间复杂度:O( 1 1 1)

稳定性:不稳定

//调整以rt为根的树,使最大值在rt
void adjustHeap(vector<int> &vi, int len,int rt)
{
    for (int j = rt << 1 | 1; j < len; j = j << 1 | 1)
    {
        if (j+1 < len && vi[j] < vi[j + 1]) j++;
        //如果子节点大于根节点,交换,并继续迭代过程
        if (vi[rt] < vi[j])
        {
            swap(vi[rt], vi[j]);
            rt = j;
        }
        else break;
    }
}
void heapSort(vector<int> &vi, int len){
    //建立堆,从最后一个非叶子节点开始,保证遍历到所有子节点;自底向上,保证根节点是最大值
    for(int i = len >> 1; i >= 0; i--) adjustHeap(vi,len,i);
    //此时堆顶是最大的元素(大顶堆),和末尾元素交换,调整堆,并使末尾向前
    for(int i = len - 1; i > 0; i--)
    {
        swap(vi[0],vi[i]);
        adjustHeap(vi,i,0);    
    }
}

参考资料

C++经典排序算法总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值