无序数组中位数

面试被问到这个问题然后一时没想到什么好方法……

收集了一下网上的做法:排序、堆、快排。

排序

排序自然就不用说了,nlogn的算法随便选一个,然后取中间位置的值。

堆的做法不是简单的堆排序,它先将前一半(len+2>>1)的元素进行堆排序(小根堆),然后后一半的元素逐个和堆顶比较,如果更小则舍弃,如果较大则替换为堆顶并调整堆。这样可以保证堆里剩下的是数组中的较大值。对于奇数长度的数组来说,直接取堆顶即可;对于偶数长度的数组,取堆顶和它较小的子节点的平均值。

快排

快速排序的思想一直没记住,正好借这个机会记录一下。而且这好像是一种比较好写的时间复杂度较低的方法(平均O(n))。

快速排序的思想是,每次确定一个元素的位置,并且将数组在这个元素的位置分割成两部分,一侧全大于等于该元素,另一侧全小于等于该元素。

具体的方法是,在当前数组中随机取一个数p,然后将它交换到第0个元素,然后用首尾两个指针遍历数组。尾部的指针先走,直到遇到第一个不大于p的值。然后首部的指针再走,直到遇到第一个不小于p的值。此时,将两个指针指向的数交换。上述过程一直进行到两个指针相遇。这时将指针指向的数和第0个元素交换,然后以这个位置为分界点,分出两个子数组,对子数组进行快速排序。

上述过程强调了尾部指针先走,为什么不能首部指针先走?

考虑指针相遇的条件,要么是完成一次交换后,先行的指针找不到满足要求的数,直到和另一个指针会合,要么是先行的指针找到满足条件的数之后,后行的指针找不到满足要求的数,和它会合。对于先行指针是尾部指针的情况而言,两种情况都会找到小于p的数。因此,就可以把p和它交换以使前半部分全部小于p而后半部分全部大于p。

如果首部指针先走,那么就会导致指针相遇时找到的数是大于p的数,这样就不能使结果满足快排的要求。

当然,如果是选择将p和结尾的数交换,那么就要让首部指针先走。总之就是把p放在哪边,另一边的指针就先走。

以上就是朴素的快速排序的方法了。贴个代码,用洛谷1177排序模板的数据测的。看运行时间后两个测试点应该是卡了重复数据。对重复数据做了一些处理,没有其它的优化。

void quick_sort(vector<int>& arr, int l, int r)
{
    
    
	if (l >= r)
		return;
	int p = rand() % (r - l + 1) + l;
	if (r != p)
		swap(arr[p], arr[r]);
	int ls = l;
	int rs = r;
	rs = rs;
	while (ls != rs)
	{
    
    
		while (ls != rs && arr[ls] <= arr[r])
			++ls;
		while (ls != rs && arr[rs] >= arr[r])
			--rs;
		if (ls != rs)
			swap(arr[ls], arr[rs]);
	}
	swap(arr[r], arr[ls]);
    int temp=arr[ls];
    while(ls>l&&arr[ls]==temp)--ls;
    while(rs<r&&arr[rs]==temp)++rs;
	quick_sort(arr, l, ls);
	quick_sort(arr, rs, r);
}

回到中位数的问题。快速排序每一步都能确定一个数出现的位置,那么当确定一个数的是在数组的一半位置时,就说明它是中位数。相对于快速排序而言,找中位数不需要对两个子数组都进行处理,只需要处理其中一个子数组即可。

在平均情况下,每次进行上述操作,数组的长度缩小一半,这样来说就需要logn次的递归,每次便利的长度减半,求和的结果就是2n,所以平均复杂度是O(n)。但它的时间消耗不稳定,最差情况和快排一样是平方级别。

猜你喜欢

转载自blog.csdn.net/m0_49792815/article/details/129529480