#include #include #include #include #include #include #include using namespace std; /* DISTRIBUTION OF DICTIONARY: Read the words...88984 1 1 2 48 3 601 4 2409 5 4882 6 8205 7 11989 8 13672 9 13014 10 11297 11 8617 12 6003 13 3814 14 2173 15 1169 16 600 17 302 18 107 19 53 20 28 Elapsed time FAST: 5.218 Elapsed time MEDIUM: 77.735 Elapsed time SLOW: 450.891 **/ vector readWords( istream & in ) { string oneLine; vector v; while( in >> oneLine ) v.push_back( oneLine ); return v; } // Returns true if word1 and word2 are the same length // and differ in only one character. bool oneCharOff( const string & word1, const string & word2 ) { if( word1.length( ) != word2.length( ) ) return false; int diffs = 0; for( int i = 0; i < word1.length( ); i++ ) if( word1[ i ] != word2[ i ] ) if( ++diffs > 1 ) return false; return diffs == 1; } // Computes a map in which the keys are words and values are vectors of words // that differ in only one character from the corresponding key. // Uses a quadratic algorithm. map > computeAdjacentWordsSlow( const vector & words ) { map > adjWords; for( int i = 0; i < words.size( ); i++ ) for( int j = i + 1; j < words.size( ); j++ ) if( oneCharOff( words[ i ], words[ j ] ) ) { adjWords[ words[ i ] ].push_back( words[ j ] ); adjWords[ words[ j ] ].push_back( words[ i ] ); } return adjWords; } // Computes a map in which the keys are words and values are vectors of words // that differ in only one character from the corresponding key. // Uses a quadratic algorithm, but speeds things up a little by // maintaining an additional map that groups words by their length. map > computeAdjacentWordsMedium( const vector & words ) { map > adjWords; map > wordsByLength; // Group the words by their length for( int i = 0; i < words.size( ); i++ ) wordsByLength[ words[ i ].length( ) ].push_back( words[ i ] ); // Work on each group separately map >::const_iterator itr; for( itr = wordsByLength.begin( ); itr != wordsByLength.end( ); ++itr ) { const vector & groupsWords = itr->second; for( int i = 0; i < groupsWords.size( ); i++ ) for( int j = i + 1; j < groupsWords.size( ); j++ ) if( oneCharOff( groupsWords[ i ], groupsWords[ j ] ) ) { adjWords[ groupsWords[ i ] ].push_back( groupsWords[ j ] ); adjWords[ groupsWords[ j ] ].push_back( groupsWords[ i ] ); } } return adjWords; } // Computes a map in which the keys are words and values are vectors of words // that differ in only one character from the corresponding key. // Uses an efficient algorithm that is O(N log N) with a map, or // O(N) is a hash_map is used. map > computeAdjacentWords( const vector & words ) { map > adjWords; map > wordsByLength; // Group the words by their length for( int i = 0; i < words.size( ); i++ ) wordsByLength[ words[ i ].length( ) ].push_back( words[ i ] ); // Work on each group separately map >::const_iterator itr; for( itr = wordsByLength.begin( ); itr != wordsByLength.end( ); ++itr ) { const vector & groupsWords = itr->second; int groupNum = itr->first; // Work on each position in each group for( int i = 0; i < groupNum; i++ ) { // Remove one character in specified position, computing representative. // Words with same representatives are adjacent; so first populate // a map... map > repToWord; for( int j = 0; j < groupsWords.size( ); j++ ) { string rep = groupsWords[ j ]; rep.erase( i, 1 ); repToWord[ rep ].push_back( groupsWords[ j ] ); } // and then look for map values with more than one string map >::const_iterator itr2; for( itr2 = repToWord.begin( ); itr2 != repToWord.end( ); ++itr2 ) { const vector & clique = itr2->second; if( clique.size( ) >= 2 ) { for( int p = 0; p < clique.size( ); p++ ) for( int q = p + 1; q < clique.size( ); q++ ) { adjWords[ clique[ p ] ].push_back( clique[ q ] ); adjWords[ clique[ q ] ].push_back( clique[ p ] ); } } } } } return adjWords; } // Find most changeable word: the word that differs in only one // character with the most words. Return a list of these words, in case of a tie. vector findMostChangeable( const map > & adjacentWords ) { vector mostChangeableWords; int maxNumberOfAdjacentWords = 0; map >::const_iterator itr = adjacentWords.begin( ); while( itr != adjacentWords.end( ) ) { const pair > & entry = *itr++; if( entry.second.size( ) > maxNumberOfAdjacentWords ) { maxNumberOfAdjacentWords = entry.second.size( ); mostChangeableWords.clear( ); } if( entry.second.size( ) == maxNumberOfAdjacentWords ) mostChangeableWords.push_back( entry.first ); } return mostChangeableWords; } void printMostChangeables( const vector & mostChangeable, const map > & adjWords ) { map > & adjacentWords = const_cast > &>( adjWords ); for( int i = 0; i < mostChangeable.size( ); i++ ) { cout << mostChangeable[ i ] << ":"; vector adjacents = adjacentWords[ mostChangeable[ i ] ]; for( int j = 0; j < adjacents.size( ); j++ ) cout << " " << adjacents[ j ]; cout << " (" << adjacents.size( ) << " words)" << endl; } } void printHighChangeables( const map > & adjacentWords, int minWords ) { map >::const_iterator itr = adjacentWords.begin( ); while( itr != adjacentWords.end( ) ) { const pair > & entry = *itr++; const vector & words = entry.second; if( words.size( ) >= minWords ) { cout << entry.first << " (" << words.size( ) << "):"; for( int i = 0; i < words.size( ); i++ ) cout << " " << words[ i ]; cout << endl; } } } // After the shortest path calculation has run, computes the vector that // contains the sequence of words changes to get from first to second. vector getChainFromPreviousMap( const map & previous, const string & first, const string & second ) { map & prev = const_cast &>( previous ); vector result; if( prev[ second ] != "" ) { string current = second; while( current != "" ) { result.push_back( current ); current = prev[ current ]; } } reverse( result.begin( ), result.end( ) ); return result; } // Runs the shortest path calculation from the adjacency map, returning a vector // that contains the sequence of word changes to get from first to second. vector findChain( const map > & adjacentWords, const string & first, const string & second ) { map previousWord; queue q; q.push( first ); while( !q.empty( ) ) { string current = q.front( ); q.pop( ); map >::const_iterator itr; itr = adjacentWords.find( current ); if( itr != adjacentWords.end( ) ) { const vector & adj = itr->second; for( int i = 0; i < adj.size( ); i++ ) if( previousWord[ adj[ i ] ] == "" ) { previousWord[ adj[ i ] ] = current; q.push( adj[ i ] ); } } } previousWord[ first ] = ""; return getChainFromPreviousMap( previousWord, first, second ); } // Runs the shortest path calculation from the original set of words, returning // a vector that contains the sequence of word changes to get from first to // second. Since this calls computeAdjacentWords, it is recommended that the // user instead call computeAdjacentWords once and then call other findChain for // each word pair vector findChain( const vector & words, const string & first, const string & second ) { map > adjacentWords = computeAdjacentWords( words ); return findChain( adjacentWords, first, second ); } int main( ) { clock_t start, end; ifstream fin( "dict.txt" ); vector words = readWords( fin ); cout << "Read the words..." << words.size( ) << endl; map > adjacentWords; start = clock( ); adjacentWords = computeAdjacentWords( words ); end = clock( ); cout << "Elapsed time FAST: " << double(end-start)/CLOCKS_PER_SEC << endl; printHighChangeables( adjacentWords, 15 ); /* start = clock( ); adjacentWords = computeAdjacentWordsMedium( words ); end = clock( ); cout << "Elapsed time MEDIUM: " << double(end-start)/CLOCKS_PER_SEC << endl; start = clock( ); adjacentWords = computeAdjacentWordsSlow( words ); end = clock( ); cout << "Elapsed time SLOW: " << double(end-start)/CLOCKS_PER_SEC << endl; cout << "Adjacents computed..." << endl; vector mostChangeable = findMostChangeable( adjacentWords ); cout << "Most changeable computed..." << endl; printMostChangeables( mostChangeable, adjacentWords ); */ for( ; ; ) { cout << "Enter two words: "; string w1, w2; cin >> w1 >> w2; vector path = findChain( adjacentWords, w1, w2 ); cout << path.size( ) << endl; for( int i = 0; i < path.size( ); i++ ) cout << path[ i ] << " " ; cout << endl; } return 0; }