数据结构必备:深度剖析八大经典排序算法

冒泡排序

原理:通过相邻元素之间的比较和交换,将最大(小)的元素逐步 “冒泡” 到序列的末尾。每一趟比较都能确定一个最大(小)元素的最终位置。

时间复杂度:平均时间复杂度O(n^{2}),最好时间复杂度O(n),最坏时间复杂度O(n^{2})

空间复杂度:O(1)

稳定性:稳定

选择排序

原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

时间复杂度:平均时间复杂度O(n^{2}),最好和最坏时间复杂度都为O(n^{2})

空间复杂度:O(1)

稳定性:不稳定

插入排序

原理:将前面的数据看作已经排序完成的有序数据,将后面的数据插入前面数据中首个小于等于它自身的位置之后,重复上述过程直到数据有序。(注:若数据本身趋于有序,插入排序是所有排序算法中,效率最高的排序算法)

时间复杂度:平均时间复杂度O(n^{2}),最好时间复杂度O(n),最坏时间复杂度O(n^{2})

空间复杂度:O(1)

稳定性:稳定

希尔排序

原理:让数据先趋于有序,再对数据进行插入排序。

时间复杂度:平均时间复杂度O(n^{2})

空间复杂度:O(1)

稳定性;不稳定

上述四种排序算法比较;

基本算法 空间复杂度 时间复杂度 稳定性
冒泡排序 O(1) O(n^{2}) 稳定
选择排序 O(1) O(n^{2}) 不稳定
插入排序 O(1) O(n^{2}) 稳定
希尔排序 O(1) O(n^{2}) 不稳定
基本算法 100 1000 10000 100000
冒泡排序 2.4e-05s 0.001799s 0.152341s 24.7368s
选择排序 1.3e-05s 0.00049s 0.041018s 4.57467s
插入排序 6e-06s 0.000367s 0.03789s 3.95476s
希尔排序 6e-06s 0.000112s 0.001235s 0.023815s

代码如下;

#include 
#include 
#include 
#include  
using namespace std;

//冒泡排序

void BubbleSort(int arr[], int size)
{
	for (int i = 0; i < size; i++)
	{
		bool flag = false;
		for (int j = i; j < size - 1; j++)
		{
			if (arr[j] >= arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = true;
			}
		}
		if (!flag)
		{
			break;
		}
	}
}

//选择排序

void ChoiceSort(int arr[], int size)
{
	for (int i = 0; i < size; i++)
	{
		int val = arr[i];
		int k = i;
		for (int j = i + 1; j < size; j++)
		{
			if (arr[j] < val)
			{
				val = arr[j];
				k = j;
			}
		}
		if (k != i)
		{
			int tmp = arr[i];
			arr[i] = arr[k];
			arr[k] = tmp;
		}
	}
}

//插入排序
void InsertSort(int arr[], int size)
{
	for (int i = 1; i < size; i++)
	{
		int val = arr[i];
		int j = i - 1;
		for (; j >= 0; j--)
		{
			if (arr[j] <= val)
			{
				break;
			}
			arr[j + 1] = arr[j];
		}
		arr[j + 1] = val;
	}
}

//希尔排序
void ShellSort(int arr[], int size)
{
	int gap = size / 2;
	for (; gap > 0; gap /= 2)
	{
		for (int i = gap; i < size; i++)
		{
			int val = arr[i];
			int j = i - gap;
			for (; j >= 0; j -= gap)
			{
				if (arr[j] <= val)
				{
					break;
				}
				arr[j + gap] = arr[j];
			}
			arr[j + gap] = val;
		}
	}
}
// 复制数组
void copyArray(int source[], int destination[], int size) {
	for (int i = 0; i < size; i++) {
		destination[i] = source[i];
	}
}

// 比较四种排序算法的运行时间
void compareSortingTimes(int arr[], int size) {
	int* arrCopy = new int[size];

	// 冒泡排序计时
	copyArray(arr, arrCopy, size);
	auto start = std::chrono::high_resolution_clock::now();
	BubbleSort(arrCopy, size);
	auto end = std::chrono::high_resolution_clock::now();
	auto durationBubble = std::chrono::duration_cast(end - start).count();
	std::cout << "冒泡排序运行时间: " << static_cast(durationBubble) / 1000000 << " 秒" << std::endl;

	// 选择排序计时
	copyArray(arr, arrCopy, size);
	start = std::chrono::high_resolution_clock::now();
	ChoiceSort(arrCopy, size);
	end = std::chrono::high_resolution_clock::now();
	auto durationChoice = std::chrono::duration_cast(end - start).count();
	std::cout << "选择排序运行时间: " << static_cast(durationChoice) / 1000000 << " 秒" << std::endl;

	// 插入排序计时
	copyArray(arr, arrCopy, size);
	start = std::chrono::high_resolution_clock::now();
	InsertSort(arrCopy, size);
	end = std::chrono::high_resolution_clock::now();
	auto durationInsert = std::chrono::duration_cast(end - start).count();
	std::cout << "插入排序运行时间: " << static_cast(durationInsert) / 1000000 << " 秒" << std::endl;

	// 希尔排序计时
	copyArray(arr, arrCopy, size);
	start = std::chrono::high_resolution_clock::now();
	ShellSort(arrCopy, size);
	end = std::chrono::high_resolution_clock::now();
	auto durationShell = std::chrono::duration_cast(end - start).count();
	std::cout << "希尔排序运行时间: " << static_cast(durationShell) / 1000000 << " 秒" << std::endl;

	delete[] arrCopy;
}

int main()
{
	const int size = 1000000;
	int arr[size];
	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_int_distribution<> dis(0, 1000000);

	// 生成随机数组
	for (int i = 0; i < size; i++) {
		arr[i] = dis(gen);
	}

	// 比较四种排序算法的运行时间
	compareSortingTimes(arr, size);

	return 0;
}

快速排序

原理:选择一个合适的基准数,把小于基准数的元素调整到基准数的左边,把大于基准数的数字调整到基准数的右边。然后对基准数的两边重复上述操作,直到数据有序。

时间复杂度:平均时间复杂度O(n\log_{2}n),最好时间复杂度O(n\log_{2}n),最坏时间复杂度O(n^{2})

空间复杂度:最好空间复杂度O(\log_{2}n),最坏空间复杂度O(n)

稳定性:不稳定

归并排序

原理:采用分治思想,把序列划分对每个子数组进行排序再进行元素的有序合并。

时间复杂度:平均时间复杂度O(n\log_{2}n),最好和最坏时间复杂度O(n\log_{2}n)

空间复杂度:O(n)

稳定性:稳定

堆排序

原理:将待排序序列构造成一个大顶堆或小顶堆,此时,整个序列的最大值或最小值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值或最小值。然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n个元素的次大值或次小值。如此反复执行,便能得到一个有序序列。

时间复杂度:平均时间复杂度O(n\log_{2}n),最好和最坏时间复杂度O(n\log_{2}n)

空间复杂度:O(1)

稳定性:不稳定

上述算法比较:

基本算法 时间复杂度 空间复杂度 稳定性
快速排序 O(n\log_{2}n) O(\log_{2}n) 不稳定
归并排序 O(n\log_{2}n) O(n) 稳定
堆排序 O(n\log_{2}n) O(1) 不稳定
基本算法 10000 100000 1000000 10000000 100000000
快速排序 0s 0.007s 0.0612s 0.6634s 9.591s
归并排序 0.0038s 0.03s 0.2642s 2.7254s 40.065s
堆排序 0.001s 0.012s 0.122s 1.7928s 29.8044s

代码如下:

#include 
#include 
#include 
#include 
using namespace std;
//快速排序
int Pos(int arr[], int begin, int end)
{
	int val = arr[begin];
	while (begin < end)
	{
		while (begin val)
		{
			end--;
		}
		if (begin < end)
		{
			arr[begin] = arr[end];
			begin++;
		}
		while (begin < end && arr[begin] < val)
		{
			begin++;
		}
		if (begin < end)
		{
			arr[end] = arr[begin];
			end--;
		}
	}
	arr[begin] = val;
	return begin;
}
void InsertSort(int arr[], int begin, int end)
{
	int size = sizeof arr / sizeof arr[0];
	for (int i = 0; i < size; i++)
	{
		int val = arr[i];
		int j = i - 1;
		for (; j >= 0; j--)
		{
			if (arr[j] > val)
			{
				return;
			}
			arr[j + 1] = arr[j];
		}
		arr[j + 1] = val;
	}
}
void QuickSort(int arr[], int i, int j)
{
	if (i >= j)//快排递归结束的条件
	{
		return;
	}
	//在[i,j]之间做一次快排分割
	//插入排序
	if (j - i < 20)
	{
		InsertSort(arr, i, j);
		return;
	}
	int pos = Pos(arr, i, j);
	//对基准数的左右分别进行快排
	QuickSort(arr, i, pos - 1);
	QuickSort(arr, pos + 1, j);
}
void QuickSort(int arr[], int size)
{
	return QuickSort(arr, 0, size - 1);
}

//归并排序
void Merge(int arr[], int l, int m, int r)
{
	int* p = new int[r - l + 1];
	int i = l;
	int j = m + 1;
	int index = 0;
	while (i <= m && j <= r)
	{
		if (arr[i] <= arr[j])
		{
			p[index++] = arr[i++];
		}
		else
		{
			p[index++] = arr[j++];
		}
	}
	while (i <= m)
	{
		p[index++] = arr[i++];
	}
	while (j <= r)
	{
		p[index++] = arr[j++];
	}
	for (int i = l, j = 0; i <= r; i++, j++)
	{
		arr[i] = p[j];
	}
	delete[]p;
}

void MergeSort(int arr[], int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	MergeSort(arr, begin, mid);
	MergeSort(arr, mid + 1, end);
	Merge(arr, begin, mid, end);
}

void MergeSort(int arr[], int size)
{
	MergeSort(arr, 0, size - 1);
}

//堆排序
// 向下调整堆
void SiftDown(int arr[], int k, int size) {
	int val = arr[k];
	while (k < size / 2)
	{
		int child = 2 * k + 1;
		if (child + 1 < size && arr[child + 1] > arr[child])
		{
			child = child + 1;
		}
		if (arr[child] > val)
		{
			arr[k] = arr[child];
			k = child;
		}
		else
		{
			break;
		}
	}
	arr[k] = val;
}

// 堆排序
void HeapSort(int arr[], int size)
{
	int n = size - 1;
	// 构建最大堆
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		SiftDown(arr, i, size);
	}

	// 排序过程
	for (int i = n; i > 0; i--)
	{
		// 交换堆顶元素和当前堆的最后一个元素
		int tmp = arr[0];
		arr[0] = arr[i];
		arr[i] = tmp;
		// 调整堆
		SiftDown(arr, 0, i);
	}
}
// 比较排序算法运行时间的函数
void compareSortingTimes(int size) {
	std::cout << "数据规模: " << size << std::endl;
	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_int_distribution<> dis(0, 1000);

	double totalQuick = 0;
	double totalMerge = 0;
	double totalHeap = 0;

	for (int trial = 0; trial < 5; ++trial) {
		int* arr = new int[size];
		for (int i = 0; i < size; i++) {
			arr[i] = dis(gen);
		}

		int* arrQuick = new int[size];
		int* arrMerge = new int[size];
		int* arrHeap = new int[size];
		for (int i = 0; i < size; i++) {
			arrQuick[i] = arr[i];
			arrMerge[i] = arr[i];
			arrHeap[i] = arr[i];
		}

		// 快速排序计时
		auto startQuick = std::chrono::high_resolution_clock::now();
		QuickSort(arrQuick, size);
		auto endQuick = std::chrono::high_resolution_clock::now();
		auto durationQuick = std::chrono::duration_cast(endQuick - startQuick).count();
		totalQuick += durationQuick;

		// 归并排序计时
		auto startMerge = std::chrono::high_resolution_clock::now();
		MergeSort(arrMerge, size);
		auto endMerge = std::chrono::high_resolution_clock::now();
		auto durationMerge = std::chrono::duration_cast(endMerge - startMerge).count();
		totalMerge += durationMerge;

		// 堆排序计时
		auto startHeap = std::chrono::high_resolution_clock::now();
		HeapSort(arrHeap, size);
		auto endHeap = std::chrono::high_resolution_clock::now();
		auto durationHeap = std::chrono::duration_cast(endHeap - startHeap).count();
		totalHeap += durationHeap;

		delete[] arr;
		delete[] arrQuick;
		delete[] arrMerge;
		delete[] arrHeap;
	}

	std::cout << "快速排序平均运行时间: " << totalQuick / 5 / 1000 << " 秒" << std::endl;
	std::cout << "归并排序平均运行时间: " << totalMerge / 5 / 1000 << " 秒" << std::endl;
	std::cout << "堆排序平均运行时间: " << totalHeap / 5 / 1000 << " 秒" << std::endl;
	std::cout << std::endl;
}

int main() {
	int size = 100000000;
	compareSortingTimes(size);
	return 0;
}

基数排序

原理:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

时间复杂度:平均时间复杂度为O(d(n+k)),其中 d 是位数,n 是数据个数,k是基数。

空间复杂度:O(n+k)

稳定性:稳定

STL中sort用了什么排序算法?

sort用了内省排序(一种混合排序算法)主要有快速排序,堆排序和插入排序。

快速排序:内省排序在开始阶段会优先使用快速排序,因为快速排序在平均情况下具有较好的性能,其平均时间复杂度为 O(n\log_{2}n)。但是,快速排序在最坏情况下(例如数组已经有序或接近有序)的时间复杂度会退化为 O(n^{2})

插入排序:在数据趋于有序的情况下,插入排序是效率最好的排序,因此在子数组规模小到一定程度时会使用插入排序,其常数因子较小,性能可能比快速排序和堆排序更好

堆排序:为了避免快速排序在最坏情况下的性能问题,当递归深度到达一定阈值时会使用堆排序,以保证在最坏情况下也能达到 O(n\log_{2}n)的时间复杂度。

你可能感兴趣的:(排序算法,数据结构,算法)