十大排序算法——选择合适的排序(上)(C++)

目录

前言

一、冒泡排序(Bubble Sort)

1.原理:

2.效率:

3.适用场景:

4.代码实现: 

 二、选择排序(Selection Sort)

1.原理:

2.效率:

3.适用场景:

4.代码实现:

 三、插入排序(Insertion Sort)

1.原理:

2.效率:

3.适用场景:

4.代码实现:

四、归并排序(Merge Sort)

1.原理:

2.效率:

3.适用场景:

4.代码实现:

五、快速排序(Quick Sort)

1.原理:

2.效率:

3.适用场景:

4.代码实现:

总结


前言


排序是数据处理的基本操作之一。在计算机科学里面,排序是算法的重要地基,排序后的数据更易于处理查找。 在本篇文章中,我将会简要的介绍一下十大排序算法,通过学习排序算法,我们可以得到思路的扩充算法效率的优化算法的稳定性,以及在不同场景的合适选择


一、冒泡排序(Bubble Sort)

冒泡排序又称为沉淀排序,进行n次操作,每次将”最大“(”最小“)的数放到最底部,然后对剩余的无序数组继续冒泡,直到最后变成有序数组,冒泡排序是稳定排序。冒泡排序小步骤优化便是,设置一个标志,用来判断剩余序列是否为有序,是的话便可直接退出,不是的话继续进行冒泡操作。

1.原理:

  • 重复遍历序列,依次比较相邻的两个元素,如果顺序颠倒则进行交换。
  • 每次遍历会将最大(最小)的元素”冒泡“到序列的末尾。

2.效率:

  • 时间复杂度:最好情况为O(n),即该序列已经是有序的。最坏情况是O(n²) 。平均为O(n²)。
  • 空间复杂度:O(1)。

3.适用场景:

  • 数据量小,且部分有序的序列 

4.代码实现: 

void Bubble_Sort(std::vector<int> &vec){
	int n = vec.size();
	//设置一个布尔函数判断剩余序列是否有序
	bool swapped;
	//i < n -1 是因为判断条件是将当前与下一个进行比较,因此用不到vec[n - 1]来比较
	for(int i = 0 ; i < n - 1 ; i ++){
		swapped = false;
		//该循环j < n - i - 1是因为已经将最大的放到末尾之后便不用在与之比较了
		for(int j = 0 ; j < n - i - 1 ; j ++){
			if(vec[j] > vec[j + 1])
				std::swap(vec[j] , vec[j + 1]) , swapped = true;
		}
		if(swapped) break;
	}
}
//std::vector<int> &vec 是传入一个以vector<int>为类型的数组

 二、选择排序(Selection Sort)

选择排序即为暴力的排序方法,该排序最简单直观易于理解,对于初学者来说是了解排序的良好教程。学习选择排序就要知道排序的目的是什么?通过字面意思便也可以知道,排序便是将n个杂乱无章的数放到它们应该待在的位置。言简意赅,选择排序便是对一个长为n的数组遍历两遍,它没有”冒泡“排序”聪明“,可以说是”死脑筋“,但是它简单易写,且不占用额外空间。对于稳定性来说,如果要求稳定排序的话,其比较规则就需要进行改变,等下在代码实现里面会体现出来。

1.原理:

  • 每次从未排序部分选择最大(小)的元素,放到已排序部分的末尾。

2.效率:

  • 时间复杂度:最好、最坏、平均情况均为O(n²)。
  • 空间复杂度:O(1)。

3.适用场景:

  • 数据量小,且内存有限的情况。

4.代码实现:

void Selection_Sort(std::vector<int>& vec){
    int n = vec.size();
    //i < n -1 是因为判断条件是将当前与之后剩下的进行比较,因此用不到vec[n - 1]来比较
    for (int i = 0; i < n - 1; i ++){
    	//本次排序从小到大排
        int ma = i;
        //暴力排序,直接遍历之后的即可,遇到符合条件的让ma坐标等于其即可
        for (int j = i + 1; j < n; j ++)
			//如果是不稳定排序,其条件是vec[ma] < vec[j]
            if (vec[ma] <= vec[j]) ma = j;
        std::swap(arr[i], arr[ma]);
    }
}

 三、插入排序(Insertion Sort)

插入排序是一种”动态的算法“,在一个有序数列上依次增加数据,当增添一个数据x之后,把他插入有序数列里面中合适的位置使得数列依旧有序。在初始的时候数列为空数列,在不断输入数据之后,使其一直保持有序性,即为插入排序。因此该数列是稳定的,且任何时候都是有序的。

1.原理:

  • 将未排序部分的元素逐个插入到已排序部分的正确位置。

2.效率:

  • 时间复杂度:最好情况为O(n),即该序列已经是有序的。最坏情况是O(n²) 。平均为O(n²)。
  • 空间复杂度:O(1)。

3.适用场景:

  • 数据量小,且部分有序的情况。

4.代码实现:

void Insertion_Sort(std::vector<int>& vec) {
    int n = vec.size();
    for (int i = 1; i < n; i++){
    	//key 保存下当前数值
        int key = vec[i];
        //令 j = i - 1 从后往前查找,这样方便后移
        int j = i - 1;
        while (j >= 0 && vec[j] > key) {
			//如果符合条件就将该数后移,不符合即为找到插入位置了
            vec[j + 1] = vec[j];
            j--;
        }
        //直接在该位置赋值即可实现插入操作
        vec[j + 1] = key;
    }
}
/*本文采用的是暴力查找,也可以使用二分查找,找到合适位置进行插入,
但是,但是,但是,最重要的是关于位置后移之后腾位置这一步,如果使用
连续存储便要全部后移,这样也会增加时间复杂度,最好是使用链表进行存储。
*/

四、归并排序(Merge Sort)

归并排序分治法的运用之一,对于归并排序,我将其步骤分为三部曲,分别是分解递归合并,并且归并排序是稳定排序,对于初学者,可以通过画图模拟全过程,更容易理解这一排序。

1.原理:

  • 分解:把初始序列分成两个长度相同的左右子序列。
  • 递归:利用递归的方法将分解后的序列继续分解,直到当前子序列只剩下一个数
  • 合并:也可以称为”分解的回溯“,不断将最末端的子序列合并,并且从小到大(从大到小)排序,使得合并后的序列便是有序的。

2.效率:

  • 时间复杂度:最好、最坏、平均情况均为O(n log n)。
  • 空间复杂度:O(1)。

3.适用场景:

  • 大规模数据,尤其是需要稳定排序的时候,条件优于快排。

4.代码实现:

void Merge(std::vector<int>& vec, int left, int mid, int right) {
	//创建一个temp容器作为中间过渡
	std::vector<int> temp(right - left + 1);
    int i = left, j = mid + 1, k = 0;
    //排序,将小的先放进去,实现从小到大
    while (i <= mid && j <= right) {
        if (vec[i] <= vec[j]) temp[k++] = vec[i++];
		else temp[k++] = vec[j++];
    }
    //分别遍历剩余的两个序列,将其余剩下的纳入temp之后
    while (i <= mid) temp[k++] = vec[i++];
    while (j <= right) temp[k++] = vec[j++];
    //将排好序的序列重新放回vec中
    for (int p = 0; p < k; p++)
        vec[left + p] = temp[p];
}

void Merge_Sort(std::vector<int>& vec, int left, int right) {
	//找中间点
	if (left >= right) return;
    int mid = left + (right - left) / 2;
	//分解
    Merge_Sort(vec, left, mid);
    Merge_Sort(vec, mid + 1, right);
	//合并
    Merge(vec, left, mid, right);
}

五、快速排序(Quick Sort)

快速排序也同样是基于分治法选择一个基准元素,将序列分成两部分,左边小于基准右边大于基准,然后递归排序。一般来说,基准选择第一个,中间,或者最后。利用双指针的方法,先将基准的数取出放入临时存储数空间(t) 里面,然后从基准右边找到第一个比基准小的数,放到基准的位置,然后从基准的左边找到第一个比基准大的数,放到刚取数的那个位置,以此类推,进行递归,最后先以同样的方式进行第二轮遍历,直到全部排完序,并且这是一个不稳定排序。

1.原理:

  • 选基准,先挑选基准右边第一个比基准小的数,放入基准的位置,然后挑选基准左边第一个比基准大的数,放入基准右边被取出数的位置。
  • 递归重复,直到排完序。

2.效率:

  • 时间复杂度:最好情况为O(n log n) , 最坏情况是O(n²) 。平均为O(n log n)。
  • 空间复杂度:O(n log n)。

3.适用场景:

  • 大规模数据,尤其是对稳定性没有要求的场景。

4.代码实现:

void Quick_Sort(std::vector<int> & vec , int l , int r){
	//本次快排选择中间为基准
	int i = l , j = r , mid;
	//位运算,可以直接实现除二的功效
	mid = l + r >> 1;
	int key = vec[mid];
	//双指针算法
	while(i <= j){
		//左边找到第一个比key大的数
		while(vec[i] < key) i++;
		//右边找到第一个比key小的数
		while(vec[j] > key) j--;
		//如果满足i > j即为已经排好序,如果没有则必然有需要交换的位置,则进行交换
		if(i <= j){
			std::swap(vec[i] , vec[j]);
			++i;
			--j;
		}
	}
	//递归进入下一段,左边一段和右边一段,直到子序列已经排好序
	if(j > l) Quick_Sort(l , j);
	if(i < r) Quick_Sort(i , r);
}

总结:

本片文章为十大排序算法——选择合适的排序(上),主要分享了冒泡排序,选择排序,插入排序,归并排序和快速排序的基本实现原理,对它们的功能实现进行了详细的分析,并且讲述了其代码实现和使用场景,希望大家能给予鼓励和支持,预计在明天晚上将继续分享十大排序算法——选择合适的排序(下)。如果看到这里了,那就点个赞支持一下,创作不易,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值