思想
每一次划分,使左边均小于基准,右边均大于,但是无序,然后再将左右边各划分一次,这样划分下去,直到分解数组大小为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++]);
}
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^dlog(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)
{
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);
swap(&ar[n], &ar[right - res / 2]);
}
else count--;
return (BFPRT(ar, left1, left1 + count, (count / 2)+1));
}
int Partition(int* ar, int left,int right, int 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)
{
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);
}
}
|