八大排序算法(C++)

0. 排序算法分析角度

a. 算法的执行效率

  1. 最好情况、最坏情况、平均时间复杂度
  2. 时间复杂度的系数、常数、低阶
  3. 比较次数、交换或移动次数

b. 算法的内存消耗

空间复杂度,当空间复杂度为O(1)时,被称为原地排序

c. 算法的稳定性

如果待排序的序列存在值相等的元素,经过排序之后,相等元素之间的先后顺序保持不变。

1. 冒泡排序

冒泡排序只会操作相邻的两个数据。每次操作都会对两个相邻的元素进行比较,如果不满足比较条件,便进行交换,直到有序。每次冒泡排序至少会让一个元素到最终位置。

可以通过一个标志位,当没有数据交换时,表示数据已经有序,设置标记,来进行优化。

//data为排序数组,n为数组长长度,按照从小到大排序。
//通过flag来优化排序,若没有交换说明已经有序,直接结束循环。
void bubble_sort(int data[], int n)
{
	if (n < 2)
		return;
	
	for (int i = 0; i < n - 1 ; i++)
	{
		bool flag = false;
		for (int j = 0; j < n - i - 1; j++)
		{
			//相邻元素进行比较,前面大于后面元素时,交换元素,并标识交换
			if (data[j] > data[j+1]) 
			{
				flag = true;
				swap(data[j], data[j + 1]);
			}
		}
		//已经有序,则退出
		if (!flag)
			break;
	}
}

2. 插入排序

插入排序通过将数据分为已排序区间和未排序区间。初始已排序区间只有1个元素,即为数组中的第一个元素。核心思想是将未排序区间中的元素,逐个在已排序区间找到自己的位置,插入其中,保证已排序区间一直处于有序。直到未排序区间为空。

//data为排序数组,n为数组长长度,按照从小到大排序。
//从未排序区间选择元素插入到已排序区间,[0,i)为已排序区间,[i,n)为未排序区间。
void insert_sort(int data[], int n)
{
	if (n < 2)
		return;

	//从未排序区间选择元素插入到已排序区间,[0,i)为已排序区间,[i,n)为未排序区间
	int value = 0;
	for (int i = 1; i < n; i++)
	{
		value = data[i];
		int j = i - 1;
		for (; j >= 0; j--)
		{
			//当待插入元素小于前面数据,移动数据
			if (value < data[j])
			{
				data[j + 1] = data[j];
			}
			//当不用交换时,退出循环			
			else
				break;
		}
		//插入value于已排序区间
		data[j + 1] = value;
	}
}

3. 选择排序

选择排序思想类似插入排序,也分为已排序区间和未排序区间。核心思想为从未排序区间中寻找到最小元素,插入到已排序区间末尾。当未排序区间为空时,排序完成。

//data为排序数组,n为数组长长度,按照从小到大排序。
//从未排序区间选择最小的元素插入到已排序区间末尾,[0,i)为已排序区间,[i,n)为未排序区间。
void selection_sort(int data[], int n)
{
	if (n < 2)
		return;

	//从未排序区间找出最小元素插入到已排序区间,[0,i)为已排序区间,[i,n)为未排序区间
	int min_index;
	for (int i = 0; i < n - 1; i++)
	{
		//找到[i,n)区间中最小的元素
		min_index = i;
		for (int j = i + 1; j < n; j++)
		{
			if (data[min_index] > data[j])
			{
				min_index = j;
			}
		}
		//将最小元素插入到已排序区间末尾
		swap(data[i], data[min_index]);
	}
}

4. 归并排序

归并排序的核心思想是将一个待排序数组,分为前后两部分,然后对左右两部分分别进行排序,最后再合并在一起,得到一个有序数组。是一种分之思想,可以采用递归的编程方式来实现。

//合并递归后的左右区间,[left,mid],[mid+1,right]
void merge(int data[], int left, int mid, int right)
{
	//临时数组,用来合并
	int len = right - left + 1;
	int* tmp = new int[len];

	//i为左区间index,j为右区间index,k为tmp数组index
	int i = left, j = mid + 1, k = 0;
	while (i <= mid && j <= right)
	{
		//左区间<右区间首元素,加入data[left]到tmp数组
		if (data[i] < data[j])
			tmp[k++] = data[i++];
		//反之,加入data[mid+1] 到tmp 数组
		else
			tmp[k++] = data[j++];
	}

	//若左区间剩余为空,将右区间剩余加入tmp数组
	if (i > mid)
	{
		while (j <= right)
			tmp[k++] = data[j++];
	}
	//若右区间剩余为空,将左区间剩余加入tmp数组
	else
	{
		while (i <= mid)
			tmp[k++] = data[i++];
	} 

	//将tmp数组copy到data[left,right]
	for (int i = 0; i < len; i++)
	{
		data[i + left] = tmp[i];
	}

	delete[] tmp;
}

//归并排序内部递归部分,[left,right]区间
void __merge_sort(int data[], int left, int right)
{
	//递归终止条件
	if (left >= right)
		return;
	
	int mid = left + ((right - left) >> 2);

	__merge_sort(data, left, mid);
	__merge_sort(data, mid + 1, right);

	merge(data, left, mid, right);
}

//data为待排序数组,n为数组大小,采用从小到大方式排序
void merge_sort(int data[], int n)
{
	__merge_sort(data, 0, n - 1);
}

5. 快速排序

快速排序与归并排序类似,但不同的是:快速排序通过找到pivot枢点来控制左右区间的划分;同时,归并排序处理问题是先划分区间,再处理,快排是通过先处理,再划分。

快排可以通过调整partition分区函数来使时间复杂度更稳定与O(nlogn),例如三数取中,随机数等方法。是非稳定排序,但由于空间复杂度为O(1),源码库中排序底层也都运用了快排,或与其他排序相结合。

//分区函数,采用取最后一个元素为pivot,
//返回分区结点的下标
int partition(int data[], int left, int right)
{
	int pivot = data[right];

	//[left,r)为小于pivot部分,(r,right]为大于pivot部分
	int r = left;
	for (int i = left; i < right; i++)
	{
		//小于pivot加入到[left,r)最后
		if (data[i] <= pivot)
		{
			swap(data[i], data[r]);
			++r;
		}
	}

	swap(data[r], data[right]);
	return r;
}

//快速排序内部递归部分,[left,right]区间
void __quick_sort(int data[], int left, int right)
{
	if (left >=  right)
		return;
	
	//对原区间进行分区
	int pos = partition(data, left, right);

	__quick_sort(data, left, pos - 1);
	__quick_sort(data, pos + 1, right);
}

//data为待排序数组,n为数组大小
void quick_sort(int data[], int n)
{
	__quick_sort(data, 0, n - 1);
}

6. 桶排序

桶排序的核心思想是通过将数据先分到几个有序的桶里,其中这些桶天然有序(例如可以将0 ~ 99分为10个桶,0 ~ 9, 10 ~ 19,20 ~ 29...) ,然后再对每个桶进行排序。排序完成之后,将各个桶的数据依次组合,就能得到有序的序列。

核心部分是能分为有序的桶,要求每个桶的数据尽量均匀,适合用于外部排序中。

7. 计数排序

可以认为计数排序是桶排序的一种特殊情况,当每个桶的粒度大小为1时,遍历一遍数据,加入桶中,依次遍历即为有序序列。

计数排序只能用于数据范围不大的场合,对一些特殊的数字要进行转换,转换为非负整数。

8. 基数排序

基数排序通过将数据通过按位来排序,例如11位的手机号码,可以先根据第一位手机号排序,再根据第二位,直到第11位,可以得到一个有序的序列。中间可以采用桶排序、计数排序等其他排序。

每一位的数据范围也不能很大。

9. 排序总结

排序算法最差情况时间复杂度最好情况时间复杂度平均时间复杂度空间时间复杂度(原地排序?)稳定性使用场景
冒泡排序O(n2)O(n)O(n2)O(1)稳定排序一般不采用
插入排序O(n2)O(n)O(n2)O(1)稳定排序源码中数据量小时,采用
选择排序O(n2)O(n2)O(n2)O(1)非稳定排序一般不采用
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定排序由于使用额外空间较多,一般选用快排O(1)
快速排序O(n2)O(nlogn)O(nlogn)O(1)非稳定排序源码中数据量大时,采用
桶排序--O(n)O(n)稳定排序不是基于比较,适用于外部排序
计数排序--O(n+k) k为数据范围O(n)稳定排序-
基数排序--O(d*n) d为分配的维度-稳定排序-
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值