排序 | 快速排序

本文详细介绍了快速排序的原理,包括双指针划分和单向逼近的划分函数,并提供了递归和非递归两种排序实现。此外,还深入探讨了BFPRT算法,一种优化的寻找数组中第k小元素的方法,通过确保划分值位于中间位置来保证平均时间复杂度为O(N)。文章最后给出了中位数寻找和第k小元素查找的函数实现。
摘要由CSDN通过智能技术生成

思想

每一次划分,使左边均小于基准,右边均大于,但是无序,然后再将左右边各划分一次,这样划分下去,直到分解数组大小为1,划分停止,此时已经有序.

划分函数

快排中划分函数时必不可少的,他的作用是确定一个基准,然后以此基准进行划分,划分的方式有很多种,最常用的是双指针在边界处进行遍历,右指针找小于基准的,左指针找大于基准的,然后交换,直到左右指针重合,再把基准放到最终指针位置,这样基准的左边均小,右边均大了.

双指针指向最左和最右的划分函数
int Partition(int* ar, int left, int right)
{
	int tmp = ar[left];
	while (left < right)
	{
		while (right > left && ar[right] > tmp) right--;
		ar[left] = ar[right];
		while (right > left && ar[left] <= tmp) left++;
		ar[right] = ar[left];
	}
	ar[left] = tmp;
	return left;
}
单向逼近的划分函数

i和j中间的为大于基准的数,向右扫描,当找到比基准小的数,就i和j中间的数交换,此方法可以用作单链表的快速排序

int Partition1(int* ar, int left, int right)
{
	int i = left-1;
	int j = left;
	while (j <= right)
	{
		while (j<=right && ar[j] > ar[left]) j++;
		if (j > right) break;
		swap(ar[++i], ar[j++]);
		/*if (ar[j] <= ar[left])
		{
			++i;
			swap(ar[i], ar[j]);
		}
		++j;*/
	}
	swap(ar[i], ar[left]);
	return i;
}
排序函数

递归版本

void Qsort(int* ar, int left,int right)
{
	if (right > left)
	{
		int pos = Partition1(ar, left, right);
		Qsort(ar, left, pos - 1);
		Qsort(ar, pos + 1, right);
	}
}

非递归版本:
非递归的实现要利用介质来储存要进行划分的区间,可以用栈,队列,对

void Qsort(int* ar, int size)
{
	stack<int> st;
	st.push(size - 1);
	st.push(0);
	
	while (!st.empty())
	{
		int left = st.top(); st.pop();
		int right = st.top(); st.pop();
		int pos = Partition(ar, left, right);
		if (pos > left)
		{
			st.push(pos - 1);
			st.push(left);
		}
		if (right > pos)
		{
			st.push(right);
			st.push(pos + 1);
			
		}
	}
}

BFPRT算法

算法思想:
对于求数组中第k小的元素的问题,我们已经有很好的常规算法了,这个算法在最好的情况下时间复杂度是O(n),但在最坏的情况下是O(n^2)的,其实bfprt算法就是在这个基础上改进的。

常规解法:
我们随机在数组中选择一个数作为划分值(number),然后进行快排的partation过程(将小于number的数放到数组左边,等于number的数放到数组中间,大于number的数放到数组右边),然后判断k与等于number区域的相对关系,如果k正好在等于number区域,那么数组第k小的数就是number,如果k在等于number区域的左边,那么我们递归对左边再进行上述过程,如果k在等于number区域的右边,那我们递归对右边再进行上述过程。

对于常规解法,我们分析一下它的时间复杂度:

递归函数复杂度计算:

T(N)=aT(N/b)+o(N^d);
当log(b,a)>d时 复杂度为O(N^log(b,a));
当log(b,a)=d时 复杂度为O(N^d
log(2,N));
当log(b,a)<d时 复杂度为O(N^d);
N为样本数据个数 即数组中元素的个数。
N/b为将这N个数划分为更小的部分,每个部分的样本数据个数,一般为均分,那么b等于2。
a为划分为多个部分后,小部分执行的次数。
d为递归函数调用完成后剩下的操作的时间复杂度。

对于最好的情况:每次所选的number正好在数组的正中间,那么上式中a等于1,b等于2,对于partation过程,时间复杂度是O(n),所以d等于1。所以T(N)= T(N/2)+ O(N),此时 log( 2 , 1 ) < 1,故时间复杂度为O(N)。

对于最坏情况:每次所选的number正好在数组最边上,那么时间复杂度为O(N ^ 2).

bfprt算法就是在这个number上做文章,bfprt算法能够保证每次所选的number在数组的中间位置,那么时间复杂度就是O(N)。

两个重要函数:取中间值函数,寻找第k小函数
中位数的寻找又得利用寻找第k小函数来寻找
第k小函数利用中位数获取最佳划分点,相互调用的递归函数,有点难以理解

void Sort(int* ar, int left, int right)//bubble
{
	//sort
	for (int i = left, n=0 ;i < right; i++,n++)
	{
		for (int j = left; j < right-n; j++)
		{
			if (ar[j] > ar[j + 1])
			{
				swap(&ar[j], &ar[j + 1]);
			}
		}
	}
}

int GetMidIndex(int* ar, int left, int right)
{
	int left1 = left;
	int count = (right - left + 1) / 5;//记录应该循环几次
	int res = (right - left + 1) % 5;
	if (right-left+1<=5)
	{
		Sort(ar, left, right);
		return (right + left) / 2;
	}
	int n = left;

	for(int i=0;i<count;i++)
	{
		Sort(ar, left, left + 4);
		swap(&ar[n++], &ar[left + 2]);
		left += 5;
	}
	if (res > 0)
	{
		Sort(ar, right - res + 1, right);//0 1 2 3 4 5 6 7 8 9 10
		swap(&ar[n], &ar[right - res / 2]);//right - res/2
	}
	else count--;
	return (BFPRT(ar, left1, left1 + count, (count / 2)+1));
	
	
}
int Partition(int* ar, int left,int right, int i)//i是基准坐标
{

	while (right > left)
	{
		while (left < right && ar[right] > ar[i]) right--;
		while (left < right && ar[left] <= ar[i]) left++;
		swap(&ar[left], &ar[right]);
	}
	swap(&ar[left], &ar[i]);
	return left;

}
int BFPRT(int* ar,int left,int right,int k)//第k大就是第size-k小
{
	int mid_index=GetMidIndex(ar, left, right);
	int part_index=Partition(ar, left, right, mid_index);
	int num = part_index - left+1;
	if (num == k)
	{
		return part_index;
	}
	else if (num > k)
	{
		return BFPRT(ar, left, part_index-1, k);
	}
	else
	{
		return BFPRT(ar, part_index+1, right, k - num);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值