算法导论:第一部分
前言
最近看了下算法导论,分享下文中的程序和思路,同时也包括一些习题的答案和程序,希望对大家有所帮助。前面有些程序用python和C++都写了遍,可能是当时闲得慌,后面可能只用C++了,当然练习就全用C++了。C++使用的相关库,可能会做相应说明?
文中会对本书的某些内容阐述自己的理解,如果有任何的错误,望大家指正。有些题目太过简单就看心情了,可能后面会补上去。
然后第一章就不写了,大家自己看看就好了。
第一部分:基础知识
2 算法基础
2.1 插入排序
插入排序在元素数量少的情况下比较有效,再加上有原址性,所以一般一些递归算法的小型排序都可以用到。
INSERTION-SORT
Python
def InsertionSort(A):
for i in range(1,len(A)):
key=A[i]
j=i-1
while j>=0 and A[j]>key:
A[j+1]=A[j]
j-=1
A[j+1]=key
print(i)
return (A)
C++
#include<vector>
using namespace std;
void InsertionSort(vector<double>&input){
for (int i=1;i<input.size();i++){
auto key=input[i];
int j=i-1;
for (;j>=0&&input[j]>key;j--){
input[j+1]=input[j];
}
input[j+1]=key;
}
}
原址性:在输入数组中仅有常数个元素需要在排序过程中存储在数组
练习2.1-1
其过程如下:
|31|41,59,26,41,58
→
\rightarrow
→31,41|59,26,41,58
→
\rightarrow
→|31,41,59|26,41,58
→
\rightarrow
→
313,412,591|26|41,58
→
\rightarrow
→26,31,41,591|41|58
→
\rightarrow
→26,31,41,41,591|58|
→
\rightarrow
→
26,31,41,41,58,59
元素表示此元素发生位置交换;
元素
i
i
i上标表示红色元素与此元素交换的次序为
i
i
i;
练习2.1-2
#include<vector>
using namespace std;
void InsertionSort(vector<double>&input){
for (int i=1 ; i< input.size(); i++){
auto key=input[i];
int j=i-1;
for (;j>=0&&input[j]<key;j--){
input[j+1]=input[j];
}
input[j+1]=key;
}
}
练习2.1-3
#include<vector>
#include<iostream>
using namespace std;
int Find(vector<double>&input,double f ){
for (int i=0;i<input.size();i++){
if (input[i]==f)
return i;
}
cerr<<"No such number "<<f<<" in input"<<endl;
return -1;
}
循环不变式的证明:
初始化:将数组中的第一个元素和输入数字进行比较,如果相等,返回元素位置,不等则继续循环。
保持:显然,如果找不到与输入相等的元素,循环会一直进行下去,直到终止条件。
终止:如果在循环中能找到相等的元素,则返回元素位置,跳出循环。否则为未找到输入元素,输出未找到元素。
练习2.1-4
#include<vector>
#include<iostream>
using namespace std;
vector<int> Tran10TOX(int num,int X){
vector<int> ret;
int result=num;
while (result!=0){
int residue=result%X;
ret.push_back(residue);
result/=X;
}
return ret;
}
int main(int argc,char** argv){
int a=9,b=10;
auto A=Tran10TOX(a,2),B=Tran10TOX(b,2),C=Tran10TOX(a+b,2);
cout<<A.size()<<','<<B.size()<<","<<C.size()<<endl;
}
//注意:高位数储存在vector后面。如:std::vector<int> A[i]是X进制的i+1位。
2.2 分析算法
练习2.2-1
显然是 Θ \Theta Θ(n3)……
练习2.2-2
#include<vector>
using namespace std;
void ChioseSort(vector<double>&input){
for (int i=0;i< input.size();i++){
for (int j=i+1;j< input.size();j++){
if (input[j]<input[i]){
auto temp=input[i];
input[i]=input[j];
input[j]=temp;
}
}
}
}
循环不变式的证明:
初始化:将A的第一个元素和其他n-1个元素进行比较,如果某个元素小于第一个元素,进行交换,继续循环,最后数组第一个元素是数组中最小的。
保持:若循环进行到第i个,前面已经是按次序排好,第i个元素将于后面n-i个元素进行比较,将后面组的最小值换置到第i个元素上。
终止:显然当循环到最后一个元素时,其是数组最大值,而且,数组已按升序排好。
为什么只要和n-1个元素运行?:因为其是用n个元素中的一个元素为参照进行对比的,显然没有必要自己和自己对比。
其最好和最坏情况下的运行时间均是
Θ
(
n
2
)
\Theta(n^2)
Θ(n2),因为不管数组顺序如何,其总要进行
(
n
−
1
)
⋅
n
2
\frac{(n-1)\cdot n}2
2(n−1)⋅n比较。
练习2.2-3
合适元素在第i位的出现概率为
(1)
P
{
X
=
i
}
=
1
n
P\{X=i\}=\frac1{n} \tag{1}
P{X=i}=n1(1)
所以其位置的期望
(2)
E
(
X
)
=
∑
i
=
1
n
i
⋅
1
n
=
n
+
1
2
E(X)=\sum_{i=1}^n i\cdot\frac{1}{n}=\frac{n+1}2 \tag{2}
E(X)=i=1∑ni⋅n1=2n+1(2)
所以平均情况下为
Θ
\Theta
Θ(n)
在最坏情况下,输入元素需要与数组中所有元素比较,所以其时间复杂度也是为
Θ
\Theta
Θ(n)。
练习2.2-4
略…
2.3 设计算法
MERGE-SORT
Python
def merge(A,p,q,r):
maxA=max(A);
lenLeft=q-p+1;
lenRight=r-q;
leftArray=[];
rightArray=[];
for i in range(lenLeft):
leftArray.append(A[p+i]);
for i in range(lenRight):
rightArray.append(A[q+i+1]);
leftArray.append(maxA+100);
rightArray.append(maxA+100);
i=j=0;
for k in range(r-p+1):
if leftArray[i]<=rightArray[j]:
A[p+k]=leftArray[i];
i=i+1;
else:
A[p+k]=rightArray[j];
j=j+1;
print(A)
return A
def sortMerge(A,p,r):
q=int((r+p)/2);
if r>p:
sortMerge(A,p,q);
sortMerge(A,q+1,r);
A=merge(A,p,q,r);
return A;
if __name__=='__main__':
A=[2,1,5,3,6,4];
print(sortMerge(A,0,len(A)-1));
C++
#include<iostream>
#include<vector>
using namespace std;
void print (vector <double> a)
{
for (auto beg=a.begin(),end=a.end();beg!=end;beg++)
{
if (beg!=end-1)
{
cout<<*beg<<",";
}
else
{
cout<<(*beg)<<endl;
}
}
}
void Merge(vector<double>& A,int p,int q,int r){
if (p>q||q>r){
cout<<"Please enter parameters as ascending."<<endl;
return;
}
int n1=q-p+1;
int n2=r-q;
vector<double> L,R;
for (int i=0;i<n1;i++){
L.push_back(A[p-1+i]);
}
for (int i=0;i<n2;i++){
R.push_back(A[q-1+i+1]);
}
int ii=0,ij=0,c=1000;
L.push_back(c);
R.push_back(c);
for (int i=p-1;i<r;i++){
if (R[ii]<L[ij]){
A[i]=R[ii];
ii++;
}else{
A[i]=L[ij];
ij++;
}
}
}
//If you want to sort vector A, you should put (A,0,A.size()-1) \
//in function MergeSort.
void MergeSort(vector<double>& A,int p,int r){
if (r>p){
int q=(r+p)/2;
MergeSort(A,p,q);
MergeSort(A,q+1,r);
Merge(A,p,q,r);
}
}
练习 2.3-1
过程如下图:
练习 2.3-2
程序如下:
#include<iostream>
#include<vector>
using namespace std;
void Merge(vector<double>& A,int p,int q,int r){
if (p>q||q>r){
cout<<"Please enter parameters as ascending."<<endl;
return;
}
int n1=q-p+1;
int n2=r-q;
vector<double> L,R;
for (int i=0;i<n1;i++){
L.push_back(A[p-1+i]);
}
for (int i=0;i<n2;i++){
R.push_back(A[q-1+i+1]);
}
int ii=0,ij=0;
for (int i=p-1;i<r;i++){
if ((R[ii]<L[ij]||ij>L.size()-1)&&ii<R.size()){
A[i]=R[ii];
ii++;
}else{
A[i]=L[ij];
ij++;
}
}
}
void MergeSort(vector<double>& A,int p,int r){
if (r>p){
int q=(r+p)/2;
MergeSort(A,p,q);
MergeSort(A,q+1,r);
Merge(A,p,q,r);
}
}
练习2.3-3
略
练习2.3-4
略
练习2.3-5
注意输入必须是个已经排序好的数组。
#include<vector>
#include<iostream>
using namespace std;
void find(const vector<double>input,int begin,int end,const double f){
int half=(begin+end)/2;
if (input[half]==f){
cout<<"Find "<<f<<" in vector."<<endl;
return ;
}else if (end-begin<2){
cerr<<"Not Find"<<endl;
return ;
}else if (input[half]<f){
find(input,half,end,f);
}else if (input[half]>f){
find(input,begin,half,f);
}
}
因为采用分治方法 T ( n ) = T ( n 2 ) + Θ ( 1 ) T(n)=T(\frac {n}2)+\Theta(1) T(n)=T(2n)+Θ(1),很容易证明最坏运行时间复杂度为 Θ ( l o g 2 ( n ) ) \Theta (log_2(n)) Θ(log2(n))。
练习2.3-6
是不可以的。在最坏情况下,即使使用find()函数找到了一个未插入的元素在已经排好的数组中的位置,要使得这个元素插入数组中需要的时间复杂度是 Θ ( n ) \Theta (n) Θ(n),所以总的时间复杂度为 Θ ( n ( n + l o g 2 ( n ) ) ) \Theta(n(n+log_2(n))) Θ(n(n+log2(n)))
练习2.3-7
需要上面的MERGE-SORT中的两个函数Merge和MergeSort,就不贴在下面了。
#include<vector>
#include<iostream>
using namespace std;
int find(const vector<double>input,int begin,int end,const double f){
int half=(begin+end)/2;
int output=0;
if (input[half]==f){
output=half;
return output;
}else if (end-begin<2){
output=begin;
return output;
}else if (input[half]<f){
output=find(input,half,end,f);
}else if (input[half]>f){
output=find(input,begin,half,f);
}
return output;
}
void Match(const vector<double>input,int begin,int end,const double f){
for (int i=begin,j=end;i<j;){
double temp=input[i]+input[j];
if (temp==f){
cout<<"Find "<<f<<" is sum of "<<input[i]<<" and "<<input[j]
<<"."<<endl;
return;
}else if(temp>f){
j--;
}else if(temp<f){
i++;
}
}
cout<<"No match."<<endl;
}
int main(int argc,char** argv){
vector<double> a={12,3,423,546,123,633456,12312};
int f=125;
MergeSort(a,0,a.size()-1);
int pos=find(a,0,a.size()-1,f);
Match(a,0,pos,f);
return 0;
}
上面算法分析:
总的时间复杂度=MergeSort的时间复杂度+find的时间复杂度+Match的时间复杂度。
前面的分析可以知道:
MergeSort的时间复杂度=
Θ
(
n
l
o
g
2
(
n
)
)
\Theta (nlog_2(n))
Θ(nlog2(n));
find的时间复杂度=
Θ
(
l
o
g
2
(
n
)
)
\Theta (log_2(n))
Θ(log2(n));
显然Match的时间复杂度==
Θ
(
n
)
\Theta (n)
Θ(n);
所以总的时间复杂度=
Θ
(
n
l
o
g
2
(
n
)
)
\Theta (nlog_2(n))
Θ(nlog2(n))。
思考题
思考题2-1
略
思考题2-2
略
思考题2-3
略
思考题2-4
2-4.d
改下MergeSort即可。
#include <iostream>
#include <vector>
using namespace std;
void Merge(vector<double>& A,int p,int q,int r,int&count){
int n1=q-p+1;
int n2=r-q;
vector<double> L,R;
for (int i=0;i<n1;i++){
L.push_back(A[p-1+i]);
}
for (int i=0;i<n2;i++){
R.push_back(A[q-1+i+1]);
}
int ii=0,ij=0,cnt=0;
for (int i=p-1;i<r;i++){
if (ij>L.size()-1&&ii<R.size()){
A[i]=R[ii];
ii++;
}else if(ij<L.size()&&ii==R.size()){
A[i]=L[ij];
ij++;
count+=ii;
}else if (L[ij]>R[ii]){
A[i]=R[ii];
ii++;
cnt++;
}else if (L[ij]<=R[ii]){
count+=cnt;
A[i]=L[ij];
ij++;
}
}
}
int CountReverse(vector<double>& A,int p,int r){
int count=0;
if (r>p){
int q=(r+p)/2;
CountReverse(A,p,q);
CountReverse(A,q+1,r);
Merge(A,p,q,r,count);
}
return count;
}
int main(int argc,char**){
vector<double> a={3,4,6,8,1,2,5,7};
int count=CountReverse(a,1,a.size());
cout<<count<<endl;
return 0;
}