排序算法小结


排序是在生活中经常遇到的事情,比如在学校时会按成绩排名,会按身高排座位。在逛电子商铺时,会按销售量/人气值筛选,刷微博时,会有热搜榜等。将很多无序的事情变的有序,事情就好处理了。其中用到的就是排序算法。
拿班主任给班级学生成绩排序来举例。

O(n^2)复杂度算法

冒泡排序

第一轮从成绩单中挑出来成绩最高的,第二轮从成绩单中挑出来成绩次高的。。。这么重复下去,直到班级内全部学生全部挑出来完。
因为每一次选出一个最好的,就像从水里冒出的气泡,所以叫冒泡排序。

void BubbleSort(vector<int> &sums)
{
    // 第i轮次挑学生
    for (int i = 0; i < nums.size() - 1; i++)
    {
        // 成绩单中还没被挑出来的学生
        for (int j = 0; j  < nums.size() - 1 - i; j++)
        {
            if (nums[j] < nums[j+1])
            {
                swap(nums[j],nums[j+1]);
            }
        }
    }
}

插入排序

先把成绩单中第一个学生的成绩写到一张白纸的中间,如果第二个学生成绩比他高则写到第一位同学的上方,如果第二个学生成绩比他低,则写到第一个同学下方。等到了第三个同学的时候,拿第三名同学的成绩和白纸上的两个同学成绩比较,插入到相应的位置。排序的纸要留好空位,以方便插入后来的同学的名字。
这个排序算法就像平时大扑克摸牌一样,后摸的牌和手里的牌比较后,插入到手中合适的位置,所以叫插入排序。

void InsertSort(vector<int> &nums)
{
    // 要插入到白纸上的同学的序号
    for (int i = 1; i < nums.size(); i++)
    {
        int tmp = nums[i];
        // j是白纸上的同学的末尾序号
        int j = i - 1;
        // 循环找出插入的位置
        while (j >=0 && nums[j] < tmp)
        {
            nums[j+1] = nums[j];
            j--;
        }
        nums[j+1] = tmp;
    }
}

选择排序

从1轮从成绩单中选出成绩最高的同学,写到第一个位置,再选择次高的成绩,写到第二个位置。。。以此类推,很像像冒泡排序,不同的是:先假设成绩单中无序区的第一个学生成绩为最好,拿排好序的学生成绩最好的那个和这个成绩相比,并决定是否交换。

void SelectSort(vector<int> &nums)
{
    for (int i = 0; i < nums.size() - 1; i++)
    {
        // 记录位置
        int max_loc = i;
        for (int j = i + 1; j < nums.size(); j++)
        {
        	// 成绩单中无序区
            if (nums[j] > nums[max_loc])
            {
                max_loc = j;
            }
        }
        swap(nums[max_loc, nums[i]]);
    }
}

O(nlogn)复杂度算法

上面3种排序都是复杂度为O(n^2)复杂度的算法,当数据量小的情况下,排序不怎么费时间。但是当数据量大的情况下,上面三种算法就显的太慢了。要想提高效率,就要让计算机少做事情(减少数据之间的相互比较)。

归并排序

把全班同学分成两组,分别排序,那么从每一组中挑选出一个最大的,就能省去一般的相互比较时间。那么:
1.就把整个班级一分为二,分别进行排序,再把两个有序数列合并成一个排好序的序列。相比排序,对有序数列的合并是很快的。
2.对班级分成2个组的排序,再分成4组,分别排序,那么就节省了3/4的时间。
3.4组分8组,8组分16组,直到每组分为2个元素,做一次对比就行。
4.合并有序数组,这样列表越来越大,知道成为一个班级中所有的人。

void Merge(vector<int> &nums, int low, int mid, int high)
{
    int i = low;
    int j = mid + 1;
    vector<int> ltmp;
    // 只要左右两个有序序列都有数,开始循环比较
    while (i <= mid && j <= high)
    {
        if (nums[i] < nums[j])
        {
            ltmp.push_back(nums[i]);
            i++;
        }
        else
        {
            ltmp.push_back(nums[j]);
            j++;
        }
    }
    // 循环结束,两个序列肯定有一个先没有数据
    while (i <= mid)
    {
        ltmp.push_back(nums[i]);
        i++;
    }
    while (j <= high)
    {
        ltmp.push_back(nums[j]);
        j++;
    }

    for (int k = 0; k < high - low + 1; k++)
    {
        nums[low + k] = ltmp[k];
    }
}

void MergeSort(vector<int> &nums, int low, int high)
{
    // 至少有两个元素
    if (low < high)
    {
        int mid = (low + high) / 2;
        // 让左右数列有序
        MergeSort(nums, low, mid);
        MergeSort(nums, mid + 1, high);
        // 合并
        Merge(nums, low, mid, high);
    }
}

快速排序

从班级学生成绩中随机选出来一个,比如我们选的是序号为0的学生,他的是70分。
1.归类:将班级所有学生成绩分成两部分,一部分是大于70分的,令一部分小于70分。
2.从上面得到的两部分学生中,分别采用第一步的方法再选出来一个学生,我们还假设选了两波人中最头部的那个学生,比如分别是50分和80分,并再分拨。
3.用同样的方法,四拨人变八拨。。。变十六拨,这样下去,所有的数字就会排序好。

int Partition(vector<int> &nums, int left, int right)
{
    int tmp = nums[left];
    while (left < right)
    {
        // 从右边找比tmp大的数,找到后把大的数放到左边
        while (nums[right] <= tmp && left < right)
        {
            right--;
        }
        nums[left] = nums[right];
        // 从左边找比tmp小的数,找到后放到右边
        while (nums[left] >= tmp && left < right)
        {
            left++;
        }
        nums[right] = nums[left];
    }
    // 把tmp归位,此时left = right
    nums[left] = tmp;

    return left;
}

void QuickSort(vector<int> &nums, int left, int right)
{
    if (left < right)
    {
        int mid = Partition(nums, left, right);
        QuickSort(nums, left, mid - 1);
        QuickSort(nums, mid + 1, right);
    }
}

堆排序

这个涉及到树的知识,笔者就不深入写了。总的过程可以分为一下几部
1.建立堆
2.得到堆顶元素,为最大元素
3.去掉堆顶,将堆最后一个元素放到堆顶,此时可以通过一次调整,让堆重新变得有序。
4.堆顶元素为第二大元素。
5.重复步骤3,直到堆变空。

// 堆的向下调整
void Sift(vector<int> &nums, int low, int high)
{
    // low:堆的根节点位置
    // high:堆的最后一个元素的位置
    // i是父节点,j是左孩子结点
    int i = low;
    int j = 2 * i + 1;
    int tmp = nums[low];

    while (j <= high)
    {
        // 如果有右孩子,且右孩子比左孩子小
        if (j + 1 <= high && nums[j+1] < nums[j])
        {
            // j指向右孩子
            j++;
        }
        if (nums[j] < tmp)
        {
            nums[i] = nums[j];
            // 向下看一层
            i = j;
            j = 2 * i + 1;
        }
        // 如果tmp更大,把tmp放在这个根节点位置
        else
        {
            nums[i] = tmp;
            break;
        }
    }
    nums[i] = tmp;
}

void HeapSort(vector<int> &nums)
{
    int n = nums.size();
    // 建立堆
    for (int i = (n - 1 - 1) / 2; i >=0; i++)
    {
        // 建立堆的时候,调整的部分的根的下标
        Sift(nums, i , n - 1);
    }

    for (int i = n - 1; i >= 0; i--)
    {
        // i指向当前堆的最后一个元素
        swap(nums[0], nums[i]);
        sift(nums, 0, i - 1);
    }
}

总结

排序方法时间复杂度空间复杂度稳定性
冒泡O(n^2)O(1)稳定
选择O(n^2)O(1)不稳定
插入O(n^2)O(1)稳定
快速O(nlogn)O(logn),O(n)不稳定
归并O(nlogn)O(n)稳定
O(nlogn)O(1)不稳定

从算法的对比我们可以看出,很容易因为算法不合格,写出的代码浪费掉成千上万的计算机资源。我们平时做事,人和人的水平差一些,很可能做出来事的效率差别十倍。所以我们在智能时代,要提高自己的专业能力,并且要善于把大目标分解成小目标,不断拆解目标任务。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值