参考自http://blog.csdn.net/cclsoft/article/details/5467743
循环字符串的最小表示法的问题可以这样描述:
对于一个字符串S,求S的循环的同构字符串S’中字典序最小的一个。
由于语言能力有限,还是用实际例子来解释比较容易:
设S=bcad,且S’是S的循环同构的串。S’可以是bcad或者cadb,adbc,dbca。而且最小表示的S’是adbc。
(1) 对于字符串循环同构的最小表示法,其问题实质是求S串的一个位置,从这个位置开始循环输出S,得到的S’字典序最小。
一种朴素的方法是设计i,j两个指针。其中i指向最小表示的位置,j作为比较指针。 令i=0,j=1
如果S[i] > S[j] i=j, j=i+1
如果S[i] < S[j] j++
如果S[i]==S[j] 设指针k,分别从i和j位置向下比较,直到S[i] != S[j]
如果S[i+k] > S[j+k] i=j,j=i+1
否则j++
返回i
起初,我想在j指针后移的过程中加入一个优化。就是j每次不是加1,而是移动到l位置。其中,l>j且S[l]<=S[j]。但是,即使加入 这一优化,在遇到bbb…bbbbbba这样的字符串时复杂度将退化到O(n^2)。
(2) 注意到,朴素算法的缺陷在于斜体的情况下i指针的移动太少了。针对这一问题改进就得到了最小表示法的算法。最小表示法的算法思路是维护两个指针i,j。
令i=0,j=1
如果S[i] > S[j] i=j, j=i+1
如果S[i] < S[j] j++
如果S[i]==S[j] 设指针k,分别从i和j位置向下比较,直到S[i] != S[j]
如果S[i+k] > S[j+k] i=i+k
否则j++
返回i和j的小者
注意到上面两个算法唯一的区别是粗体的一行。这一行就把复杂度降到O(n)了。
这里证明一下为什么 S[i+k] > S[j+k] i=i+k ,
当s[i+k] > s[j+k] 时 i = i+k+1;
这个证明可以转化为证明 从 i ~ i+k 开始 到 i+k结束的前缀 一定大于某个t ( t 在 j~j+k 内 ) 开始的前缀 , 接下来开始证明。
从i ~ i +k 上取一个 ti ,对于 S[ ti ..i+k ] , 这一个字串,显然可以在S[j ....j+k] 上找到一个更小的字串.
例如字符串dbcdbcdbce来说
0 1 2 3 4 5 6 7 8 9
d b c d b c d b c a i = 0 , i + k = 6
d b c d b c a d b c j = 3 , j + k = 9
显然0 ~ 5 上面的子串,一定小于下面的对应位置的子串
所以上式成立
值得一提的是,与KMP类似,最小表示法处理的是一个字符串S的性质,而不是看论文时给人感觉的处理两个字符串。
应用最小表示法判断两个字符串同构,只要将两个串的最小表示求出来,然后从最小表示开始比较。剩下的工作就不用多说了。
#include <bits/stdc++.h>
using namespace std;
int MinimumRepresentation(char *s,int len){
int i=0,j=1,k=0; //i和j是两个进行比较的起始匹配位点,k是匹配长度
while( i < len && j < len && k < len ){
int t = s[(i+k)%len] - s[(j+k)%len];//比较两个串的大小关系
if( t == 0 ) k++;//如果相同,匹配长度增大,比较位置向移
else {//如果不同,则字典序大的位置肯定不会是答案,改变那个匹配位点
if(t>0) i += k+1;
else j += k+1;
if( i == j ) j++;//i和j一定要错开
k = 0;//匹配长度要重置为0
}
}
return i < j ? i : j ;//因为字典序大的位置被后移了,所以较小的位置就是答案
}