再论快排

Posted leehm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再论快排相关的知识,希望对你有一定的参考价值。

时间久了,有些遗忘,回顾记录一下:

快速排序:

   顾名思义,对于c++,快速排序历史上一直是实践中已知最快的泛型排序算法,平均运行时间:O(n*logn),最差就是O(n^2)。 

        STL中的sort用的就是快排,只不过根据数据量级穿插了别的如插入排序,堆排等算法,进行动态调整,使性能达到最优解。一般来说,对于小数据量n<=20插入排序会更优,减少了快排递归的损耗。

        快速排序是一种分治的递归算法。简单来讲,就是先从需要排序的序列中选定一个中心点pivot,然后进行n轮的比较交换,每一轮下来,将序列调整为左右两个部分,小于pivot放在一边,大于pivot的在另外一边。依次类推,再分别按照相同的思路处理左右两部分,直至无法分割为止。

        最坏的情况,就是倒序,pivot选择的是第一个,每次分治其实都是分了一个完整的和一个空的,因此相当于没有分,最差O(n^2)。

关于pivot选择:

  根据快排思想,pivot的选择在一定程度会决定了算法实现的优劣,如何避免或者说降低最坏的情况的出现呢?

        1.  第一元素作为pivot

                 常见的大部分介绍demo都是选择第一元素,这种对于预排序或者倒序的,很容易出现最坏情况,这绝对是个可怕的坏主意,应该立即放弃这种想法

        2.  随机选择pivot

    一般来说这种策略非常安全,除非随机数故障。另外:随机数生成开销显著,根本减少不了算法其余部分的平均运行时间

        3. 三数中值法

    一般做法就是,左边,中间,右边,取中间大的那个。这样避免了预排序或者倒序的情况,直接避免了最坏情况,而且能减少很多的比较次数。  

 

代码实现:

       1.  双指针循环

       2.  单指针循环

 

//以下3和4暂时没有代码记录,仅作思路参考。

       3.   其实这里12都是在数组内部交换排序,如果不在意空间复杂度,更为简便的就是多开几个数组,小于的放入一个,等于的放入一个,大于的放入一个,依次来递归同样可以达到效果,只是无法体现整个算法的交换和排序过程。

       4.  非递归的方式:所有递归的方式最终转换非递归都是使用栈或者队列,类似二叉树层序遍历一样,此处同样,分割后的两边的起始与结束可以入栈出栈,直到栈为空来实现。

 

 

//中位数选择
int median3(std::vector<int>&vecInt, int start, int end)
{
	int center = (start + end) / 2;
	//end取最大的
	if (vecInt[end] < vecInt[start])
		std::swap(vecInt[end], vecInt[start]);
	if (vecInt[end] < vecInt[center])
		std::swap(vecInt[end], vecInt[center]);
	//start取大的那个。
	if (vecInt[start] < vecInt[center])
		std::swap(vecInt[start], vecInt[center]);

	return start;
}


//双指针循环,最后返回分割点
int partion1(std::vector<int>&vecInt, int start, int end)
{
	median3(vecInt,start,end);
	int pivot = vecInt[start];
	int i = start;
	int j = end;
	while (i < j)
	{
		//注意顺序, 必须先从右边开始查找,
		//i标记小从左到右,最终停留在大值上,j标记大的,最终停留在小的值上面。
		//最后循环完成,必然是ij重叠,也就是分界处,此时的值要与原始的pivot交换,因此此值必须要小于pivot
		//因此,必须先从j先走,停留在小于的值上面等i。
		//否则,如果先从i开始,最终停留在大于pivot上等j,交换后以ij分界,左边就有值大于pivot,逻辑错误。
		while (vecInt[j] > pivot && i < j) { j--; }
		while (vecInt[i] <= pivot && i < j) { i++; }
		if (i < j)
		{
			std::swap(vecInt[i],vecInt[j]);
		}
	}
	//将pivot放入分界线处i=j
	//std::swap(pivot,vecInt[i]);
	//i=j时候,pivot的start位置与分界处i交换,否则会覆盖数据
	std::swap(vecInt[start], vecInt[i]);

	return i;
}

//单指针循环 最后返回分割点
int partion2(std::vector<int>&vecInt, int start, int end)
{
	median3(vecInt, start, end);
	//pivot标记值,mark标记分界线
	int pivot = vecInt[start];
	int mark = start;

	//从mark后开始比较,遇见小于等于pivot的值,扩充mark范围,往mark位置放。
	//最后完成后mark指向分界处,左边小于等于pivot,右边大于
	for (int i = start + 1; i <= end; i++)
	{
		if (vecInt[i] <= pivot)
		{
			mark++;
			std::swap(vecInt[i], vecInt[mark]);
		}
	}
	//最后交换start位置到pivot交换到mark位置
	std::swap(vecInt[start], vecInt[mark]);

	return mark;
}

//快速排序实现,分治思想
int qSort(vector<int> &vecInt, int start, int end)
{
	if (start >= end)
		return 0;

	int q = partion1(vecInt,start,end);
	//递归调用,i的位置为pivot
	qSort(vecInt, start, q-1);
	qSort(vecInt, q+1,  end);

	return 0;
}


//快速排序对外接口
int Solution::quickSort(vector<int> &vecInt)
{
	if (vecInt.size() <= 1)
		return 0;
	//
	int endIndex = vecInt.size() - 1;
	qSort(vecInt, 0, endIndex);

	return 0;
}

  

Demo:

void testQuickSort()
{
	std::vector<int> vecInt{ 4,4,2,1,6,5,4,9};
	std::cout << "vecInt: [ ";
	for (int i = 0; i < vecInt.size(); i++)
	{
		std::cout << vecInt[i] << " ,";
	}
	std::cout << " ]" << std::endl;
	Solution sl;

	std::cout << "after quickSort" << std::endl;
	sl.quickSort(vecInt);
	std::cout << "vecInt: [ ";
	for (int i = 0; i < vecInt.size(); i++)
	{
		std::cout << vecInt[i] << ",";
	}
	std::cout << " ]" << std::endl;

}

结果:

技术图片

 

以上是关于再论快排的主要内容,如果未能解决你的问题,请参考以下文章

再论智能指针(上)

详谈再论JAVA获取本机IP地址

如果就以平时项目经验来面试,通过估计很难,再论面试前的准备

如果就以平时项目经验来面试,通过估计很难,再论面试前的准备

再论智能指针(下)

再论 ORM