算法与设计分析作业
2 Divide and Conquer
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
For example, Given [3, 2 ,1, 5, 6, 4] and k = 2, return 5.
problem-solving ideas
借鉴快速排序的思想,快排每一次排序之后可以确定一个元素的位置,该元素的左边均小于该元素,右边均大于该元素,因此可以用该元素的位置与k比较,来确定第k大的元素在当前数组那个范围内,然后递归。从而省去另一部分的查找。
Pseudo-code
function findKthLargest(A,k,s,e){
随机选取一个元素A[j]作为分界点
for i=s to e do
if A[i] < A[j]
将A[i]放在A[j]的右边
end if
if A[i] > A[j]
将A[i]放在A[j]的左边
end if
end for
A[j]当前在A数组中的位置为w,数组长度为length(数组从0开始)
if k == length-w
返回 A[j]
else if k> length-w
返回 findKthLargest(A,k,s,w);
else
返回 findKthLargest(A,k,w,e)
end if
}
Subproblem reduction graph
Prove the correctness
根据快排的性质,每一次的递归都可以确定一个元素的位置,且该元素左边的元素均小于该元素值,右边的元素均大于该元素值,而我们要找的是第k大的元素,通过两者位置的比较,我们一定能确定第k大元素在哪一个范围内,随着递归深入范围逐渐缩小,因此最后一定能找到第k大的元素。
#### The complexity of your algorithm
最好的情况:每次挑选的元素都能将数组平均分成左右相等两边,则 T(n) =T(n/2)+cn。根据公式T(n)=O(n),所以时间复杂度为O(n)。
最坏的情况:每次挑选的元素都将数组分在一边(即挑选的都是最大值或最小值),则T(n)=T(n-1)+cn。T(n)=O(n2).
平均情况应该接近于最好的情况。
3 Divide and Conquer
Consider an n-node complete binary tree T, where n = 2d −1 for some d. Each node v of T is labeled with a real number xv. You may assume that the real numbers labeling the nodes are all distinct. A node v of T is a local minimum if the label xv is less than the label xw for all nodes w that are joined to v by an edge.
You are given such a complete binary tree T, but the labeling is only specified in the following implicit way: for each node v, you can determine the value xv by probing the node v. Show how to find a local minimum of T using only O(logn) probes to the nodes of T.
problem-solving ideas
问题的关键在于如何分治把范围缩小,且范围内一定要有局部最小值。因此递归的子树的要求是,递归子树的根结点的值要小于父结点的值,这样的子树一定有局部最小值,且范围被缩小。
Pseudo-code
function findLocalMinimum(T){
if T的值小于左右子树根结点的值,所有子树若为空则算小于
返回 T->data
else if T的值大于左子树根结点的值小于右子树根结点的值
返回 findLocalMinimum(T的右子树)
else if T的值小于左子树根结点的值大于右子树根结点的值
返回 findLocalMinimum(T的左子树)
else
返回 findLocalMinimum(T的左子树或T的右子树)
end if
}
Subproblem reduction graph
Prove the correctness
任何一棵完全二叉树都有局部最小值,完全二叉树的子树仍然是完全二叉树。对于任何一棵树T,如果T的值小于左右子树的根节点,则T的值就是局部最小值,否则T的值一定大于其中某一棵子树,由于该子树的父结点大于该子树根结点的值,因此父结点不用考虑,且该子树一定有局部最小值。
The complexity of your algorithm
分析算法可得 T(n) =T(n/2)+c。根据公式T(n)=O(log n),所以时间复杂度为O(log n)。
6 Divide and Conquer
Recall the problem of finding the number of inversions. As in the course, we are given a sequence of n numbers a1,··· ,an, which we assume are all distinct, and we difine an inversion to be a pair i < j such that ai > aj.
We motivated the problem of counting inversions as a good measure of how different two orderings are. However, one might feel that this measure is too sensitive. Let’s call a pair a significant inversion if i < j and ai > 3aj. Given an O(nlogn) algorithm to count the number of significant inversions between two orderings.
problem-solving ideas
只需在普通的寻找逆序对中改一些步骤。依旧需要归并排序,在归并排序的过程中声明j1,j2(普通的寻找逆序对只声明j),j1用来归并排序。j2来表示是否当前的指向的元素是否已经用来记过数(j2一开始指向A[mid+1],每当A[i]>3*A[j2]就记逆序对数,并且j2向后移动,表明已经被记过数了)。
Pseudo-code
function sortAndCount(A,s,e){
if s < e
mid = (s+e)/2
count1 = sortAndCount(A,s,mid)
count2 = sortAndCount(A,mid+1,e)
返回 count1+count2+mergeAndCount(A,s,mid,e)
else
返回 0
end if
}
function mergeAndCount(A,start,mid,end){
i=start,j1=mid+1,j2=mid+1,count=0,k=0
建立数组temp临时存放排序好的数组元素
while i<=mid and j1<=end
if A[i]<A[j1]
if A[i]>3*A[j2]
count += mid - i + 1
j2++
else
temp[k++]=A[i++]
end if
else
temp[k++]=A[j1++]
end if
end while
while i<=mid
if(j2<=end)
if A[i]>3*A[j2]
count += mid - i + 1
j2++
else
temp[k++]=A[i++]
end if
else
temp[k++]=A[i++]
end if
end while
while j1<end
temp[k++]=A[j1++]
end while
将temp数组再重新赋给A
返回 count
}
Subproblem reduction graph
Prove the correctness
普通的寻找逆序对的方法的正确性已经被证明,这里只是根据题意修改了计数的方法,因此该方法也应该是正确的。
The complexity of your algorithm
时间复杂度和普通的求逆序对算法一样为O(nlogn)。T(n)=2*T(n/2)+cn 推出 T(n)=O(nlogn)。
10 Divide and Conquer
Implement the Strassen algorithm algorithm for MatrixMultiplication problem in your favourite language, and compare the performance with grade-school method.
C++ Code
//矩阵乘法(分治)
#include <STDLIB.H>
#include <IOSTREAM>
#include <time.h>
using namespace std;
void print(int **M, int n)
{
int i;
int j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
cout << M[i][j] << " ";
}
cout << endl;
}
}
void init(int ** &M, int n)
{
M = new int *[n];
int i;
for (i = 0; i < n; i++)
{
M[i] = new int[n];
}
}
void add(int **&A, int **&B, int n, int **&C) //A+B=C
{
int i;
int j;
for (i = 0; i < n; i++)
{
for (j = 0; j