![7316a3f516eb75c6df6639fab6720e71.png](https://img-blog.csdnimg.cn/img_convert/7316a3f516eb75c6df6639fab6720e71.png)
文/yyc
在计算机科学领域,我们常常将问题划分为多个种类,其中最为著名的莫过于P问题、NP问题、NPC问题。
A. Polynomial问题(P问题)
Polynomial在英语里意为“多项式的”,一言以蔽之:能被确定性机具用多项式算法解决的判定问题。
P问题具有以下几种特质:
- 在做替换时,问题仍具有多项式性。
- 相对容易地计算出总步数。
- 答案是与不是是对称的。
在经典算法中,很多问题问题都能通过具有多项式时间复杂度的解法算出,例如非常基础的最短路问题。
题目描述
给出一个有向图,请输出从任意两点间的最短路径长度。 代码实现(c++)//n代表图中点的数目
dis[i][j]代表i,j两点间的最短路径长度
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
代码展示了弗洛伊德(Floyd)算法的实现过程。我们可以清楚地认识到这个算法的时间复杂度是稳定的O(n^3)而空间复杂度是稳定的O(n^2),也就是这个算法的时间和空间复杂度是可以由问题中给出的条件n(即有向图的节点个数)确定。所以,这个问题可以用用具有多项式时间和空间复杂度的算法完成,那么它就是P问题。
B. NP-complete问题 (NPC问题)
NPC问题,也称NP完全问题,是经过证明的不能用多项式时间复杂度解决的问题。NPC问题能够通过多项式时间复杂度的算法进行检验而无法通过多项式时间复杂度的算法转化为P问题,故NPC问题常常为最为困难的一类问题。
著名的“八皇后问题”就是NPC问题。
题目描述
在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 代码实现(c++)void search(int i) { //采用递归进行搜索
for (int j=1;j<=n;j++)
if((!b[j])&&(!c[i+j])&&(!d[i-j+n-1])) {
a[i]=j; b[j]=1;
c[i+j]=1; d[i-j+n-1]=1;
if (i==n) print(); //输出结果,递归出口
else search(i+1); //进行下一层搜索
a[i]=0; b[j]=0; //回溯步骤
c[i+j]=0; d[i-j+n-1]=0;
}
}
八皇后问题是由国际象棋棋手马克斯·贝瑟尔于1848年提出的问题,本样例中展示的其实是“n皇后问题”。高斯曾尝试使用多项式时间复杂度的方法进行求解,但无果而终,后人证明这是一道“NPC问题”。在计算机出现后,这个问题得到了很好的解答。
在代码实现中,我们运用了回溯搜索的方法。实际上,“搜索算法”很大程度上就是一种“死算&尝试”的算法。反观“八皇后问题”,我们可以运用O(n)的方法对结果进行检验(即确认n个皇后不能互相攻击,但样例代码在枚举时就已事先进行了判断,所以并未出现检验的代码片段),可以用不可知时间复杂度的算法进行枚举(因为对于每种枚举方式时间复杂度都可能不同),所以是一个典型的NPC问题。
C. Non-Deterministic Polynomial问题(NP问题)
一言以蔽之:能被非确定性机在多项式时间复杂度内解决的判定性问题(或:能在多项式时间复杂度内进行验证的一类问题。)。可以看出,NPC问题其实是NP问题的一个子集。
有些NP问题可以转化为P问题;有些NP问题可已经证明为NPC问题;然而还有一些NP问题既无法被证明可转化为P问题,也无法经证明为NPC问题。
例如大家所熟知的行列式求值问题。
题目描述
给出一个n阶行列式,求其值。 代码实现1(c++)int Det_solve(Det a) {
int n=a.n,w=-1,ans=0;
if(n==1) return a.a[1][1];
for(int i=1;i<=n;i++) {
w=w*-1;
Det b; b.n=n-1;
for(int j=2;j<=n;j++) {
for(int k=1;k<=i;k++) b.a[j-1][k]=a.a[j][k];
for(int k=i+1;k<=n;k++) b.a[j-1][k-1]=a.a[j][k];
}
ans=ans+w*a.a[1][i]*Det_solve(b); }
return ans;
}
这里运用了搜索的求解方法。对于不同的数据,该算法的时间和空间复杂度很难通过给定的值通过多项式时间复杂度计算得出,故可以说明这是一个NP问题。
代码实现2(c++)int Gauss(){
int r; fs f,ans;
ans.uup=1; ans.dwn=1;
int n=e.n;
for(int i=0;i<n;i++) {
r=i;
for(int j=i+1;j<n;j++)
if(fabs(e.a[j][i])>fabs(e.a[r][i])) r=j;
if(fabs(e.a[r][i])<=eps) { return 0; }
if(r!=i) for(int j=0;j<=n;j++) {
fs tmp; tmp.uup=-1; tmp.dwn=1;
swap(e.a[r][j],e.a[i][j]); ans=ans*tmp;
}
for(int k=i+1;k<n;k++){
f=e.a[k][i]/e.a[i][i];
for(int j=0;j<=n;j++) {
e.a[k][j]=e.a[k][j]-f*e.a[i][j];
}
}
} //构造出行阶梯形矩阵
for(int i=n-1;i>=0;i--) {
f=e.a[i][i];
for(int j=0;j<=n;j++) e.a[i][j]=e.a[i][j]/f; ans=ans*f;
for(int j=i-1;j>=0;j--) {
f=e.a[j][i];
for(int k=0;k<=n;k++) {
e.a[j][k]=e.a[j][k]-e.a[i][k]*f;
}
}
} //转化为单位矩阵
fout(ans);
return 1;
}
以上代码通过高斯消元进行初等矩阵变换来计算出行列式的值。我们不难计算出这个算法的时间复杂度是O(n^3)和O(n^2),和高斯消元相同,也就是说,这个问题可以转化为P问题。
D.总结
p问题,np问题和npc问题的划分给出了一个实现算法的依据。事实上,按照“越快越好”的原则,不具有多项式时间复杂度的算法有时有优于具有确定时间复杂度的算法。
所以究竟孰优孰劣?我想各位还是仁者见仁,智者见智吧。