字符串相似度的計算(百度筆試題回憶)


題目

這道題應該說很出名了,原題見字符串相似度的計算,但是考試的時候真的想不出怎么實現。看了解答方法后,我現在就把實現方法說一下:

如果僅僅只計算字符串的距離,則只需以下3個步驟

image

如果需要把字符串轉變的過程記錄下來,則需要6個步驟

image

粗略解法

下面我就先實現只計算字符串距離的代碼,使用了模板。這種方法雖然可以計算出結果來,但是重復計算非常多,后面會有個對比的。

//字符串相似度的計算,模板實現,可用於其他容器
#include <string>
#include <iostream>
using namespace std;

template<typename Iterator>
int caculateDistance(Iterator pAbegin, Iterator pAend, Iterator pBbegin, Iterator pBend)
{
    if (pAbegin>pAend)
    {
        if (pBbegin>pBend)
            return 0;
        else
            return pBend-pBbegin+1;
    }
    if (pBbegin>pBend)
    {
        if (pAbegin>pAend)
            return 0;
        else
            return pAend-pAbegin+1;
    }
    if (*pAbegin == *pBbegin)
    {
        return caculateDistance(pAbegin+1,pAend,pBbegin+1,pBend);
    }
    else
    {
        int t1 = caculateDistance(pAbegin+1,pAend,pBbegin+2,pBend);
        int t2 = caculateDistance(pAbegin+2,pAend,pBbegin+1,pBend);
        int t3 = caculateDistance(pAbegin+2,pAend,pBbegin+2,pBend);
        int minValue = t1>t2?t2:t1;
        minValue = minValue>t3?t3:minValue;
        return minValue+1;
    }
}

int main()
{
    string A = "abcdefghijklmn";
    string B = "cdefghijklmn";
    cout << caculateDistance(A.begin(),A.end(),B.begin(),B.end()) << endl;
    return 0;
}

解法改進

考慮到有些數據被重復計算的情況(字符串相似度的計算中實現了存在數據重復計算的情況的代碼),就把子問題的結果保存起來以備后查。

歷史記錄結構體

結構體定義如下:

//存儲歷史記錄的結構體
template<typename Iterator>
struct DataForHistory
{
    DataForHistory(Iterator iAbegin, Iterator iAend, Iterator iBbegin, Iterator iBend)
    :pAbegin(iAbegin), pAend(iAend), pBbegin(iBbegin), pBend(iBend), result(0)
    {
    }
    bool operator==(const DataForHistory& rh)
    {
        if (pAbegin == rh.pAbegin
        && pAend == rh.pAend
        && pBbegin == rh.pBbegin
        && pBend == rh.pBend)
            return true;
        else
            return false;
    }
    void set(Iterator iAbegin, Iterator iAend, Iterator iBbegin, Iterator iBend)
    {
        pAbegin = iAbegin;
        pAend = iAend;
        pBbegin = iBbegin;
        pBend = iBend;
    }
    Iterator pAbegin;
    Iterator pAend;
    Iterator pBbegin;
    Iterator pBend;
    int result;//存儲計算的結果
};

自定義查找歷史記錄函數

上面是一個保存歷史記錄的結構體,包含四個迭代器和一個與根據四個迭代器計算出來的字符串距離result。下面我自己定義了一個find()函數用於查找即將需要的記錄是否在歷史記錄中:

//查找歷史記錄中是否存在data
template<typename Iterator>
int find(vector<DataForHistory<Iterator> >& history, DataForHistory<Iterator>& data)
{
    int k=-1;
    for (int i=0; i<history.size(); i++)
    {
        if (data == history[i])
        {
            k = i;
            break;
        }
    }
    return k;
}

遞歸決策函數

從而就可以定義一個函數來“決策是否需要遞歸計算,如果data已經存在就不再遞歸計算而直接返回歷史記錄中保存的數據;如果歷史記錄中找不到,則遞歸計算出數據,然后push到歷史記錄中”,實現代碼如下:

template<typename Iterator>
int findOrCaculate(vector<DataForHistory<Iterator> >& history, Iterator pAbegin, Iterator pAend, Iterator pBbegin, Iterator pBend)
{
    DataForHistory<Iterator> data(pAbegin,pAend,pBbegin,pBend);
    int i = find(history,data);
    if (i != -1)
//    if (false)    //不查找歷史記錄,對每一次需求都重新遞歸計算
    {
        return history[i].result;
    }
    else
    {
        data.result = caculateDistance(pAbegin,pAend,pBbegin,pBend);
        history.push_back(data);
        //cout << data.pAbegin << ", " << data.pAend << ", "
        //     << data.pBbegin << ", " << data.pBend << ". "
        //cout << data.result << endl;
        return data.result;
    }
}

上面代碼中有一個注釋了的if(false)是用來測試不使用查找歷史記錄而是每出現一次需要計算的時候直接遞歸計算時所需的遞歸次數,我測試的結果如下:

使用查找歷史數據:image

不是用查找歷史數據:image

這樣一看相差忒大了,所以使用查找數據是很明智的選擇(即使查找需要話費時間,但相對於重復進行相同的遞歸計算還是很不錯的)。

PS

上文我使用了自定義的find函數,是由於中途寫代碼時使用<algorithm>中的find()函數是出現了一大堆的錯誤,結果沒找到錯誤的原因,模板嘛,一出現錯誤總是一大堆的。其實到我就找到錯誤的根源了,錯誤代碼:

template<typename Iterator>
int findOrCaculate(vector<DataForHistory<Iterator> >& history, Iterator pAbegin, Iterator pAend, Iterator pBbegin, Iterator pBend)
{
    DataForHistory<Iterator> data(pAbegin,pAend,pBbegin,pBend);
    vector<DataForHistory<Iterator> >::iterator it = find(history.begin(),history.end(),data);
    if (it)
    {
        return it->result;
    }
    else
    {
        data.result = caculateDistance(pAbegin,pAend,pBbegin,pBend);
        history.push_back(data);
        return data.result;
    }
}

錯誤提示:

image

like.cpp:69:2: error: need 'typename' before 'std::vector<DataForHistory<Iterator> >::iterator' beca
use 'std::vector<DataForHistory<Iterator> >' is a dependent scope

所以我就在'std::vector<DataForHistory<Iterator> >::iterator'之前加了typename。(這里不清楚為什么?)

然后繼續編譯,還有錯:

image

like.cpp:70:2: error: could not convert 'it' from 'std::vector<DataForHistory<__gnu_cxx::__normal_it
erator<char*, std::basic_string<char> > >, std::allocator<DataForHistory<__gnu_cxx::__normal_iterato
r<char*, std::basic_string<char> > > > >::iterator {aka __gnu_cxx::__normal_iterator<DataForHistory<
__gnu_cxx::__normal_iterator<char*, std::basic_string<char> > >*, std::vector<DataForHistory<__gnu_c
xx::__normal_iterator<char*, std::basic_string<char> > >, std::allocator<DataForHistory<__gnu_cxx::_
_normal_iterator<char*, std::basic_string<char> > > > > >}' to 'bool'

看到上面的from ‘xxx’ to ‘bool’了,這下知道了it不是bool型的,怎么判斷,需要改為

if (it != history.end())

即找到最后了還是沒找到。這樣就解決了find函數的問題,就可以不用自己定義專門的find函數了,正確代碼如下:

//決策是否遞歸計算,如果data已經存在就不再遞歸計算而直接返回歷史記錄中保存的數據
//如果歷史記錄中找不到,則遞歸計算出數據,然后push到歷史記錄中
template<typename Iterator>
int findOrCaculate(vector<DataForHistory<Iterator> >& history, Iterator pAbegin, Iterator pAend, Iterator pBbegin, Iterator pBend)
{
    DataForHistory<Iterator> data(pAbegin,pAend,pBbegin,pBend);
    typename vector<DataForHistory<Iterator> >::iterator it = find(history.begin(),history.end(),data);
    if (it != history.end())
    {
        return it->result;
    }
    else
    {
        data.result = caculateDistance(pAbegin,pAend,pBbegin,pBend);
        history.push_back(data);
        return data.result;
    }
}

遞歸函數

接下來就是怎么寫遞歸函數了,形式上和前面的遞歸函數差不多,只是把需要繼續使用遞歸函數的時候不是直接調用遞歸函數,而采用簡潔調用。改為調用findOrCaulate()。

template<typename Iterator>
int caculateDistance(Iterator pAbegin, Iterator pAend, Iterator pBbegin, Iterator pBend)
{
    static vector<DataForHistory<Iterator> > history;
    cout << "call caculateDistance() times:"<< history.size() << endl;
    if (pAbegin>pAend)
    {
        if (pBbegin>pBend)
            return 0;
        else
            return pBend-pBbegin+1;
    }
    if (pBbegin>pBend)
    {
        if (pAbegin>pAend)
            return 0;
        else
            return pAend-pAbegin+1;
    }
    if (*pAbegin == *pBbegin)
    {
        findOrCaculate(history,pAbegin+1,pAend,pBbegin+1,pBend);
    }
    else
    {
        int t1,t2,t3;
        t1 = findOrCaculate(history,pAbegin+1,pAend,pBbegin+2,pBend);
        t2 = findOrCaculate(history,pAbegin+2,pAend,pBbegin+1,pBend);
        t3 = findOrCaculate(history,pAbegin+2,pAend,pBbegin+2,pBend);
        int minValue = t1>t2?t2:t1;
        minValue = minValue>t3?t3:minValue;
        return minValue+1;
    }
}

更好的解法

中文譯文:一個快速、高效的Levenshtein算法實現

英文原文:http://www.codeproject.com/Articles/13525/Fast-memory-efficient-Levenshtein-algorithm

C++代碼實現:

//Levenshtein算法計算兩字符串的編輯距離

#include <algorithm>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

int Levenshtein(string& s, string& t)
{
    //第一步
    int n = s.size();
    int m = t.size();
    if (0 == n) return m;
    else if (0 == m) return n;
    
    vector<int> v0(m+1);
    vector<int> v1(m+1);
    
    //第二步
    for (int i=0; i<=m; i++)
        v0[i] = i;

    //第三四步
    int cost=0;//編輯代價
    for (int i=1; i<=n; i++)
    {
        v1[0] = i;
        for (int j=1; j<=m; j++)
        {
            //第五步
            if (s[i-1] == t[j-1])
            {
                cost=0;
            }
            else
            {
                cost=1;
            }

            //第六步
            int min = v0[j] + 1;
            int b = v1[j-1] + 1;
            int c = v0[j-1] + cost;
            min = min>b?b:min;
            min = min>c?c:min;
            v1[j] = min;
        }
        copy(v1.begin(),v1.end(),v0.begin());
    }
    //第七步
    return v0[m];
}

int main()
{
    string s;
    string t;
    string str;
    fstream f("t.c");
    getline(f,s,'\1');
    f.close();
    f.open("tt.c");
    getline(f,t,'\1');
    f.close();
    cout << s << endl;
    cout << t << endl;
    cout << Levenshtein(t,s) << endl;
}
image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM