十大排序算法对比和C++实现

Posted mingogo乔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十大排序算法对比和C++实现相关的知识,希望对你有一定的参考价值。

一、性能对比

算法平均时间复杂度最好情况最坏情况稳定性优点缺点
插入排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2)稳定比较次数少;适用于基本有序的数列交换次数多,插入成本大
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)稳定交换次数少比较次数多,任何情况都多
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)稳定-费时费力
堆排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn)不稳定
快速排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2) O(n2)不稳定
希尔排序 < O ( n 2 ) <O(n^2) <O(n2) < O ( n 2 ) <O(n^2) <O(n2) < O ( n 2 ) <O(n^2) <O(n2)不稳定
归并排序
基数排序
计数排序

二、基本的排序算法实现

1. 插入排序

将第i个元素作为label,插入到 i 之前的合适位置;
每一趟排序结束后,i 位置前元素有序;

缺点是需要进行多次元素的赋值,如果有数据插入,所有之前比它大的数字都要移动;
优点是有需要时候才会进行排序,最好情况下只需要 O ( n ) O(n) O(n)次比较,不需要移动,适合用在元素基本有序的情况。

void insertionsort(vector<int>& data) {
	int sz = data.size();
	for (int i = 1; i < sz; ++i) {
		int label = data[i],j = i;
		for (; j > 0 && data[j - 1] > label; --j) {
			data[j] = data[j - 1];
		}
		data[j] = label;
	}
}

2. 选择排序

在第i个元素之后选择最小的元素,放在第i个位置;
每一趟排序后,第i个元素前元素全局有序;
进行元素的交换;

优点是赋值次数少;
缺点是最好情况和最坏情况复杂度相同,不论任何情况都要进行 O ( n 2 ) O(n^2) O(n2)次比较。

void selection(vector<int>& data) {
	int sz = data.size();
	for (int i = 0; i < sz; ++i) {
		int label = i;//最小元素的坐标
		for (int j = i + 1; j < sz; ++j) {
			if (data[j] < data[label]) {
				label = j;
			}
		}
		swap(data, i, label);
	}
}

3. 冒泡排序

笨蛋版本的选择排序,每趟排序后的结果和选择排序相同,但增加了很多交换次数。
貌似没有发现什么优点的亚子。。。
缺点是费时费力,和选择排序相同,不论任何情况都要进行 O ( n 2 ) O(n^2) O(n2)次比较,交换次数又和需要多次赋值的插入排序相同。

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

三、高效的排序算法实现

1. 希尔排序

基本思路就是分成子序列进行预排序,间隔从大到小,每种间隔h分别有h个子序列。

复杂度比较难于分析,大致是 O ( n m ) , 1 < m < 2 O(n^m),1<m<2 O(nm),1<m<2

void shellsort(vector<int>&data){
	int sz = data.size();
	//构建最优的间隔序列
	stack<int> H;
	int h = 1;
	while(h < sz){
		H.push(h);
		h = 3 * h + 1;
	}

	//从大到小遍历间隔序列
	while(!H.empty()){
		h = H.back();
		H.pop();
		//每种间隔有h个子序列
		for(int j = h; j < 2*h && j < sz; ++j){
			//对每个子序列进行排序,考虑到序列元素较少且基本有序,这里使用插入排序
			for(int k = j; k < sz; k += h){
				int label = data[k], t = k;
				for(; t-h >= 0 && data[t-h] > label; t -= h){
					data[t] = data[t-h];
				}
				data[t] = label;
			}
		}
	}
}

2. 堆排序

要排序的数列是一个层序遍历的树;
维护一个大顶堆,每次交换队首元素和队尾元素,即,将原本位于队首的最大的元素置于最后,然后将大顶堆size-1,进行更新维持为大顶堆。

简要理解其复杂度,大致需要进行 n n n 次交换队首和队尾元素,每次交换后更新树,需要进行树的深度(即 l o g i logi logi )次的最大元素上传,总复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

C++中有priority_queue容器,本文不使用容器手撕大顶堆。

void heapify(vector<int>& data, int i, int size) {
	if (i > (size - 1) / 2) {
		return;
	}
	//对当前子堆进行堆排序,找到最大值
	int max = i;
	if (2*i + 1 < size && data[2*i + 1] > data[max]) {
		max = 2*i + 1;
	}
	if (2*i + 2 < size && data[2*i + 2] > data[max]) {
		max = 2*i + 2;
	}
	if (max != i) {
		swap(data, i, max);
		//进行交换后,可能破坏了已经完成的下层堆排序,需要对交换过的节点进行,进行一个推排序递归
		heapify(data, max, size);
	}

}

void buildheap(vector<int>& data, int size) {
	//从底而上遍历根节点们
	for (int i = (size-1) / 2 ; i >= 0; --i) {
		heapify(data, i, size);
	}
}
//排序
void heapsort(vector<int>& data) {
	int sz = data.size();
	while (sz > 0) {
		buildheap(data, sz);
		swap(data, 0, sz-1);
		sz--;
	}
}

3. 快速排序

选择一个bound,所有比它小的元素都在左边,比它大的元素都在右边,然后对左右序列递归。

通过划分左右序列,减少比较次数。
最优情况下每次划分都分在中间,划分了 l o g n logn logn 次;最劣情况下每次有一个子序列只有1个元素,划分了 n n n 次。
每次划分的子序列加起来进行了 n n n 次比较,因此,最优时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最差为 O ( n 2 ) O(n^2) O(n2)
一般情况的复杂度接近最优情况,为 O ( n l o g n ) O(nlogn) O(nlogn)

双指针写的快排算法,额外空间复杂度为 O ( 1 ) O(1) O(1)

void quicksort(vector<int>&data,int first, int last){
	swap(nums, first, (first+last)>>1);
	int bound = data[first], lower = first + 1, upper = last;
	while(lower <= upper){
		while(lower <= last && data[lower] <= bound) ++lower;
		while(upper > first && data[upper] >= bound) --upper;
		if(lower < upper) swap(data, lower, upper);
	}
	//upper右边的元素都>=bound
	//upper此时指向的值小于bound, 也就是first位置的值
	swap(data, first, upper);
	//此时upper位置就是bound,判断它的左右两边
	//如果不止一个元素,就递归进行快排
	if(upper-1 > first) quicksort(data, first, upper-1);
	if(upper+1 < last) quicksort(data, upper+1, last);
}

//调用
//quicksort(d, 0, d.size()-1);

4. 归并排序

5. 基数排序

6. 计数排序

以上是关于十大排序算法对比和C++实现的主要内容,如果未能解决你的问题,请参考以下文章

十大排序算法对比和C++实现

十大经典算法排序总结对比

js 十大经典算法排序总结对比

十大经典排序算法的算法描述和代码实现

经典十大排序算法(含升序降序,基数排序含负数排序)Java版完整代码建议收藏系列

十大经典排序算法详解