八大排序汇总(C++)

本文详细介绍了八大排序算法,包括冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序、希尔排序的时间复杂度和稳定性,并通过实例分析了它们的特点和应用场景。时间复杂度是评价算法效率的重要标准,稳定性和空间复杂度也是选择排序算法的关键因素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

目录

 

认识时间复杂度

排序算法中的稳定性

八大排序性能对比​

冒泡排序

选择排序

插入排序

归并排序

快速排序

堆排序

希尔排序


  • 认识时间复杂度

常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。

时间复杂度:时间复杂度为一个算法流程中,在最差情况下,常数操作数量的指标。常用O(读作bigO)来表示。具体来说,在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项系数,剩下的部分如果记为f(N),那么时间复杂度为O(f(N))。

例如:

  1. 计算操作次数为(An^2+Bn+C),A为高阶项系数,Bn为低阶,B为低阶系数,时间复杂度我们只要n^2,故时间复杂度为O(n^2)。
  2. O(1)称为常数时间,具体的值和样本量没有关系。

评价算法的标准:评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间,也就是常数项时间。那么O(N)比O(N^2)好。如果两个算法都是O(N),他们的常数操作数量的表达式分别为 1000N+2和10N+10,那么就是10N+10这个算法更好。

一个简单地理解时间复杂度的例子:

一个有序数组A,另一个无序数组B,请打印B中的所有不在A中的数,A数组长度为N,B数组的长度为M。

算法流程1:对于数组B中的每一个数,都在A中通过遍历的方式找一下。这个时间复杂度为O(M*N)

算法流程2:对于数组B中的每一个数,都在A中通过二分的方式找一下。一个数在A中二分查找的时间复杂度为O(logN),即若有N个有序数,我们从中查找到我指定的数最差需要logN次。所以时间复杂度为 M*O(logN)

算法流程3:先把数组B排序,然后用类似外排的方式打印所有出现在A中出现的数。我们先计B排序用的是快排,时间复杂度为O(MlogM),然后外排的时间复杂度为O(M+N),所以整个算法的时间复杂度为O(MlogM)+O(M+N)。现在我们可以分类讨论了。

  1. 如果N很小,那么复杂度为O(MlogM) + O(M) = O(MlogM + Mlog2) = O(Mlog2M) = O(MlogM)
  2. 如果M相比N很小,那么复杂度为O(M+N)

所以这个例子要有具体的样本量才能区分算法流程2和算法流程3哪个好。

  1. 如果N更小,流程2更好
  2. 如果M更小,流程3更好

  • 排序算法中的稳定性

排序算法稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

举个例子:

对于数组{10,6,8,11,6,5,4,1001,2,1 },对于第一个6和第二个6,他们在经过排序以后,第一个6还是在第二个6的前面,则称这个算法是稳定的。而,如果第一个6和第二个6的在排序后,两个数字的顺序不确定,一会儿第一个6在前,一会第二个6在前,则这个算法为不稳定算法。


  • 八大排序性能对比

 


  • 冒泡排序

时间复杂度:O(N^2)   ——> N +(N-1) + (N-2)+···+2+1 = AN^2 +BN +C

最好时间复杂度为:O(N^2) 

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

特点:有限个数即可排完,不需要辅助数组。稳定算法。

应用:工程上不常用,效率不高,适合小数据排序

代码:

#include "pch.h"
#include <iostream>
#include <stack>
#include <vector>
#include<string>

using namespace std;

void swap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;

}

void BubbleSort(int arr[], int n) {
	while (n-- > 0) {
		for (int i = 0; i < n; i++) {
			if (arr[i] > arr[i+1]) {
				swap(arr[i],arr[i+1]);
			}
		}
	}
}

int main()
{
	int a[10] = { 10,9,8,11,6,5,4,1001,2,1 };
	BubbleSort(a, 10);
	for (int i = 0; i < 10; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;

	return 0;
}

思路分析:

  1. 首先把数组的最后一个数,排为最大的数,然后将数组的倒数第二个数,排为第二大的数,依次从后往前把数组的每个下标排好。
  2. 如果一个数比它后一个数大,则将交换这两个数的位置。


  • 选择排序

时间复杂度:O(N^2)

最好时间复杂度为:O(N^2) 

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

特点:有限个数即可排完,不需要辅助数组。不稳定算法。

应用:工程上不常用,数据量大时,它的效率明显高于冒泡,因为选择排序数据移动次数少。

代码:

#include "pch.h"
#include <iostream>
#include <stack>
#include <vector>
#include<string>

using namespace std;

void swap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;

}

void SelectSort(int arr[], int n) {
	int minindex;
	for (int i = 0; i < (n - 1); i++) {
		minindex = i;
		for (int j = i + 1; j < n; j++) {
			if (arr[minindex] > arr[j]) {
				minindex = j;
			}
		}
		swap(arr[i],arr[minindex]);
	}
}

int main()
{
	int a[10] = { 10,9,8,11,6,5,4,1001,2,1 };
	SelectSort(a, 10);
	for (int i = 0; i < 10; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;

	return 0;
}

思路分析:

  1. 这个排序和冒泡排序恰好相反,首先把最小的数字找到放在数组第一位上,依次从前往后从小到大地把数组排好。
  2. 从前往后依次遍历数组中的数,当前数和剩下数组中最小的数相交换,然后继续遍历下一个数。


  • 插入排序

时间复杂度:O(N^2)

最好时间复杂度:O(N)

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

特点:时间复杂度与数据状况有关。和冒泡排序和选择排序不一样,这两个排序的时间复杂度和数据是否有序无关,而插入排序和数据是否有序是有关系的,如果数据有序则插入排序的时间复杂度为O(N).插入排序是稳定的排序算法。

应用:工程上常用

代码:

#include "pch.h"
#include <iostream>
#include <stack>
#include <vector>
#include<string>

using namespace std;

void swap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;

}

void InsertSort(int arr[], int n) {
	for (int i = 1; i < n; i++) {
		for (int j = i - 1; (j >= 0 && (arr[j] > arr[j + 1])); j--) {
			swap(arr[j], arr[j + 1]);
		}
	}
}

int main()
{
	int a[] = { 1,3,12,10,6,8,11,6,5,4,1001,2,1 };
	int n = sizeof(a) / sizeof(a[0]);
	InsertSort(a, n);
	for (int i = 0; i < n; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;

	return 0;
}

思路分析:

  1. 每步将一个待排序的数字,按其数值的大小插入前面已经排序的数字中的适当位置上,直到全部插入完为止。
  2. 在数组中,从数组下标为1的位置开始,从前往后遍历需要排序的数字。
  3. 把关键的代码InsertSort那段短小的代码记下来就OK。


  • 归并排序

时间复杂度:O(NlogN)

最好时间复杂度:O(NlogN)

(额外)空间复杂度:O(N)

特点:时间复杂度于数据是否有序的状况无关。是稳定排序算法。它是高级排序算法中,唯一一个稳定的排序算法。

应用:要求排序稳定,空间不重要,则使用归并算法。

代码:

#include "pch.h"
#include <iostream>
#include <stack>
#include <vector>
#include<string>

using namespace std;

void swap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;

}

void merge(int*a, int start, int end, int* result) {
	int leftlen = (end - start) / 2 + 1;

	int left_index = start;
	int right_index = start + leftlen;
	int result_index = start;
	while (left_index < start + leftlen && right_index < end + 1) {
		if (a[left_index] <= a[right_index]) {
			result[result_index++] = a[left_index++];
		}
		else {
			result[result_index++] = a[right_index++]; 
		}
	}

	while (left_index < start + leftlen) {
		result[result_index++] = a[left_index++];
	}
	while (right_index < end + 1) {
		result[result_index++] = a[right_index++];
	}
}


void merge_sort(int* a, int start, int end, int* result) {
	if (end - start == 1) {//只有两个数了
		if (a[start] > a[end]) {
			swap(a[start], a[end]);
			return;
		}
	}
	else if (start == end) {
		return;
	}
	else {
		merge_sort(a, start, (end - start) / 2 + start, result); //把左边排好序了
		merge_sort(a, (end - start) / 2 + start + 1, end, result); //把右边排好序了

		merge(a, start, end, result);

		for (int i = start; i < end + 1; i++) {
			a[i] = result[i];
		}
	}
}

int main()
{
	int a[] = { 1,3,12,10,6,8,11,6,5,4,1001,2,1 };
	
	const int length = sizeof(a) / sizeof(a[0]);

	cout << "数组原始顺序: ";
	for (int i = 0; i < length; i++) {
		cout << a[i] << " ";
	}
	cout << endl;

	int result[length];
	merge_sort(a, 0,length-1,result);
	cout << "数组排序后的顺序: ";
	for (int i = 0; i < length; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;
	return 0;
}

思路分析:以下内容来自百度百科

归并排序主要分为两部分:

1、划分子区间

2、合并子区间

现在以 9,6,7,22,20,33,16,20 为例讲解上面两个过程:

第一步,划分子区间:每次递归的从中间把数据划分为左区间和右区间。原始区间为[start,end],start=0,end=[length-1],减一是因为数组的下标从0开始,本例中length=8,end=7.现在从中间元素划分,划分之后的左右区间分别为 [start,(end-start+1)/2+start],右区间为[(end-start+1)/2+start+1,end],本例中把start和end带入可以得到[0,7],划分后的左右子区间为[0,4],[5,7],然后分别对[start,end]=[0,4]和[start,end]=[5,7]重复上一步过程,直到每个子区间只有一个或者两个元素。整个分解过程为:

 

子区间划分好以后,分别对左右子区间进行排序,排好序之后,在递归的把左右子区间进行合并,整个过程如下图所示:

 以上代码及思路转自参考链接:http://www.cnblogs.com/rio2607/p/4489893.html


  • 快速排序

时间复杂度:O(nlogn)

最好时间复杂度:O(nlogn) ——对应——(额外)空间复杂度:O(logn)

最坏时间复杂度:O(n^2) ——对应——(额外)空间复杂度:O(n) ——最坏情况快排退化为冒泡

平均时间复杂度:O(nlogn)

特点:是不稳定的算法。

应用:当数据集较大时,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

代码:

#include "pch.h"
#include <iostream>

using namespace std;

void swap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;

}

void quick_sort(int* a, int start, int end) {
	//递归过程先写结束条件
	if (start > end) return;
	/*if ((end - start) == 1) {  //下面的算法包含了这种情况的
		if (a[end] < a[start]) {
			swap(a[start], a[end]);
		}
		return;
	}*/

	//分而治之
	int i = start;
	int j = end;
	int temp = a[start];

	while (i != j) {
		//总是先动最右边的指针
		while ((i < j) && a[j] >= temp) {
			j--;
		}
		while ((i < j) && a[i] <= temp) {
			i++;
		}
		//交换i,j指针指向的值
		if (i < j) {
			swap(a[i], a[j]);
		}		
	}
	//把基准数放入它该在的位置
	swap(a[start],a[i]);

	//递归基准数左边的数组
	quick_sort(a, start, i - 1);
	//递归基准数右边的数组
	quick_sort(a, i + 1, end);

}

int main()
{
	int a[] = { 1,3,12,10,6,8,11,6,5,4,1001,2,1,99,78,79,20,26,33,77 };
	
	const int length = sizeof(a) / sizeof(a[0]);

	cout << "数组原始顺序: ";
	for (int i = 0; i < length; i++) {
		cout << a[i] << " ";
	}
	cout << endl;

	quick_sort(a, 0,length-1);
	cout << "排序后的顺序: ";
	for (int i = 0; i < length; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;
	return 0;
}

思路分析:快速排序从小到大排序:在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。


  • 堆排序

时间复杂度:O(nlogn)

最好时间复杂度:O(nlogn) 

最坏时间复杂度:O(nlogn) 

平均时间复杂度:O(nlogn)

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

特点:是不稳定的算法。

应用:当数据集较大时,所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况

代码:

#include "pch.h"
#include <iostream>

using namespace std;
void swap(int &a, int &b);
void heap_sort(int* a, int len);
void BuildMaxHeap(int* a, int len);
void MaxHeap(int* a, int n, int size);

void swap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;
}

void heap_sort(int* a, int len) {
	//将数组初始化为大堆
	BuildMaxHeap(a, len);
	int size = len;
	for (int i = size; i > 1; i--) {
		swap(a[0], a[i-1]);
		size--;
		MaxHeap(a,1,size);
	}
}

//堆的编号从1~n,对应数组下标0~n-1
void BuildMaxHeap(int* a, int len) {
	int n = len / 2; 
	for (int i = n; i >= 1; i--) {//从倒数第二层的最右结点开始遍历到第一个堆的编号第一的节点。
		MaxHeap(a, i, len);
	}
}

void MaxHeap(int* a, int n, int size) {
	int largestIndex, leftChild, rightChild;
	leftChild = n * 2;
	rightChild = n * 2 + 1;
	
	if (leftChild <= size && (a[leftChild - 1] > a[n - 1])) {
		largestIndex = leftChild;
	}
	else {
		largestIndex = n;
	}
	if (rightChild <= size && (a[rightChild - 1] > a[largestIndex - 1])) {
		largestIndex = rightChild;
	}
	if (largestIndex != n) {
		swap(a[n - 1], a[largestIndex-1]);
		MaxHeap(a, largestIndex, size);
	}
}

int main()
{
	int a[] = { 1,3,12,10,6,8,11,6,5,4,1001,2,1,99,78,79,20,26,33,77 };

	int length = sizeof(a) / sizeof(a[0]);

	cout << "数组原始顺序: ";
	for (int i = 0; i < length; i++) {
		cout << a[i] << " ";
	}
	cout << endl;

	heap_sort(a, length);
	cout << "排序后的顺序: ";
	for (int i = 0; i < length; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;
	return 0;
}

思路分析:

  1. 首先将数组元素建成大小为n的大顶堆,堆顶(数组第一个元素)是所有元素中的最大值
  2. 然后将堆顶元素和数组最后一个元素进行交换
  3. 随后,将除了最后一个数的n-1个元素建立成大顶堆,再将最大元素和数组倒数第二个元素进行交换
  4. 数组下标从后往前排好,按照2,3 步骤重复直至堆大小减为1。


  • 希尔排序

时间复杂度:

最好时间复杂度:

最坏时间复杂度:

平均时间复杂度:

(额外)空间复杂度:

特点:是不稳定的算法。

应用:

代码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值