思考题7-1
a) 过程如下,其中红色表示
j
的下标,绿色表示
b) 在第一次检查
i<j
时有
i=p,j≥p
(因为
A[p]=x
)。若
i=j
,算法将终止并且没有访问越界。若
i<j
,肯定可以交换一次或者已经交换了一次,交换后的值就相当于哨兵的作用。由于
i≤r,j≥p
,因此下一次循环也不会越界。而当某一个下标到达数组端点时,
i
就再也不会小于或等于
c) 若
A[p]
是最小值,则
j=p
,否则肯定会交换
A[i]
和
A[j]
值(至少一次),然后
j
的值会一直减小,因此有
d) 每次交换时都会将小于等于主元的数放在下标
i
的位置,大于等于主元的放在下标
e) 代码如下:
int HOARE_PARTITION(int *array,int p,int r)
{
int pivot = *(array + p);
int i = p - 1, j = r + 1;
while(1)
{
do{ --j;} while (*(array + j) > pivot);
do{ ++i;} while (*(array + i) < pivot);
if(i < j)
{
int temp = *(array + i);
*(array + i) = *(array + j);
*(array + j) = temp;
}
else return j;
}
}
void QUICKSORT(int *array,int p,int r)
{
if(p < r)
{
int q = HOARE_PARTITION(array,p,r);
QUICKSORT(array,p,q);
QUICKSORT(array,q+1,r);
}
}
思考题7-2
a) 显然划分会不平衡,导致 T(n)=T(n−1)+Θ(n) ,最后是 Θ(n2) 。
b) 很简单的一种方法就是从原PARTITION
返回的
q
值向两边扩散,找到不等于
PARTITION’(A, p, r)
i = j = PARTITION(A, p, r)
while i > 1 and A[i - 1] = A[i]
i = i - 1
while j < r and A[j + 1] = A[j]
j = j + 1
return i, j
c)
RANDOMIZE-QUICKSORT'(A, p, r)
q, t = PARTITION’(A, p, r)
if p < q
RANDOMIZE-QUICKSORT'(A,p,q)
if t < r
RANDOMIZE-QUICKSORT'(A,t,r)
d) 不需要改变很多。7.4.2节中为了决定两个元素什么时候不会比较而将它们假定为不同,对于任意
Zij
,只有当
zi
或
zj
被选为主元,两个元素才会比较。而当两个元素相同的时候,这在PARTITION'
中是不成立的。 注意这只是增加了比较次数,但是只是常数变化,对分析没有变化。
#include <iostream>
#include <cstdlib>
using std::cout;
using std::endl;
struct piovt
{
int low;
int high;
};
int PARTITION(int *array, int p, int r)
{
int key = *(array + r);
int i = p - 1;
for(int j = p; j < r; ++j)
{
if(*(array + j) <= key)
{
++i;
int temp = *(array + i);
*(array + i) = *(array + j);
*(array + j) = temp;
}
}
int temp = *(array + i + 1);
*(array + i + 1) = *(array + r);
*(array + r) = temp;
return i + 1;
}
int RANDOMIZED_PARTITION(int *array, int p, int r)
{
int i = rand() % (r - p + 1) + p;
if(i != r)
{
int temp = *(array + i);
*(array + i) = *(array + r);
*(array + r) = temp;
}
return PARTITION(array,p,r);
}
piovt RANDOMIZED_PARTITION_PIVOT(int *array, int p, int r)
{
piovt ret;
ret.low = ret.high = RANDOMIZED_PARTITION(array,p,r);
int key = *(array + ret.low);
while(ret.low >= p && *(array + ret.low) == key)
--ret.low;
while(ret.high <= r && *(array + ret.high) == key)
++ret.high;
return ret;
}
void RANDOMIZED_QUICKSORT_PIVOT(int *array, int p, int r)
{
piovt ret = RANDOMIZED_PARTITION_PIVOT(array,p,r);
if(p < ret.low)
RANDOMIZED_QUICKSORT_PIVOT(array,p,ret.low);
if(ret.high < r)
RANDOMIZED_QUICKSORT_PIVOT(array,ret.high,r);
}
int main()
{
int ia[] = {13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
RANDOMIZED_QUICKSORT_PIVOT(ia,0,15);
for(int i = 0; i < 16; ++i)
cout << ia[i] << ' ';
cout << endl;
return 0;
}
思考题7-3
a)
n
个元素选一个做主元,概率
b) 设第
q
小的元素被选为主元,一共有 PARTITION
。就得到这个公式。
c) 根据b)有:
d)
e) 猜测
E[T(n)]≤anlgn
:
思考题7-4
a) 原始版本中,得到
q
之后在 QUICKSORT
,在尾递归中,只是没有进行第二次递归调用,对左边子数组还是有递归调用。现在只看伪代码的第五行
p=q+1
,它改变
p
然后重新进入循环,实际上有调用TAIL-RECURSIVE-QUICKSORT(A, q+1,r)
,因而总体一样。
b) 当数组排序好的时候,每次PARTITION
返回 TAIL-RECURSIVE-QUICKSORT
,栈深度为
c) 修改算法,让它只对分割的较长一段进行尾递归,这样就可以减少栈深度。
TAIL-RECURSIVE-QUICKSORT'(A, p, r)
while p < r
q = PARTITION(A, p, r)
if q - p < r - q
TAIL-RECURSIVE-QUICKSORT'(A, p, q - 1)
p = q + 1
else TAIL-RECURSIVE-QUICKSORT'(A, q + 1, r)
r = q - 1
附上两种尾递归快排的代码(部分函数请参考思考题7-2):
void TAIL_RECURSIVE_QUICKSORT(int *array,int p, int r)
{
while(p < r)
{
int q = RANDOMIZED_PARTITION(array,p,r);
TAIL_RECURSIVE_QUICKSORT(array,p,q-1);
p = q + 1;
}
}
void TAIL_RECURSIVE_QUICKSORT_MODIFIED(int *array,int p, int r)
{
while(p < r)
{
int q = RANDOMIZED_PARTITION(array,p,r);
if(q - p < r - q)
{
TAIL_RECURSIVE_QUICKSORT_MODIFIED(array,p,q-1);
p = q + 1;
}
else
{
TAIL_RECURSIVE_QUICKSORT_MODIFIED(array,q+1,r);
r = q - 1;
}
}
}
思考题7-5
a) 取三个数排列一共有
n!(n−3)!
种。为了得到第
i
号元素,需要取比
b) 平凡实现选择主元的概率为
1n
。所以有:
即增加了 32 。
c) 从练习7.2-6知平凡实现得到一个好概率为
1−2(1/3)=1/3
。对于三数取中:
增加了 1327÷13=3927 。
d) 就算是三数取中,也需要将数组分成两半,只是提高了不平衡的概率,这只影响常数因子。
思考题7-6
首先要注意,如果两个区间重叠,我们不需要将它们排序,我们将重叠的区间视为相等。
a) 类似于思考题7-2,我们随机选择一个主元(这里的主元是一个区间),然后检查这个区间是否和其他区间有重叠,找出它们的共同区间,将所有的区间都和这个共同区间进行比较,根据比较结果排序。接下来讨论主要的PARTITION
部分。它很像正常快排的PARTITION
,从第一个区间开始,若找到比共同区间小的则放在共同区间左边(这里的小是指某个区间右端点小于共同区间的左端点),若第
i
个区间包含共同区间,视为相等,放入到相等区间,剩余的则是未知的。一直比较到最末,完成一次PARTITION
并返回重叠区间的下标。
#include <iostream>
#include <cstdlib>
using std::cout;
using std::endl;
struct interval
{
int left;
int right;
};
void swap(interval &a,interval &b) //使用引用交换两个值
{
interval temp = a;
a = b;
b = temp;
}
interval PARTITION(interval *array, int p, int r)
{
int pivot = rand() % (r - p + 1) + p;
if(pivot != r)
swap(array[r],array[pivot]);
interval common = {array[r].left,array[r].right}; //共同区间
for(int i = p; i < r; ++i) //检测重叠部分
{
//有重叠,求出共同区间,即区间交集
if(array[i].left < common.right && array[i].right > common.left)
{
if(array[i].left > common.left)
common.left = array[i].left;
if(array[i].right < common.right)
common.right = array[i].right;
}
}
int j = p - 1, k = p - 1;
for(int i = p; i < r; ++i)
{
//共同区间是和主元区间重叠的所有区间的交集,因此只要是和主元区间重叠的区间都满足下面的if条件
if(array[i].left <= common.left && array[i].right >= common.right)
swap(array[i],array[++k]); //重叠区间放在一起
else if(array[i].right < common.left)
{
swap(array[i],array[++k]);
swap(array[++j],array[k]); //小于共同区间的放在所有重叠区域左边
}
}
swap(array[j+1],array[r]);
interval ret = {j+1,k};
return ret;
}
void FUZZY_SORT(interval *array,int p, int r)
{
if(p < r)
{
interval q = PARTITION(array,p,r);
FUZZY_SORT(array,p,q.left-1);
FUZZY_SORT(array,q.right+1,r);
}
}
int main()
{
interval ia[] = {{1,5},{6,7},{5,7},{3,7},{9,11},{7,9},{9,15},{1,9},{6,10},{13,14},{11,15},{13,14},{12,14},{14,15},{15,15}};
FUZZY_SORT(ia,0,14);
for(int i = 0; i < 15; ++i)
cout << ia[i].left << ' ' << ia[i].right << endl;
return 0;
}
b) 如果所有区间是重叠的,则只会调用一次PARTITION
,时间复杂度为