🦄1.分治法(Divide-and-Conquer)
1.1分治法的设计思想
将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,
分而治之
1.2分治法的适用条件
- 该问题的规模缩小到一定的程度就可以容易地解决;
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质
- 利用该问题分解出的子问题的解可以合并为该问题的解;
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。
1.3分治法的基本步骤
divide-and-conquer(P)
{
if ( | P | <= n0) adhoc(P); //解决小规模的问题
divide P into smaller subinstances P1,P2,...,Pk;//分解问题
for (i=1,i<=k,i++)
yi=divide-and-conquer(Pi); //递归的解各子问题
return merge(y1,...,yk); //将各子问题的解合并为原问题的解
}
1.4主定理Master Theorem
🦄2.范例
2.1合并排序
2.1.1 基本思想
将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。
2.1.2 伪代码实现
public static void mergeSort(Comparable a[], int left, int right)
{
if (left<right)
{//至少有2个元素
int i=(left+right)/2; //取中点
mergeSort(a, left, i);
mergeSort(a, i+1, right);
merge(a, b, left, i, right); //合并到数组b
copy(a, b, left, right); //复制回数组a
}
}
2.1.3 复杂度分析【nlogn】
T(n)=Θ(nlogn) 渐进意义下的最优算法
2.2 二分搜索
给已排好序的n个元素中寻找特定元素x
2.2.1 基本思想
- 该问题的规模缩小到一定的程度就可以容易地解决
- 该问题可以分解为若干个规模较小的相同问题;
- 分解出的子问题的解可以合并为原问题的解;
- 该问题具有最优子结构性质;
2.2.2 伪代码实现
2.3.3 复杂度分析【最坏logn】
2.3 Strassen矩阵乘法
A和B的乘积矩阵C中的元素C[i,j]定义为
传统方法:O(n3)【计算时,三个for循环】
2.3.1基本思想
为了降低时间复杂度,必须减少乘法的次数
2.3.2 复杂度分析【nlog7 =n 2.81】
2.4 大整数乘法
请设计一个有效的算法,可以进行两个n位大整数的乘法运算
2.4.1基本思想
为了降低时间复杂度,必须减少乘法的次数
XY = ac 2n + (ad+bc) 2n/2 + bd
🦄3.习题
3.1 指定序号元素查找
1,查找数组a[n]中的最大元素,至少需要多少次比较?
2,查找数组a[n]中的最大和最小元素,用最少的元素比较次数。
3,查找数组a[n]中的第k小的元素(k相对于n比较小);
4,查找数组a[n]中的中位数(序号为n/2);
3.2设计算法
设计算法,找出数组a[n]的中位数。
设计算法,找出数组a[n]中序为k的数。
设计算法,输出数组a[n]中所有序为奇数的数。
设计算法,计算序列a[n]中的逆序数
3.3矩阵走法
-
有一个5×4的矩阵,从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,总共有多少种走法?
-
有一个矩阵,从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,求解总共有多少种走法的函数f(x,y),可以理解x是方格的横坐标、y是纵坐标,x≥0,y≥0。 定义f(0,0)=0,f(x,1)=1, f(1,y)=1, 其它情况f的递归定义是()。
答: f(x,y) =f(x-1,y)+f(x,y-1) -
某段楼梯有10级台阶,每次只能跨一级或两级,爬上这段楼梯共有______种走法?
4.要拼一个1×7长方形,可用的纸片有以下规格:1×1, 1×2, 1×3,共有______种拼法?
== 解:f(n) = f(n-1)+f(n-2)+f(n-3) 1,2,4,7,13,24,44==
3.4排序
3.4.1合并排序
如果再合并排序算法的分割步中,将数组a[0:n-1]划分为⌊√n⌋个子数组,每个子数组中有O(√n)个元素。然后递归地对子数组进行排序,最后将所得到的个排好序的子数组合并成所要求的排好序的数组。以上排序算法的平均时间复杂度是多少?
Θ(√n*log√n)
3.4.2快速排序
low和high是索引
void QuickSort ( SqList &L, int low, int high ){
//在序列lowhigh 中递归地进行快速排序
if ( low < high) {
int p= Partition (L, low, high); //p作为中间值索引进行划分
QuickSort ( L, low, p-1 ); //对左序列递归处理
QuickSort ( L, p+1, high); //对右序列递归处理
}
}
int Partition ( SqList &L, int low, int high ) {
pivotkey = L.r[low].key; //基准对象关键字【找第一个数字】
while(low<high){
👇这个while先把所有小于基准的放左边
while(low<high && L.r[high].key >= pivotkey) high--;//如果大于基准值则high--,换下一个比较
L.r[low] L.r[high]; //小于基准对象的移到区间的左侧
👇这个while再把所有大于基准的放右边
while(low<high&& L.r[low].key <= pivotkey) low++;
L.r[high] L.r[low] ; //大于基准对象的移到区间的右侧
}
return low;
}
Partition函数作用:通过基准将数组分成两部分,小于基准的在基准左边,大于则在右边。Partition时间复杂度Θ(n)
3.4.2.1复杂度
平均时间复杂度Θ(n*logn)
最坏时间复杂度O(n2)
平均空间复杂度O(logn),logn次都需要一个空间存基准
3.5线性时间选择算法
- 关于线性时间选择算法select中,每5个元素划为一组,并将所有组的中位数合成一个“短数组”,下列说法中正确的是
== “短数组”的长度大约为n/5;“短数组”的中位数将作为select的基准元==
3.6快速排序中第k小的元素的算法
可基于快速排序中的划分算法Partition (int a[], int low, int high)完成查找数组a[low:high]中第k小的元素的算法,其中,1≤k≤high-low+1。
int Search ( int a[], int low, int high, int k ) {
//在a[low:high]中查找第k小的元素
if (low==high) return a[low];
int p = Partition(a, low, high);
int t = p-low+1;
if(k<=t) return Search(a, low, p, k );
else return Search(a, p+1, high, k-p );
3.6.1复杂度
平均时间复杂度情况下,是Θ(n)
最坏时间复杂度O(n2)
🦄4.书后习题
2-4 大整数乘法的O(nm log(3/2))
2-5
2-27 以中位数为基准的选择问题
2-31
2-8, 2-9, 2-15, 2-16, 2-28