数据结构与算法_排序算法_三个高级排序算法性能对比

本文分析了快速排序、归并排序和堆排序三种高级排序算法的性能。在800万和8000万个数据的排序测试中,快速排序表现最佳,其次是归并排序,堆排序耗时最长。这主要是因为快速排序和归并排序对CPU缓存更友好,而堆排序由于其非顺序操作和较多无效比较导致效率较低。
摘要由CSDN通过智能技术生成

堆排序,快排,归并排序算法性能分析

比较三个高级排序算法的性能,分别是归并排序,希尔排序和堆排序,这三个排序算法的时间性能分析表如下:
在这里插入图片描述
堆排序:上浮调整是O(logn) ,将元素进行交换O(n);最好情况下,没有占用额外的空间,空间复杂度O(1);
快速排序:平均时间复杂度:O(nlogn),根据基准数将数列分成两半,时间复杂地O(n);递归O(logn); 最坏情况下时间复杂度:O(n^2),即数据基本趋于有序。
归并排序:归的过程中O(logn),将数据合并O(n),所以时间复杂度O(logn
n);空间复杂度O(n);

下面的代码分别测试了三个排序算法,对800万和8000万个数据进行排序后的时间对比:可以看到快速排序是最时间最少,其次是归并排序,最后是堆排序。
在这里插入图片描述

问题:从上面的性能分析表中可以看到,这三个算法的时间复杂度都是O(logn*n),为什么时间相差很大呢?

主要原因有两个:

  1. 快排和归并排序,在遍历元素时候都是按照顺序遍历的,对CPU缓存是友好的(CPU缓存命中率高),但是堆排序是按照父子节点的关系进行数据的上浮或下沉调整,即操作数组时候,元素不是按顺序操作的,根据程序局部性原理,这样操作效率就低。
  2. 堆排序的过程中,以大根堆为例,将堆顶元素与堆末尾元素交换后,将堆顶的元素进行下沉调整,堆顶的小元素一直下沉到最下边,中间的过程做了很多无效的比较操作,影响了算法效率。

代码

#include <iostream>
#include <time.h>


using namespace std;
void MergeS(int arr[], int begin, int mid, int end, int *p)
{

	int i = begin;
	int j = mid + 1;
	int index = 0;
	// 将比较的两个小数列按序合成一个大数列
	while (i <= mid && j <= end)
	{
		if (arr[i] <= arr[j])
		{
			p[index++] = arr[i++];
		}
		else
		{
			p[index++] = arr[j++];
		}
	}

	// 如果右边的元素都放进去了,把左边的元素放入临时数组中
	while (i <= mid)
	{
		p[index++] = arr[i++];
	}
	// 同理将右边数据放入临时数组中
	while (j <= end)
	{
		p[index++] = arr[j++];
	}

	// 最后将临时空间中的数据放入原数组中
	for (i = begin, index = 0; i <= end; i++, index++)
	{
		arr[i] = p[index];
	}
	// 最后删除 临时空间 

}

void MergeSort1(int arr[], int begin, int end, int *p)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	// 先递 
	MergeSort1(arr, begin, mid, p);
	MergeSort1(arr, mid + 1, end, p);

	// 开始处理归
	// 对树中的数据进行排序并合并
	MergeS(arr, begin, mid, end, p);
}

void MergeSort1(int arr[], int size)
{
	int *p = new int[size];
	MergeSort1(arr, 0, size - 1, p);
	delete[] p;
}

// 快排分割处理函数
int Partition(int arr[], int l, int r)
{
	// 优化2:选择基准数的优化,“三数取中”,arr[l] arr[r]  arr[(l+r)/2]

	// 基类基准数
	int val = arr[l];
	// 一次快排处理 O(n)
	while (l < r)
	{
		while (l < r && arr[r] > val)  // 从右边开始往前找一个小于val的数字
		{
			r--;
		}
		if (l < r)
		{
			arr[l] = arr[r];
			l++;
		}

		while (l < r && arr[l] < val)  // 从左边开始找到大于val的数
		{
			l++;
		}
		if (l < r)
		{
			arr[r] = arr[l];
			r--;
		}
	}
	// 最后,当l == r时候,arr[r] = val
	arr[r] = val;
	return r;

}
// 时间复杂度:O(logn)   * O(n)
// 空间复杂度:递归占用的栈内存 O(logn)
// 最坏时间复杂度:n*O(n) = o(n^2)
void QuickSort(int arr[], int begin, int end)  // 1 递归的参数
{
	// 2 递归的结束条件 
	if (begin >= end)
	{
		return;
	}
	 优化1:当[begin,end]序列的元素个数小于指定数量时候,采用插入排序
	//if (end - begin <= N)  // 这个 N 根据实际的数量进行调试,确定。
	//{
	//	InsertSort1(arr, end - begin);
	//	return;
	//}
	// 3 在begin 和 end 之间做一次快排分割处理
	int pos = Partition(arr, begin, end);  // O(n)

	// 对基准数的左右序列,再分别进行快排。
	QuickSort(arr, begin, pos - 1);
	QuickSort(arr, pos + 1, end);
}

void QuickSort(int arr[], int size)
{
	QuickSort(arr, 0, size - 1);
}
// 堆的下沉调整 
void siftDown(int arr[], int i, int size)  //  i表示第i个非叶子节点
{
	int val = arr[i];		// 记录非叶子节点值
	while (i < size / 2)    // (size - 1 - 1 )/ 2 = size - 2 /2 = size/2-1
	{
		int child = 2 * i + 1;
		if (child + 1 < size && arr[child + 1] > arr[child])
		{
			child = child + 1;
		}

		if (arr[child] > val)   // 如果孩子值大于节点值
		{
			arr[i] = arr[child];
			i = child;
		}
		else
		{
			break;
		}
	}
	arr[i] = val; // 将非叶子节点放入i位置
}
// 堆排序
// 时间复杂度:O(logn) * O(n)
// 不稳定 

void HeapSort(int arr[], int size)
{
	int n = size - 1;

	// 1 从第一个非叶子节点开始进行大根堆调整
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		siftDown(arr, i, size);
	}
	// 2  把堆顶元素和末尾元素进行交换,从0号位置开始进行堆的下沉
	for (int i = n; i > 0; i--)
	{
		int tmp = arr[0];
		arr[0] = arr[i];
		arr[i] = tmp;

		siftDown(arr, 0, i);  // 第三个参数,表示参与调整个数,每次不同排序最后一个元素
	}
}


// 思想:
int main()
{
	const int COUNT = 8000000;
	int* arr = new int[COUNT];
	int* brr = new int[COUNT];
	int* crr = new int[COUNT];

	srand(time(NULL));

	for (int i = 0; i < COUNT; i++)
	{
		int val = rand() % COUNT;
		arr[i] = val;
		brr[i] = val;
		crr[i] = val;
	}

	cout << endl;

	clock_t begin, end;
	begin = clock();
	QuickSort(arr, COUNT);
	end = clock();
	cout << "QuickSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl;


	begin = clock();
	MergeSort1(brr, COUNT);
	end = clock();
	cout << "MergeSort1 spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl;

	begin = clock();
	HeapSort(crr, COUNT);
	end = clock();
	cout << "HeapSort spend:" << (end - begin) * 1.0 / CLOCKS_PER_SEC << "s" << endl;


	cout << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值