1. Implement exercise 2.3-7.
请给出一个运行时间为θ(nlgn)的算法,使之能在给定一个由n个整数构成的集合S和另一个整数x时,判断出S中是否存在有两个其和等于x的元素。
问题分析:
可以先将此问题分为两个部分:集合S排序和查找,查找可以用时间复杂度为θ(lgn) 的折半查找,因此,若要满足题目要求,排序算法的时间复杂度不可以超过θ(nlgn)。
时间复杂度为θ(nlgn)的算法有快速排序、堆排序和归并排序,但快速排序在最坏情况下时间复杂度为θ(n*n),堆排序适用于大量数据的情况,在数据量较小的情况下堆排序并不提倡使用,因此采用归并排序算法。尽管它需要额外的空间,但好在题目没有要求。
具体实现方法如下,首先将S排成有序集合,然后对S中的每一个元素si,判断x-si是否在集合S中(折半查找),如此即可满足题意。
源码:
#include<iostream>
using namespace std;
void M(int* A,int l,int q,int r){
int n1=q-l+1;
int n2=r-q;
int L[n1+1],R[n2+1];
int i,j;
for(i=0;i<n1;i++){
L[i]=A[l+i];
}
for(j=0;j<n2;j++){
R[j]=A[q+j+1];
}
L[n1]=1000;
R[n2]=1000;
i=0;
j=0;
for(int k=l;k<=r;k++){
if(L[i]<=R[j]){
A[k]=L[i];
i++;
}
else{
A[k]=R[j];
j++;
}
}
}
void MSORT(int* A,int l,int r){
if(l<r){
int q=(l+r)/2;
MSORT(A,l,q);
MSORT(A,q+1,r);
M(A,l,q,r);
}
}
bool binarysearch(int* A,int y,int min,int max){
int mid;
while(min<=max){
mid=(min+max)/2;
if(A[mid]==y){
return true;
}
if(A[mid]>y){
max=mid-1;
}
else{
min=mid+1;
}
}
return false;
}
bool checksum(int* A,int x){
int n=16;
for(int i=0;i<n;i++){
if(A[i]>=0&&binarysearch(A,x-A[i],0,15)){
return true;
}
}
return false;
}
int main(){
int A[16],x;
cout<<"Before sorting:"<<endl;
for(int i=0;i<16;i++){
A[i]=rand()%100;
cout<<A[i]<<" ";
}
cout<<endl;
cout<<"Input x:";
cin>>x;
MSORT(A,0,15);
cout<<"After sorting:"<<endl;
for(int i=0;i<16;i++){
cout<<A[i]<<" ";
}
cout<<endl;
if(checksum(A,x)){
cout<<"Find it!"<<endl;
}
else{
cout<<"Do not exist!"<<endl;
}
system("pause");
return 0;
}
2. Implement priority queue.
问题分析:
以最大优先级队列为例:构建优先级队列即为在一串乱序数字中,第k次将第k大的数字找出来,因此不进行排序,直接构造最大堆,然后将堆的根依次extract,即为最大优先级队列。
因为不对构建的最大堆进行排序,仅仅是extract根节点,所以建堆阶段时间复杂度要小于堆排序,而之后的heap_extract_max操作运行时间为ο(lgn),对n个元素,又堆中节点是逐渐减少,故时间复杂度小于ο(nlgn)。
源码:
#include<iostream>
using namespace std;
int length=16;
int heap_size;
int parent(int i){
return (i-1)/2;
}
int left(int i){
return 2*i+1;
}
int right(int i){
return 2*i+2;
}
void exchange(int& a,int& b){
int t;
t=a;
a=b;
b=t;
}
void max_heapify(int* A,int i){
int l=left(i);
int r=right(i);
int largest;
if(l<heap_size&&A[l]>A[i])
largest=l;
else
largest=i;
if(r<heap_size&&A[r]>A[largest]){
largest=r;
}
if(largest!=i){
exchange(A[i],A[largest]);
max_heapify(A,largest);
}
}
void build_max_heap(int* A){
heap_size=length;
for(int i=length/2-1;i>=0;i--){
max_heapify(A,i);
}
}
/*int heap_maximum(int* A){
return A[0];
}*/
int heap_extract_max(int* A){
if(heap_size<1){
exit(-1);
}
int max=A[0];
A[0]=A[heap_size-1];
heap_size--;
max_heapify(A,0);
return max;
}
/*void heap_increase_key(int* A,int i, int key){
if(key<A[i]){
exit(-2);
}
A[i]=key;
while(i>0&&A[parent(i)]<A[i]){
exchange(A[i],A[parent(i)]);
i=parent(i);
}
}*/
/*void max_heap_insert(int* A,int key){
A[heap_size]=-1000;
heap_increase_key(A,heap_size,key);
}*/
int main(){
int A[20];
int n;
cout<<"Before Heapify;"<<endl;
for(int i=0;i<length;i++){
A[i]=rand()%100;
cout<<A[i]<<" ";
}
cout<<endl;
build_max_heap(A);
cout<<"After Heapify:"<<endl;
for(int i=0;i<length;i++){
cout<<A[i]<<" ";
}
cout<<endl;
/*cout<<"Input the number you want to insert:"<<endl;
cin>>n;
max_heap_insert(A,n);
for(int i=0;i<length+1;i++){
cout<<A[i]<<" ";
}
cout<<endl;*/
cout<<"The priority queue is:"<<endl;
for(int i=0;i<length;i++){
cout<<heap_extract_max(A)<<" ";
}
cout<<endl;
system("pause");
return 0;
}
3. Implement Quicksort and answer the following questions.
(1) How many comparisons will Quicksort do on a list of n elements that all have the same value?
(2) What are the maximum and minimum number of comparisons will Quicksort do on a list of n elements, give an instance for maximum and minimum case respectively.
问题分析:
此题目仅要求一个简单的快速排序实现,附带两个问题,在对快速排序理解的基础上并不难完成。
作为对冒泡排序的一种改进,快速排序成功的将平均时间复杂度降为Ο(nlog n),每一趟排序它用分治法的策略把一个序列分为两个子序列,我们学习的快速排序是一个原地分割的版本,也就降低了空间复杂度。
源码:
#include<iostream>
using namespace std;
int n=0;
void exchange(int& a,int& b){
int t;
t=a;
a=b;
b=t;
}
int PARTITION(int* A,int l,int r){
int x=A[r];
int i=l-1;
for(int j=l;j<=r-1;j++){
if(A[j]<=x){
n++;
i++;
exchange(A[i],A[j]);
}
}
exchange(A[i+1],A[r]);
return i+1;
}
void QUICKSORT(int* A,int l,int r){
if(l<r){
int q=PARTITION(A,l,r);
QUICKSORT(A,l,q-1);
QUICKSORT(A,q+1,r);
}
}
int main(){
// int A[7]={1,3,2,5,7,6,4};//best
int A[7]={1,2,3,4,5,6,7};//worst
cout<<"Before Sorting;"<<endl;
for(int i=0;i<7;i++){
// A[i]=11;
cout<<A[i]<<" ";
}
cout<<endl;
QUICKSORT(A,0,6);
cout<<"After Sorting:"<<endl;
for(int i=0;i<7;i++){
cout<<A[i]<<" ";
}
cout<<endl;
cout<<"Count:"<<n<<endl;
system("pause");
return 0;
}
4. Give a divide and conquer algorithm for the following problem: you are given two sorted lists of size m and n, and are allowed unit time access to the ith element of each list. Give an O(lg m + lgn) time algorithm for computing the kth largest element in the union of the two lists. (For simplicity, you can assume that the elements of the two lists are distinct).
问题分析:
假设两个有序的序列为从大到小排列。问题要求时间复杂度为O(lg m + lgn),并且采用分治的方式,因此可将两个序列(命名为A和B)分别从中间分开,子序列长度为m/2(设x)、n/2(设y)。这样A[x]和B[y]就是A和B中间的元素。
设A[x]>B[y](同理可得A[x]<B[y]的情况),这样合并后A[x]之前的元素必然在B[y]之前,即B[y]之前至少有x+y+1个元素。若k<x+y+1,则可排除B序列的后半部分元素,也就是将B排除了一半。
A[x]之后有m-x-1个元素,B[y]之后有n-y-1个元素,由于A[x]>B[y],则A[x]之后有至少(m-x-1)+(n-y-1)+1=(m+n)-(x+y+1)个元素。这样若k>x+y+1,则可将A[x]之前的元素排除,也就是将A排除了一半。
每一次比较必将A或者B排除一半,根据分治方法,不难判断出算法的时间复杂度为O(lg m + lgn)。
源码:
#include<iostream>
using namespace std;
int A[8]={0,3,6,7,11,14,16,23};
int B[13]={1,4,5,10,15,19,24,26,27,28,31,35,40};
int searchk(int al,int ar,int bl,int br,int k){
int am=(al+ar)/2;
int bm=(bl+br)/2;
if(al>ar)
return B[bl+k-1];
if(bl>br)
return A[al+k-1];
if (A[am] <= B[bm]) {
if (k <= (am - al) + (bm - bl) + 1) {
return searchk(al, ar, bl, bm-1, k);
} else {
return searchk(am+1, ar, bl, br, k-(am-al)-1);
}
} else {
if (k <= (am - al) + (bm - bl) + 1) {
return searchk(al, am-1, bl, br, k);
} else {
return searchk(al, ar, bm+1, br, k-(bm-bl)-1);
}
}
return -1;
}
int main(){
int k;
cout<<"INPUT K:";
cin>>k;
int n=searchk(0,7,0,12,22-k);
cout<<"The "<<k<<"th largest element is "<<n<<endl;
return 0;
}