C++變位詞問(wèn)題分析
在《編程珠璣》一書(shū)的第二章提到了一個(gè)變位詞問(wèn)題,變位詞指的是一個(gè)單詞可以通過(guò)改變其他單詞中字母的順序來(lái)得到,也叫做兄弟單詞,如army->mary。由變位詞可以引申出幾個(gè)算法問(wèn)題,包括字符串包含問(wèn)題,比較兩個(gè)字符串是否是變位詞,以及找出字典中變位詞集合的問(wèn)題。
一、字符串包含問(wèn)題
(1) 問(wèn)題描述:存在字符串1和字符串2,假設(shè)字符串2相對(duì)較短,如何快速地判定字符串2中的字符都存在于字符串1里(假定字符串只包含字母)?
(2) 舉例:字符串1為ABCDEFGHIJK,字符串2為ABCDE,則字符串1包含字符串2,因?yàn)樽址?中包含的字母在字符串1中也都有。
(3) 解決方案:
思路一
最直接的思路就是針對(duì)字符串2中的每個(gè)字符,輪詢字符串1進(jìn)行比較,看是否在字符串1里面。很明顯這種的時(shí)間效率為O(n*m)。
/*************************************************************************
> File Name: test.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<string>
using namespace std;
void Compare(string long_str, string short_str)
{
int i,j;
for(i=0; i<short_str.size(); ++i)
{
for(j=0; j<long_str.size(); ++j)
{
if(long_str[j] == short_str[i])
{
break;
}
}
if(j == long_str.size())
{
cout << "false" << endl;
return;
}
}
cout << "true" << endl;
return;
}
int main()
{
string l = "ABCDEFGHIJK";
string s = "ABCDEF";
Compare(l, s);
return 0;
}
思路二
這里由于假定了字符串只包含字母,所以我們可以用一個(gè)額外的數(shù)組flag[26]作為26個(gè)字符標(biāo)識(shí)位,先遍歷長(zhǎng)字符串將對(duì)應(yīng)的標(biāo)識(shí)位置1,再遍歷短字符串,如果對(duì)應(yīng)的標(biāo)示位都是1,則包含;否則不包含。這種方法的時(shí)間復(fù)雜度為O(n+m),為了提高空間效率,這里不使用數(shù)組而使用26個(gè)bit位來(lái)作為標(biāo)示位(bitset容器)。
/*************************************************************************
> File Name: test1.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<bitset>
#include<string>
using namespace std;
bool Compare(string long_str, string short_str)
{
bitset<26> flag;
for(int i=0; i<long_str.size(); ++i)
{
// flag.set(n)置第n位為1
flag.set(long_str[i]-'A');
}
for(int i=0; i<short_str.size(); ++i)
{
// flag.test(n)判斷第n位是否為1
if(!flag.test(short_str[i]-'A'))
return false;
}
return true;
}
int main()
{
string l = "ABCDEFGHIJK";
string s = "ABCDEZ";
if(Compare(l, s))
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
這種方法還可以進(jìn)行優(yōu)化,例如如果長(zhǎng)字串的前綴就為短字串,那么我們可以不需要n+m次,而只需要2m次。具體實(shí)現(xiàn)請(qǐng)自己思考。
思路三
給每個(gè)字母分配一個(gè)素?cái)?shù),從2開(kāi)始到3,5,7...遍歷長(zhǎng)字串,求得每個(gè)字符對(duì)應(yīng)素?cái)?shù)的乘積。然后遍歷短字串,判斷該乘積能否被短字符串中的字符對(duì)應(yīng)的素?cái)?shù)整除,如果除的結(jié)果存在余數(shù),則說(shuō)明有不匹配的字母;如果整個(gè)過(guò)程都沒(méi)有余數(shù),則說(shuō)明短字串中的字母在長(zhǎng)字串里都有。這種方法的時(shí)間復(fù)雜度也是O(n+m),需要26個(gè)額外空間存素?cái)?shù),但是這種方法有一個(gè)缺點(diǎn)就是需要跟大整數(shù)打交道,因?yàn)槌朔e可能非常大。(這里我們使用<cstdint>頭文件中定義的int64_t和uint64_t)
/*************************************************************************
> File Name: test2.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<string>
#include<stdint.h>
//#include<cstdint> // C++11
using namespace std;
bool Compare(string long_str, string short_str)
{
unsigned int primeNum[26] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,
53,59,61,67,71,73,79,83,89,97,101};
/* int64_t和uint64_t分別表示64位的有符號(hào)和無(wú)符號(hào)整形數(shù) */
/* 在不同位數(shù)機(jī)器的平臺(tái)下通用,都是64位 */
uint64_t ch = 1;
for(int i=0; i<long_str.size(); ++i)
{
ch = ch*primeNum[long_str[i]-'A'];
}
for(int i=0; i<short_str.size(); ++i)
{
if(ch%primeNum[short_str[i]-'A'] != 0)
return false;
}
return true;
}
int main()
{
string l = "ABCDEFGHIJK";
string s = "ABCDEK";
if(Compare(l, s))
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
二、比較兩個(gè)字符串是否為變位詞
(1) 問(wèn)題描述:如果兩個(gè)字符串的字符一樣,但是順序不一樣,被認(rèn)為是兄弟字符串,問(wèn)如何在迅速匹配兄弟字符串(如,bad和adb就是兄弟字符串)。
(2) 注意:第一點(diǎn)中討論了字符串包含問(wèn)題,但是不要以為兩個(gè)字符串互相包含就是(變位詞)兄弟字符串了,例如aabcde和edcba互相包含,但它們不是變位詞。
(3) 解決方案:
思路一
給每個(gè)字母分配一個(gè)素?cái)?shù),可以通過(guò)判斷兩個(gè)字符串的素?cái)?shù)乘積是否相等。跟上述素?cái)?shù)法一樣,時(shí)間復(fù)雜度也是O(n+m),需要跟大整數(shù)打交道。
/*************************************************************************
> File Name: test3.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<string>
#include<stdint.h>
//#include<cstdint> // C++11
using namespace std;
bool Compare(string s1, string s2)
{
unsigned int primeNum[26] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,
53,59,61,67,71,73,79,83,89,97,101};
uint64_t ch = 1;
for(int i=0; i<s1.size(); ++i)
{
ch = ch*primeNum[s1[i]-'a'];
}
for(int i=0; i<s2.size(); ++i)
{
ch = ch/primeNum[s2[i]-'a'];
}
if(ch == 1)
return true;
else
return false;
}
int main()
{
string s1 = "abandon";
string s2 = "banadon";
if(Compare(s1, s2))
cout << "They are brother words!" << endl;
else
cout << "They aren't brother words!" << endl;
return 0;
}
思路二
將兩個(gè)字符串按照字母表順序排序,看排序后的字符串是否相等,如果相等則是兄弟字符串(變位詞)。這種方法的時(shí)間效率根據(jù)你使用的排序算法不同而不同。當(dāng)然,你可以自己寫(xiě)排序算法,這里我們使用C++的STL中的sort()函數(shù)對(duì)字符串進(jìn)行排序。
/*************************************************************************
> File Name: test4.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
// 自定義序函數(shù)(二元謂詞)
bool myfunction(char i, char j)
{
return i > j;
}
bool Compare(string s1, string s2)
{
// 采用泛型算法對(duì)s1,s2排序,sort()采用的是快速排序算法
sort(s1.begin(), s1.end(), myfunction);
sort(s2.begin(), s2.end(), myfunction);
if(!s1.compare(s2)) // 相等返回0
return true;
else
return false;
}
int main()
{
string s1 = "abandon";
string s2 = "banadon";
if(Compare(s1, s2))
cout << "They are brother words!" << endl;
else
cout << "They aren't brother words!" << endl;
return 0;
}
三、字典找出所有變位詞集合(重點(diǎn))
(1) 問(wèn)題描述:給定一個(gè)英語(yǔ)字典,找出其中的所有變位詞集合。
(2) 解決方案:
思路一
對(duì)于這個(gè)問(wèn)題,最快想到的最直接的方法就是針對(duì)每一個(gè)單詞跟字典中的其他單詞進(jìn)行比較。然而,假設(shè)一次比較至少花費(fèi)1微秒的時(shí)間,則擁有二十萬(wàn)單詞的字典將花費(fèi):200000單詞 x 200000比較/單詞 x 1微秒/比較 = 40000x10^6秒 = 40000秒 ≈ 11.1小時(shí)。比較的次數(shù)太多了,導(dǎo)致效率低下,我們需要找出效率更高的方法。
思路二
標(biāo)識(shí)字典中的每一個(gè)單詞,使得在相同變位詞類中的單詞具有相同的的標(biāo)識(shí),然后集中具有相同標(biāo)識(shí)的單詞。將每個(gè)單詞按照字母表排序,排序后得到的字符串作為該單詞的標(biāo)識(shí)。那么對(duì)于該問(wèn)題的解題過(guò)程可以分為三步:第一步,讀入字典文件,對(duì)單詞進(jìn)行排序得到標(biāo)識(shí);第二步,將所有的單詞按照其標(biāo)識(shí)的順序排序;第三步,將同一個(gè)變位詞類中的各個(gè)單詞放到同一行中。
這里出現(xiàn)了標(biāo)識(shí)-單詞(key-value)對(duì),我們很容易想到C++中的關(guān)聯(lián)容器map,使用map的好處就是:
動(dòng)態(tài)管理內(nèi)存,容器大小動(dòng)態(tài)改變;
單詞與它的標(biāo)識(shí)一一對(duì)應(yīng),對(duì)于相同標(biāo)識(shí)(key)的單詞直接加在值(value)后面;
無(wú)需根據(jù)標(biāo)識(shí)排序,因?yàn)閙ap會(huì)自動(dòng)按關(guān)鍵字有序(默認(rèn)升序)。
所以,在將每個(gè)單詞及其標(biāo)識(shí)存入map以后,就可以直接遍歷輸出了,每一個(gè)map元素就是一個(gè)變位詞集合。
C++實(shí)現(xiàn)代碼如下:
/*************************************************************************
> File Name: test5.cpp
> Author: SongLee
************************************************************************/
#include<iostream>
#include<fstream> // file I/O
#include<map> // map
#include<string> // string
#include<algorithm> // sort
using namespace std;
/*
*map是C++中的關(guān)聯(lián)容器
* 按關(guān)鍵字有序
* 關(guān)鍵字不可重復(fù)
*/
map<string, string> word;
/* 自定義比較函數(shù)(用于排序) */
bool myfunction(char i, char j)
{
return i < j;
}
/*
*對(duì)每個(gè)單詞排序
*排序后字符串作為關(guān)鍵字,原單詞作為值
*存入map中
*/
void sign_sort(const char* dic)
{
// 文件流
ifstream in(dic);
if(!in)
{
cout << "Couldn't open file: " + string(dic) << endl;
return;
}
string aword;
string asign;
while(in >> aword)
{
asign = aword;
sort(asign.begin(), asign.end(), myfunction);
// 若標(biāo)識(shí)不存在,創(chuàng)建一個(gè)新map元素,若存在,加在值后面
word[asign] += aword + " ";
}
in.close();
}
/*
*寫(xiě)入輸出文件
*/
void write_file(const char* file)
{
ofstream out(file);
if(!out)
{
cout << "Couldn't create file: " + string(file) << endl;
return;
}
map<string, string>::iterator begin = word.begin();
map<string, string>::iterator end = word.end();
while(begin != end)
{
out << begin->second << "\n";
++begin;
}
out.close();
}
int main()
{
string dic;
string outfile;
cout << "Please input dictionary name: ";
cin >> dic;
cout << "Please input output filename: ";
cin >> outfile;
sign_sort(dic.c_str());
write_file(outfile.c_str());
return 0;
}
附:(2012.5.6百度實(shí)習(xí)筆試題)一個(gè)單詞交換字母位置,可得另一個(gè)單詞,如army->mary,成為兄弟單詞。提供一個(gè)單詞,在字典中找到它的兄弟。描述數(shù)據(jù)結(jié)構(gòu)和查詢過(guò)程。
解題思路:如果不允許進(jìn)行預(yù)處理,那么我們只能順序遍歷整個(gè)字典,計(jì)算每個(gè)單詞的標(biāo)識(shí)與給定單詞的標(biāo)識(shí)比較。如果允許進(jìn)行預(yù)處理,我們可以如上述思路二將所有單詞加入一個(gè)map,然后輸出關(guān)鍵字(給定單詞的標(biāo)識(shí))對(duì)應(yīng)的值,值中就包含了該單詞的所有兄弟單詞。
相信本文所述實(shí)例有助于讀者更好的掌握C++下數(shù)據(jù)結(jié)構(gòu)與算法的實(shí)現(xiàn)技巧。
欄 目:C語(yǔ)言
下一篇:C++可變參數(shù)的函數(shù)與模板實(shí)例分析
本文標(biāo)題:C++變位詞問(wèn)題分析
本文地址:http://www.jygsgssxh.com/a1/Cyuyan/3498.html
您可能感興趣的文章
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 01-10深入理解C++中常見(jiàn)的關(guān)鍵字含義
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10c++中inline的用法分析
- 01-10用C++實(shí)現(xiàn)DBSCAN聚類算法
- 01-10全排列算法的非遞歸實(shí)現(xiàn)與遞歸實(shí)現(xiàn)的方法(C++)
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關(guān)鍵字的使用詳解
- 01-10深入C/C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)方式詳解
- 01-10深入理解C/C++混合編程


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 04-02jquery與jsp,用jquery
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10C#中split用法實(shí)例總結(jié)


