【算法】分治算法(第三章习题解答)

文章介绍了使用分治算法解决不同问题的方法,包括将包含正负数的数组重新排列、最大化两个子集元素之和的差以及在已排序数组中寻找中位数。此外,还提出了计算大整数幂模运算的分治策略,以避免直接计算导致的时间和精度问题。所有算法都强调了在限定时间和空间复杂度内完成操作的重要性。
摘要由CSDN通过智能技术生成

3 分治算法

在这里插入图片描述

3.1

A A A n n n 个非 0 0 0 实数构成的数组, 设计一个算法重新排列数组中的数, 使得负数都排在正数前面. 要求算法使用 O ( n ) O(n) O(n) 的时间和 O ( 1 ) O(1) O(1) 的空间.

算法设计:由于算法要求使用 O ( n ) O(n) O(n) 的时间和 O ( 1 ) O(1) O(1) 的空间,因此可以使用类似快速排序的方式将数组扫描一遍完成局部排序。而且已知 A A A中只有正数和负数,因此如果以比较作为基本运算,比较的对象就是 0 0 0,相当于对 A A A进行一轮哨兵元素为 0 0 0的快速排序。

数据输入: A [ 1.. n ] A[1 . . n] A[1..n]

结果输出:排序好的 A [ 1.. n ] A[1 . . n] A[1..n]

伪码描述:

  1. p ← 1 / / 从起始位置向后扫描的指针 p \leftarrow 1 \quad / / 从起始位置向后扫描的指针 p1//从起始位置向后扫描的指针
  2. q ← n / / 从末尾位置向前扫描的指针 q \leftarrow n \quad / / 从末尾位置向前扫描的指针 qn//从末尾位置向前扫描的指针
  3. w h i l e    A [ p ] < 0    a n d    p < n    d o / / 直到找到第一个正数 while\ \ A[p]<0\ \ and \ \ p<n\ \ do \quad //直到找到第一个正数 while  A[p]<0  and  p<n  do//直到找到第一个正数
  4. p ← p + 1 p \leftarrow p+1 pp+1
  5. w h i l e    A [ q ] > 0    a n d    p > 1    d o / / 直到找到第一个负数 while\ \ A[q]>0\ \ and \ \ p>1\ \ do \quad //直到找到第一个负数 while  A[q]>0  and  p>1  do//直到找到第一个负数
  6. q ← q − 1 q \leftarrow q-1 qq1
  7. i f    p < q / / 检查满足交换的条件 if\ \ p<q \quad //检查满足交换的条件 if  p<q//检查满足交换的条件
  8. t h e n   s w a p ( A [ p ] , A [ q ] ) ; g o t o   3 / / 交换两个元素并开始新一轮扫描 then \ swap(A[p], A[q]);goto\ 3\quad//交换两个元素并开始新一轮扫描 then swap(A[p],A[q]);goto 3//交换两个元素并开始新一轮扫描
  9. e l s e   r e t u r n    A / / 排序结束返回 else\ return\ \ A\quad//排序结束返回 else return  A//排序结束返回

复杂度分析:该算法相当于对数组进行一轮哨兵元素为 0 0 0的快速排序,易得算法的时间复杂度是
W ( n ) = O ( n ) \begin{aligned} W(n)=O(n ) \end{aligned} W(n)=O(n)
算法没有空间存储需求,全部操作都在原数组上完成,因此空间复杂度是
S ( n ) = O ( 1 ) \begin{aligned} S(n)=O(1 ) \end{aligned} S(n)=O(1)

3.2

S S S n n n 个不等的正整数集合, n n n 为偶数, 给出一个算法将 S S S 划分为子集 S 1 S_1 S1 S 2 S_2 S2,使得 ∣ S 1 ∣ = ∣ S 2 ∣ = n / 2 |S_1| = |S_2| = n/2 S1=S2=n/2, 且 ∣ ∑ x ∈ S 1 x − ∑ x ∈ S 2 x ∣ | ∑ _{x∈S_1} x − ∑ _{x∈S_2} x| xS1xxS2x 达到最大, 即使得两个子集元素之和的差达到最大.

算法设计: n n n个不等的正整数集合 S S S的元素个数为偶数,因此 n / 2 n/2 n/2仍为整数,划分后 ∣ S 1 ∣ = ∣ S 2 ∣ = n / 2 |S_1| = |S_2| = n/2 S1=S2=n/2,要想使得 ∣ ∑ x ∈ S 1 x − ∑ x ∈ S 2 x ∣ | ∑ _{x∈S_1} x − ∑ _{x∈S_2} x| xS1xxS2x 达到最大,就需要找出这个集合中较大或者较小的 n / 2 n/2 n/2个数,一种简易的思路就是先对集合 S S S进行排序,排好序后直接按索引将前 n / 2 n/2 n/2个数划分出来作为目标子集即可达到目的。

数据输入: S [ 1.. n ] S[1 . . n] S[1..n]

结果输出: S 1 [ 1.. n / 2 ] , S 2 [ 1.. n / 2 ] S_1[1..n/2], S_2[1..n/2] S1[1..n/2],S2[1..n/2]

伪码描述:

  1. L ← Sort ⁡ ( S ) / / 先对集合 S 使用排序算法进行排序得到数组 L L \leftarrow \operatorname{Sort}(S) \quad / / 先对集合S使用排序算法进行排序得到数组L LSort(S)//先对集合S使用排序算法进行排序得到数组L
  2. S 1 ← L [ 1.. n / 2 ] / / 划分排序后的数组的到子集 S 1 S_1 \leftarrow L[1..n/2] \quad / / 划分排序后的数组的到子集S_1 S1L[1..n/2]//划分排序后的数组的到子集S1
  3. S 2 ← L [ n / 2.. n ] / / 划分排序后的数组的到子集 S 2 S_2 \leftarrow L[n/2..n] \quad / / 划分排序后的数组的到子集S_2 S2L[n/2..n]//划分排序后的数组的到子集S2
  4. r e t u r n    S 1 , S 2 / / 返回划分好的两个子集 return\ \ S_1, S_2\quad//返回划分好的两个子集 return  S1,S2//返回划分好的两个子集

复杂度分析:该算法的时间代价集中在第 1 1 1行的排序算法的环节,排序的时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn),之后的数组划分部分时间复杂度为 O ( 1 ) O(1 ) O(1),因此该算法的总时间复杂度为
W ( n ) = O ( n log ⁡ n ) + O ( 1 ) = O ( n log ⁡ n ) \begin{aligned} W(n)=O(n \log n)+O(1 )=O(n \log n) \end{aligned} W(n)=O(nlogn)+O(1)=O(nlogn)

默认排序算法都在原数组上完成,因此算法只需要常数规模的存储空间,空间复杂度是
S ( n ) = O ( 1 ) \begin{aligned} S(n)=O(1 ) \end{aligned} S(n)=O(1)

3.3

A A A B B B 都是从小到大已经排好序的 n n n 个不等的整数构成的数组, 如果把 A A A B B B 合并后的数组记作 C C C, 设计一个算法找出 C C C 的中位数并给出复杂度分析.

算法设计:第一时间想到的思路是直接对 A A A B B B进行顺序遍历同时比较大小,找到第 n n n个数返回,这相当于将 C C C进行了排序,但是题目只需要我们给出 C C C的中位数即可,显然这样的算法并不是最优解。

因此不妨采用递归分治的思想,并采用类似二分查找的方式寻找中位数,具体操作即将 A A A B B B的中位数拿出比较,如果二者相等则就是 C C C的中位数,如果不相等,则根据具体情况递归查询规模减半的 A A A B B B的一半子集。

数据输入: A [ 1.. n ] , B [ 1.. n ] A[1 . . n],B[1 . . n] A[1..n],B[1..n]

结果输出: C C C的中位数 m e d i a n median median

伪码描述:

f i n d M e d i a n   ( A , B , s t a r t 1 , e n d 1 , s t a r t 2 , e n d 2 ) : findMedian\ (A, B, start1,end1,start2,end2): findMedian (A,B,start1,end1,start2,end2):

  1. m i d 1 ← ( s t a r t 1 + e n d 1 ) / 2 / / 找到 A 的中间元素索引值 mid1 \leftarrow (start1+end1)/2 \quad / / 找到A的中间元素索引值 mid1(start1+end1)/2//找到A的中间元素索引值
  2. m i d 2 ← ( s t a r t 2 + e n d 2 ) / 2 / / 找到 B 的中间元素索引值 mid2 \leftarrow (start2+end2)/2 \quad / / 找到B的中间元素索引值 mid2(start2+end2)/2//找到B的中间元素索引值
  3. i f    A [ m i d 1 ] = B [ m i d 2 ] / / 判断是否为中位数 if\ \ A[mid1]=B[mid2]\quad //判断是否为中位数 if  A[mid1]=B[mid2]//判断是否为中位数
  4. t h e n   r e t u r n   A [ m i d 1 ] / / 找到峰顶返回 then \ return \ A[mid1]\quad//找到峰顶返回 then return A[mid1]//找到峰顶返回
  5. i f    A [ m i d 1 ] < B [ m i d 2 ] / / 峰顶在后半部分 if\ \ A[mid1]<B[mid2] \quad //峰顶在后半部分 if  A[mid1]<B[mid2]//峰顶在后半部分
  6. t h e n   f i n d M e d i a n   ( A , B , m i d 1 , e n d 1 , s t a r t 2 , m i d 2 ) / / 递归寻找 then \ findMedian\ (A, B, mid1,end1,start2,mid2)\quad//递归寻找 then findMedian (A,B,mid1,end1,start2,mid2)//递归寻找
  7. i f    A [ m i d 1 ] > B [ m i d 2 ] / / 峰顶在前半部分 if\ \ A[mid1]>B[mid2] \quad //峰顶在前半部分 if  A[mid1]>B[mid2]//峰顶在前半部分
  8. t h e n   f i n d M e d i a n   ( A , B , s t a r t 1 , m i d 1 , m i d 2 , e n d 2 ) / / 递归寻找 then \ findMedian\ (A, B, start1,mid1,mid2,end2)\quad//递归寻找 then findMedian (A,B,start1,mid1,mid2,end2)//递归寻找

递推方程:
{ W ( n ) = W ( n 2 ) + 1 W ( 1 ) = 1 \left\{\begin{array}{l} W(n)=W\left(\frac{n}{2}\right)+1 \\ W(1)=1 \end{array}\right. {W(n)=W(2n)+1W(1)=1
复杂度分析:该算法使用递归分治的思想,由递推方程可知,每次递归问题的规模减半,因此易得算法的时间复杂度是
W ( n ) = O ( log ⁡ n ) \begin{aligned} W(n)=O(\log n ) \end{aligned} W(n)=O(logn)
算法只需要常数规模的存储空间,因此空间复杂度是
S ( n ) = O ( 1 ) \begin{aligned} S(n)=O(1 ) \end{aligned} S(n)=O(1)

3.4

输入三个正整数 a , p , k a, p, k a,p,k, 求 a p   m o d   k a^p\ mod\ k ap mod k 的值. 提示: 由于数据的规模很大, 如果直接计算, 不仅需要采用高精度, 而且时间复杂度很大。例如 1 0 25   m o d   7 = 3 10^{25}\ mod\ 7 = 3 1025 mod 7=3, 但 1 0 25 10^{25} 1025 超出了整型数的表示范围,不能直接计算. 请使用分治策略实现取余运算的算法并给出复杂度分析.

算法设计:根据取余运算的相关性质:
a p a q   m o d   k = ( a p   m o d   k ) ( a q   m o d   k ) a^pa^q\ mod \ k=(a^p\ mod\ k)(a^q\ mod\ k) apaq mod k=(ap mod k)(aq mod k)
由此想到可以对指数运算的取余操作运用分治策略,具体操作可以用以下公式概括:
a p   m o d   k = a p / 2 a p / 2   m o d   k = ( a p / 2   m o d   k ) ( a p / 2   m o d   k ) a^p\ mod\ k=a^{p/2}a^{p/2}\ mod \ k=(a^{p/2}\ mod\ k)(a^{p/2}\ mod\ k) ap mod k=ap/2ap/2 mod k=(ap/2 mod k)(ap/2 mod k)
值得注意的是,在分治过程中 p / 2 p/2 p/2不一定是整数,因此需要额外考虑和处理 p p p为奇数的情况。

数据输入:三个正整数 a , p , k a, p, k a,p,k

结果输出:取余运算结果 r e s u l t result result

伪码描述:

M o d   ( a , p , k ) : Mod\ (a, p, k): Mod (a,p,k):

  1. i f    p = 0 / / 递归终止条件 if\ \ p=0\quad //递归终止条件 if  p=0//递归终止条件
  2. t h e n    r e t u r n    1 then\ \ return\ \ 1 then  return  1
  3. t ← M o d   ( a , p / 2 , k ) / / 递归分治,规模减半 t \leftarrow Mod\ (a, p/2, k) \quad / / 递归分治,规模减半 tMod (a,p/2,k)//递归分治,规模减半
  4. i f    p % 2 = 1 / / 判断 p 是否为奇数 if\ \ p\%2=1\quad //判断p是否为奇数 if  p%2=1//判断p是否为奇数
  5. t h e n   r e t u r n   ( t ∗ t ∗ b ) % k / / 处理 p 为奇数的情况 then \ return \ (t*t*b)\%k\quad//处理p为奇数的情况 then return (ttb)%k//处理p为奇数的情况
  6. e l s e    r e t u r n   ( t ∗ t ) % k / / p 为偶数,正常返回 else\ \ return \ (t*t)\%k\quad//p为偶数,正常返回 else  return (tt)%k//p为偶数,正常返回

递推方程:
{ W ( n ) = W ( n 2 ) + 1 W ( 1 ) = 1 \left\{\begin{array}{l} W(n)=W\left(\frac{n}{2}\right)+1 \\ W(1)=1 \end{array}\right. {W(n)=W(2n)+1W(1)=1
复杂度分析:该算法使用递归分治的思想,每次递归问题的规模减半,因此易得算法的时间复杂度是
W ( n ) = O ( log ⁡ n ) \begin{aligned} W(n)=O(\log n ) \end{aligned} W(n)=O(logn)
算法只需要常数规模的存储空间,因此空间复杂度是
S ( n ) = O ( 1 ) \begin{aligned} S(n)=O(1 ) \end{aligned} S(n)=O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mitch311

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值