CLRS第七章思考题

本文详细解答了CLRS第七章的多个思考题,涉及快速排序的原理与优化,包括主元选择、算法复杂度分析、尾递归优化以及区间排序等问题。通过对各个思考题的深入探讨,阐述了算法设计的关键点和效率提升策略。
摘要由CSDN通过智能技术生成

思考题7-1

a) 过程如下,其中红色表示 j 的下标,绿色表示 i 的下标,最后 x=13,j=9,i=10
这里写图片描述

b) 在第一次检查 i<j 时有 i=p,jp (因为 A[p]=x )。若 i=j ,算法将终止并且没有访问越界。若 i<j ,肯定可以交换一次或者已经交换了一次,交换后的值就相当于哨兵的作用。由于 ir,jp ,因此下一次循环也不会越界。而当某一个下标到达数组端点时, i 就再也不会小于或等于 j 了。

c) 若 A[p] 是最小值,则 j=p ,否则肯定会交换 A[i] A[j] 值(至少一次),然后 j 的值会一直减小,因此有 pj<r

d) 每次交换时都会将小于等于主元的数放在下标 i 的位置,大于等于主元的放在下标 j 的位置。然后 j 减小 i 增大,最后位于 j 右侧的大于等于主元,j 左侧的小于等于主元。

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(n1)+Θ(n) ,最后是 Θ(n2)

b) 很简单的一种方法就是从原PARTITION返回的 q 值向两边扩散,找到不等于 A[q] 的值的下标 q,t 。找 q,t 的时间不会超过 Θ(n) ,因而总的时间复杂度为 Θ(rp)

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 个元素选一个做主元,概率 1n。因而 E[xi]=1n

b) 设第 q 小的元素被选为主元,一共有 n 种选择。每次的概率为 Xq 。快排将问题分解为 q1 nq 大小的两个子问题以及一个线性的PARTITION。就得到这个公式。

c) 根据b)有:

E[T(n)]=E[q=1nXq(T(q1)+T(nq)+Θ(n))]=q=1n1n(E[T(q1)]+E[T(nq)]+Θ(n))]=1nq=1nE[T(q1)]+1nq=1nE[T(nq)]+1nq=1nΘ(n)=1nq=0n1E[T(q)]+1nq=0n1E[T(nq+1)]+Θ(n)=1nq=0n1E[T(q)]+1nq=0n1E[T(q)]+Θ(n)=2nq=0n1E[T(q)]+Θ(n)=2nq=2n1E[T(q)]+2E[T(0)]n+2E[T(1)]n+Θ(n)=2nq=2n1E[T(q)]+Θ(n)

d)

k=2n1klgk=k=2n/21klgk+k=n/2n1klgkk=2n/2klgk+k=n/2+1nklgkk=2n/2klg(n/2)+k=n/2+1nklgn=lg(n/2)k=2n/2k +lgnk=n/2+1nk=(lgnlg2)((n/2)(n/2+1)2)+lgn(n(n+1)2(n/2)(n/2+1)2)=lgnn(n+1)2(n/2)(n/2+1)2=12lgn(n2+2n+1)18(n2+2n+1/8)=12n2lgn18n28nlgn+4lgn2n1/8812n2lgn18n2

e) 猜测 E[T(n)]anlgn

E[T(n)]=2nq=2n1E[T(q)]+Θ(n)2nq=2n1anlgn+Θ(n)2an(12n2lgn18n2)+Θ(n)=anlgna4n+Θ(n)anlgn(7.7)

思考题7-4

a) 原始版本中,得到 q 之后在 q 两边调用QUICKSORT,在尾递归中,只是没有进行第二次递归调用,对左边子数组还是有递归调用。现在只看伪代码的第五行 p=q+1 ,它改变 p 然后重新进入循环,实际上有调用TAIL-RECURSIVE-QUICKSORT(A, q+1,r),因而总体一样。

b) 当数组排序好的时候,每次PARTITION返回 r,因而需要调用 n TAIL-RECURSIVE-QUICKSORT,栈深度为 Θ(n)

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!(n3)! 种。为了得到第 i 号元素,需要取比 i 大、小的元素各一个,比 i 小的有 i1 个,比 i 大的有 ni 个。这三个元素一共有 3! 种排列,除以总的排列数得: pi=6(i1)(ni)n(n1)(n2)

b) 平凡实现选择主元的概率为 1n 。所以有:

limn6(i1)(ni)n(n1)(n2)/1n=limn6(n/21)(n/2)(n1)(n2)=limn3n2(n1)=32

即增加了 32

c) 从练习7.2-6知平凡实现得到一个好概率为 12(1/3)=1/3 。对于三数取中:

limni=n/32n/36(i1)(ni)n(n1)(n2)=limn6n(n1)(n2)i=n/32n/3(i1)(ni)=limn(n3)2n/3n/3(i1)(n1)di=limn(n3)16[3627n31627n3+o(n3)927n3+227n3+o(n3)]=limn1n(n1)(n2)1327(n3+o(n3))=limn1327n3+o(n3)n3+o(n3)=1327

增加了 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,时间复杂度为 Θ(n)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值