【求助】关于堆排序、归并排序、快速排序的比较,到底谁快

cuijiyue 2013-08-19 04:44:09
刚开始学排序,有个深深的疑惑
看书上说,
堆排序 归并排序 快速排序
最坏时间 O(nlogn) O(nlogn) O(n^2)
最好时间 O(nlogn) O(nlogn) O(nlogn)
平均时间 O(nlogn) O(nlogn) O(nlogn)

辅助空间 O(1) O(n) O(logn)~O(n)

从数据看明显堆排序最好啊,可是为什么都说快速排序比较好,求详细解释

有人说代码实现后,数据量足够大的时候,快速排序的时间确实是比堆排序短,等会我写完代码也实验下。

搜到解释是,对于数组,快速排序每下一次寻址都是紧挨当前地址的,而堆排序的下一次寻址和当前地址的距离比较长。
...全文
14596 17 打赏 收藏 转发到动态 举报
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
方品 2015-03-15
  • 打赏
  • 举报
回复
stl中的快速排序有很多优化,比如:随机算法,三数取中等几乎能够避免最坏情况发生。
cnmhx 2015-03-14
  • 打赏
  • 举报
回复
同学们,这些算法都有标准的开源程序。 自己写程序对理解算法的确有好处。 但是如果用于得到一般性的比较结果,那是靠不住的。
cokexk 2015-03-10
  • 打赏
  • 举报
回复
快排比归并排序快的原因是快排对数组的访问次数要少于归并排序~
showjim 2013-09-30
  • 打赏
  • 举报
回复
引用 12 楼 liangbch 的回复:
Mark一下, 有时间好好做一些实验,给出具体结果。 这里先说说的我的看法,希望实验能支持我的预测。 我认为, 1. 快排的时间复杂度是不稳定的,在最快情况下比归并排序慢的多。 2. 当数据量大时,充分优化的归并排序可比快速排序更快。其原因有 1). 归并排序对内存的访问是严格的顺序方式(3个2个源数组,1个目标数组,都是顺序放分),故cache的命中率比快排更高,从这点上,相同的内存读写操作,归并优于快排,当数组占用的空间大大超过cache的大小,则这一优势将更加明显。 2)普通写法的归并排序有2个缺点,如果改进,则可以提速。如果你的实验是基于最普通的版本,得到的结果是快排优于归并,而优化的归并排序的版本,其性能则可能反超快排。 2.1) 归并排序不是In place.需要将结果存储到临时缓冲区,然后在复制回来,这个过程可以优化掉。使用乒乓做法,在第i级归并过程,从buff1 归并到buff2,在i+1级归并过程,从buff2复制到buff1。 2.2) 2路归并排序的核心动作是比较2个对列的头元素那个更大,其比较结果是随机的,2个分支机会均等,CPU的分支预测算法不起作用,当预测失败,可大大降低程序性能,如果消除这个分支,可明显提高程序性能。
1.快排的时间复杂度确实不稳定,极端情况是O(n^2),但是平摊下来是T(n*lg(n)),而归并是严格的O(n*log(n))。 2.快速排序比归并排序快。其原因有 1)快排对内存的访问是顺序方式(包括逆序),只有两个目标而且是同一个数组,故cache的命中率不会比归并低。特别是数组空间接近于cache大小时,这一优势将更加明显。 2)快排的内存写操作次数平摊下来是T(n*lg(n)/2),而归并的内存写操作次数是严格的O(n*log(n)),由于内存写操作开销比较大,所以对于随机数据快排优于归并。
DeDeWo 2013-09-30
  • 打赏
  • 举报
回复 1
楼主知道标准库的sort是怎么实现的么? 先快排,递归深度超过一个阀值就改成堆排,然后对最后的几个进行插入排序。
liangbch 2013-09-03
  • 打赏
  • 举报
回复
Mark一下, 有时间好好做一些实验,给出具体结果。 这里先说说的我的看法,希望实验能支持我的预测。 我认为, 1. 快排的时间复杂度是不稳定的,在最快情况下比归并排序慢的多。 2. 当数据量大时,充分优化的归并排序可比快速排序更快。其原因有 1). 归并排序对内存的访问是严格的顺序方式(3个2个源数组,1个目标数组,都是顺序放分),故cache的命中率比快排更高,从这点上,相同的内存读写操作,归并优于快排,当数组占用的空间大大超过cache的大小,则这一优势将更加明显。 2)普通写法的归并排序有2个缺点,如果改进,则可以提速。如果你的实验是基于最普通的版本,得到的结果是快排优于归并,而优化的归并排序的版本,其性能则可能反超快排。 2.1) 归并排序不是In place.需要将结果存储到临时缓冲区,然后在复制回来,这个过程可以优化掉。使用乒乓做法,在第i级归并过程,从buff1 归并到buff2,在i+1级归并过程,从buff2复制到buff1。 2.2) 2路归并排序的核心动作是比较2个对列的头元素那个更大,其比较结果是随机的,2个分支机会均等,CPU的分支预测算法不起作用,当预测失败,可大大降低程序性能,如果消除这个分支,可明显提高程序性能。
net_assassin 2013-08-26
  • 打赏
  • 举报
回复
具体问题具体分析,快速排序当数据量越大的时候越能体现出它的优势。
大尾巴猫 2013-08-26
  • 打赏
  • 举报
回复
引用 10 楼 SmallYamateh 的回复:
堆排序为什么比希尔排序慢呢,难道你写错了?logN复杂度的“优先队列的pop”,一共执行N次,怎么着也比希尔排序的O(N^(5/4))要快啊。
我自己按照堆排序的算法思想实现了堆排序,写完后对照网上的代码,代码几乎是差不多的。 至于为何堆排序效率比希尔排序慢很多,我在上面帖子已经说了,是堆排序的算法缺陷造成无用功太多。(我把堆排序看成是高级排序中的冒泡排序,冒泡也是两两交换做了太多无用功所以最慢。) 贴下我实现的希尔排序和堆排序,你可以自己去运行比较。
template <typename T>
void ShellSort(T arr[], int n)          //希尔排序就是增量逐步缩小的直接插入排序
{                                       //这里采用黄金分割的比例逐次减小增量
	int gap = n * 0.382 + 0.5;          //所以代码和直接插入排序非常像。区别就是把增量1换成gap
	int i, j;
	T temp;
	while (gap > 0)
	{
		for (i = gap; i < n; i++)
		{
			temp = arr[i];
			for (j = i - gap; j >= 0; j -= gap)
			{
				if (temp < arr[j])
					arr[j + gap] = arr[j];
				else
					break;
			}
			arr[j + gap] = temp;
		}
		gap = gap * 0.382 + 0.5;
	}
}
template <typename T>
void HeapSort(T arr[], int n)
{
	for (int i = n / 2 - 1; i >= 0; i--)    //建立最大堆,从最后一个非叶节点开始
		MaxHeapify(arr, n, i);              //对于N规模的堆(0,n-1), n/2-1是最后一个非叶节点

	T temp;
	for (int i = n - 1; i > 0; i--)   
	{
		temp = arr[i];               //每次取堆顶数据和堆底部数据交换
		arr[i] = arr[0];             //并逐步缩小堆的大小
		arr[0] = temp;
		MaxHeapify(arr, i, 0);       //重新筛选堆
	}
}


template <typename T>
void MaxHeapify(T arr[], int n, int i)
{
	T temp, max_data;
	int pos = i;
	int left = pos * 2 + 1;
	int right = left + 1;
	int max_pos;
	temp = arr[pos];

	while (left < n)     //至少有左子树
	{
		//取左右子树的最大值
		max_data = arr[left];
		max_pos = left;
		if (right < n)   //左右子树均有
			if (arr[right] > arr[left])
			{
				max_data = arr[right];
				max_pos = right;
			}

		if (temp < max_data)         //节点比左右子树最大值小,继续向下调整
		{
			arr[pos] = max_data;
			pos = max_pos;
			left = pos * 2 + 1;
			right = left + 1;
		}
		else
			break;
	}
	arr[pos] = temp;   //回填
}
我觉得我写的堆排序中规中矩,没啥大问题啊。难道堆排序还有更强悍的实现方法?
kosora曹 2013-08-26
  • 打赏
  • 举报
回复
引用 1 楼 ananluowei 的回复:
4种非平方级的排序: 希尔排序,堆排序,归并排序,快速排序 我测试的平均排序时间:数据是随机整数,时间单位是秒 数据规模 快速排序 归并排序 希尔排序 堆排序 1000万 0.75 1.22 1.77 3.57 5000万 3.78 6.29 9.48 26.54 1亿 7.65 13.06 18.79 61.31 堆排序是最差的。 这是算法硬伤,没办法的。因为每次取一个最大值和堆底部的数据(记为X)交换,重新筛选堆,把堆顶的X调整到位,有很大可能是依旧调整到堆的底部(堆的底部X显然是比较小的数,才会在底部),然后再次和堆顶最大值交换,再调整下来。 从上面看出,堆排序做了许多无用功。 至于快速排序为啥比归并排序快,我说不清楚。
堆排序为什么比希尔排序慢呢,难道你写错了?logN复杂度的“优先队列的pop”,一共执行N次,怎么着也比希尔排序的O(N^(5/4))要快啊。
cuijiyue 2013-08-22
  • 打赏
  • 举报
回复
引用 7 楼 zerglarva_aahha 的回复:
谁说的快排好啊?我一般都用堆的,我认为堆好。 这个哪有谱啊,考察使用环境啊。不是只有时间代价一种衡量标准的,还有空间代价,LZ也有过栈溢出的经历,那不就是空间代价过高了嘛。改成别的办法还是避免不了空间代价。LZ自己不是也已经把空间待见列出来了嘛。
我是看讲解快速排序的时候说,快速排序之所以叫快速排序,就是因为它最快,才叫快速。然后后面总结的时候,列了张表,就是我贴出来的,然后我就深深的疑问了
食人族哲学家 2013-08-22
  • 打赏
  • 举报
回复
谁说的快排好啊?我一般都用堆的,我认为堆好。 这个哪有谱啊,考察使用环境啊。不是只有时间代价一种衡量标准的,还有空间代价,LZ也有过栈溢出的经历,那不就是空间代价过高了嘛。改成别的办法还是避免不了空间代价。LZ自己不是也已经把空间待见列出来了嘛。
cuijiyue 2013-08-20
  • 打赏
  • 举报
回复
引用 4 楼 ananluowei 的回复:
[quote=引用 3 楼 cuijiyue 的回复:]
哦,我这个快速排序小段区间用了插入排序优化,就算不用,也太会溢出啊,除非碰到特别设计的针对算法的数据序列。
template <typename T>
void QuickSort(T arr[], int n)   //快速排序
{
	QSpartition(arr, 0, n - 1);
	InsertSort(arr, n);          //最后用插入排序对整个数组排序
}

template <typename T>
void QSpartition(T arr[], int low, int high)     //快速排序递归函数
{
    
//	if (low >= high)     //有插入排序,不需要这句
//		continue;
	
	const int min_insert = 128;
	if (high - low < min_insert)  //数据小于一定数量的时候用直接插入排序
	{
		return;  //最后用插入排序对整个数组排序
	}

	int i, j;
	T temp;
	
    //下面一段是前中后3个数据进行插入排序
	i = (low + high) / 2;
	if (arr[i] < arr[low])
	{
		temp = arr[i];
		arr[i] = arr[low];
		arr[low] = temp;
	}
	if (arr[high] < arr[i])
	{
		temp = arr[high];
		arr[high] = arr[i];
		arr[i] = temp;
		if (arr[i] < arr[low])
		{
			temp = arr[i];
			arr[i] = arr[low];
			arr[low] = temp;
		}
	}
//	if (high - low < 3)   //小区间用插入排序,这里就不需要判断了
//		return;           //不用插入排序,只有2个数据的时候排序错误
	
	temp = arr[i];           
	arr[i] = arr[low + 1];  //中值放在最左边
	i = low + 1;            //左右边界缩小1
	j = high - 1;           //边界2个数字插入排序排过,满足左<=中<=右

	while (i < j)
	{
		while (temp < arr[j] && i < j)   //从右往左扫描小于目标的值,应该放在左半部分
			j--;
		if (i < j)                        //找到后放在左边
			arr[i++] = arr[j];
		else
			break;
		while (temp > arr[i] && i < j)   //从左往右扫描大于目标的值,要放在右边
			i++;
		if (i< j)
			arr[j--] = arr[i];
		else
			break;
	}
	arr[i] = temp;                       // i = j,正好是分界线,回填目标值
	if ((i - low) > 1) 
		QSpartition(arr, low, i - 1);     //递归左边
	if ((high - i) > 1)
		QSpartition(arr, i + 1, high);     //递归右边
}

template <typename T>
void InsertSort(T arr[], int n)   //直接插入排序
{
	int i, j;
	T temp;

	for (i = 1; i < n; i++)
	{
		temp = arr[i];
		for (j = i - 1; j >= 0; j--)
		{
			if (temp < arr[j])            //逐个往前比较,碰到大于目标的,拉过来
				arr[j + 1] = arr[j];
			else
				break;
		}
		arr[j + 1] = temp;               //把目标值填入空位
	}
}
[/quote] 多谢了,看你代码里主要有两个优化,我看的书是《大话数据结构》,那个比较低位、高位、中间位的优化叫选取枢轴,添上后代码确实不会栈溢出了, 加入插入排序的优化后,时间会比没有加之前缩短一倍。
hlyces 2013-08-20
  • 打赏
  • 举报
回复
没事别递归,栈溢出
大尾巴猫 2013-08-19
  • 打赏
  • 举报
回复
引用 3 楼 cuijiyue 的回复:
哦,我这个快速排序小段区间用了插入排序优化,就算不用,也太会溢出啊,除非碰到特别设计的针对算法的数据序列。
template <typename T>
void QuickSort(T arr[], int n)   //快速排序
{
	QSpartition(arr, 0, n - 1);
	InsertSort(arr, n);          //最后用插入排序对整个数组排序
}

template <typename T>
void QSpartition(T arr[], int low, int high)     //快速排序递归函数
{
    
//	if (low >= high)     //有插入排序,不需要这句
//		continue;
	
	const int min_insert = 128;
	if (high - low < min_insert)  //数据小于一定数量的时候用直接插入排序
	{
		return;  //最后用插入排序对整个数组排序
	}

	int i, j;
	T temp;
	
    //下面一段是前中后3个数据进行插入排序
	i = (low + high) / 2;
	if (arr[i] < arr[low])
	{
		temp = arr[i];
		arr[i] = arr[low];
		arr[low] = temp;
	}
	if (arr[high] < arr[i])
	{
		temp = arr[high];
		arr[high] = arr[i];
		arr[i] = temp;
		if (arr[i] < arr[low])
		{
			temp = arr[i];
			arr[i] = arr[low];
			arr[low] = temp;
		}
	}
//	if (high - low < 3)   //小区间用插入排序,这里就不需要判断了
//		return;           //不用插入排序,只有2个数据的时候排序错误
	
	temp = arr[i];           
	arr[i] = arr[low + 1];  //中值放在最左边
	i = low + 1;            //左右边界缩小1
	j = high - 1;           //边界2个数字插入排序排过,满足左<=中<=右

	while (i < j)
	{
		while (temp < arr[j] && i < j)   //从右往左扫描小于目标的值,应该放在左半部分
			j--;
		if (i < j)                        //找到后放在左边
			arr[i++] = arr[j];
		else
			break;
		while (temp > arr[i] && i < j)   //从左往右扫描大于目标的值,要放在右边
			i++;
		if (i< j)
			arr[j--] = arr[i];
		else
			break;
	}
	arr[i] = temp;                       // i = j,正好是分界线,回填目标值
	if ((i - low) > 1) 
		QSpartition(arr, low, i - 1);     //递归左边
	if ((high - i) > 1)
		QSpartition(arr, i + 1, high);     //递归右边
}

template <typename T>
void InsertSort(T arr[], int n)   //直接插入排序
{
	int i, j;
	T temp;

	for (i = 1; i < n; i++)
	{
		temp = arr[i];
		for (j = i - 1; j >= 0; j--)
		{
			if (temp < arr[j])            //逐个往前比较,碰到大于目标的,拉过来
				arr[j + 1] = arr[j];
			else
				break;
		}
		arr[j + 1] = temp;               //把目标值填入空位
	}
}
cuijiyue 2013-08-19
  • 打赏
  • 举报
回复
引用 1 楼 ananluowei 的回复:
4种非平方级的排序: 希尔排序,堆排序,归并排序,快速排序 我测试的平均排序时间:数据是随机整数,时间单位是秒 数据规模 快速排序 归并排序 希尔排序 堆排序 1000万 0.75 1.22 1.77 3.57 5000万 3.78 6.29 9.48 26.54 1亿 7.65 13.06 18.79 61.31 堆排序是最差的。 这是算法硬伤,没办法的。因为每次取一个最大值和堆底部的数据(记为X)交换,重新筛选堆,把堆顶的X调整到位,有很大可能是依旧调整到堆的底部(堆的底部X显然是比较小的数,才会在底部),然后再次和堆顶最大值交换,再调整下来。 从上面看出,堆排序做了许多无用功。 至于快速排序为啥比归并排序快,我说不清楚。
请问你的快快速排序是怎么写的,我写的快速排序,当测试数组大于5000的时候就栈溢出了。 其他的几个排序都对着,不过他们呢没有用栈。 这是快速排序的代码,win7 32位,vs2010.
int FindPos(double *p,int low,int high)
{
	double val = p[low];
	while (low<high)
	{
		while(low<high&&p[high]>=val)
			high--;
		p[low]=p[high];
		while(low<high&&p[low]<val)
			low++;
		p[high]=p[low];
	}
	p[low]=val;
	return low;
}
void QuickSort(double *a,int low,int high)
{
	if (!a||high<low)
		return;

	if (low<high)
	{
		int pos=FindPos(a,low,high);
		QuickSort(a,low,pos-1);
		QuickSort(a,pos+1,high);
	}
}
terablade 2013-08-19
  • 打赏
  • 举报
回复
算法复杂度一样只是说明随着数据量的增加,算法时间代价增长的趋势相同,并不是执行的时间就一样,这里面有很多常量参数的差别,即使是同样的算法,不同的人写的代码,不同的应用场景下执行时间也可能差别很大。 快排的最坏时间虽然复杂度高,但是在统计意义上,这种数据出现的概率极小,而堆排序过程里的交换跟快排过程里的交换虽然都是常量时间,但是常量时间差很多。
大尾巴猫 2013-08-19
  • 打赏
  • 举报
回复
4种非平方级的排序: 希尔排序,堆排序,归并排序,快速排序 我测试的平均排序时间:数据是随机整数,时间单位是秒 数据规模 快速排序 归并排序 希尔排序 堆排序 1000万 0.75 1.22 1.77 3.57 5000万 3.78 6.29 9.48 26.54 1亿 7.65 13.06 18.79 61.31 堆排序是最差的。 这是算法硬伤,没办法的。因为每次取一个最大值和堆底部的数据(记为X)交换,重新筛选堆,把堆顶的X调整到位,有很大可能是依旧调整到堆的底部(堆的底部X显然是比较小的数,才会在底部),然后再次和堆顶最大值交换,再调整下来。 从上面看出,堆排序做了许多无用功。 至于快速排序为啥比归并排序快,我说不清楚。

33,010

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧