Phone Directory
#include <bits/stdc++.h> // Includes common standard libraries like iostream, vector, string.using namespace std; // Use the standard namespace.
// Class representing a single node in the Trie.class TrieNode {public: char data; // The character stored in this node. // An array of pointers to child TrieNodes. Size 26 for 'a' through 'z'. TrieNode* children[26]; bool isTerminal; // True if a word ends at this node.
// Constructor for TrieNode. TrieNode(char ch) { data = ch; isTerminal = false; // Initially, no word ends here.
// Initialize all child pointers to NULL. for(int i = 0; i < 26; i++) { children[i] = NULL; } } // Note: For a complete Trie implementation, a destructor should be added // to recursively delete child nodes to prevent memory leaks.};
// Class representing the Trie data structure.class Trie {public: TrieNode* root; // The root node of the Trie.
// Constructor for Trie. Initializes the root node with a null character. Trie() { root = new TrieNode('\0'); }
// Private recursive helper function for inserting a word. // 'root': Current node being processed. // 'word': The remaining part of the word to insert. void insertUtil(TrieNode* &root, string word) { // root is passed by reference to allow modification // Base case: If the word is empty, we've inserted all its characters. if(word.length() == 0) { root->isTerminal = true; // Mark this node as the end of a word. return; }
// Assumption: Word contains only lowercase English letters ('a' through 'z'). int index = word[0] - 'a'; // Calculate the index for the current character. TrieNode* child;
// Check if the child node for the current character already exists. if(root->children[index] == NULL) { // Character is absent. Create a new TrieNode for it. child = new TrieNode(word[0]); root->children[index] = child; // Link the new child node. } else { // Character is already present in the Trie path. child = root->children[index]; }
// Recursive call: Process the rest of the word from the child node. insertUtil(child, word.substr(1)); }
// Public method to insert a word. void insertNode(string word) { insertUtil(root, word); }};
// --- Core Suggestion Logic ---
// Helper function to print/collect all suggestions (words) from a given TrieNode.// Performs a Depth-First Search (DFS) from 'curr'.// 'curr': The current TrieNode to start collecting suggestions from.// 'temp': A vector of strings to store the collected suggestions (passed by reference).// 'prefix': The string path from the Trie root to 'curr' (current prefix being built).void printSuggetions(TrieNode* curr, vector<string> &temp, string prefix) { // Base case (1): If the current node marks the end of a word, add the 'prefix' to suggestions. if(curr->isTerminal) { temp.push_back(prefix); // 'prefix' now represents a complete word. }
// Traverse all possible children (a-z). for(char ch = 'a'; ch <= 'z'; ch++) { TrieNode* next = curr->children[ch - 'a']; // Get the child node for 'ch'.
// If the child exists, continue DFS. if(next != NULL) { prefix.push_back(ch); // Add the current character to the prefix. printSuggetions(next, temp, prefix); // Recurse on the child node. prefix.pop_back(); // Backtrack: Remove the character for exploring other branches. } }}
// Main function to get suggestions based on the query.// 'root': The root of the Trie.// 'query': The search query string.// Returns a vector of vectors of strings, where each inner vector contains suggestions// for a prefix of the query.vector< vector<string> > getSuggestions(TrieNode* root, string query) { vector< vector<string> > answer; // Stores all lists of suggestions. TrieNode* prev = root; // 'prev' tracks the current node in Trie traversal for query. string prefix = ""; // Builds the current query prefix.
// Iterate through each character of the query string. for(int i = 0; i < query.length(); i++) { char lastChar = query[i]; // Get the current character from the query. prefix.push_back(lastChar); // Append it to the current prefix.
// Try to move to the next TrieNode based on 'lastChar'. TrieNode* curr = prev->children[lastChar - 'a'];
// If the path for 'lastChar' does not exist in the Trie from 'prev' node. if(curr == NULL) { // No more contacts match this prefix. Fill remaining suggestions with empty lists. for(int j = i; j < query.length(); j++) { answer.push_back({}); // Push empty vector for remaining prefixes. } break; // Stop processing further query characters. }
// If 'curr' node is found (path exists). // Collect all words (suggestions) from this 'curr' node's subtree. vector<string> temp; printSuggetions(curr, temp, prefix); answer.push_back(temp); // Add the collected suggestions for this prefix. // temp.clear(); // Not strictly needed here as 'temp' is local and re-declared. // But if 'temp' was outside the loop, it would be needed.
prev = curr; // Move 'prev' to 'curr' for the next iteration. }
return answer;}
// Top-level function for the phone directory feature.// 'contactList': List of contact names.// 'query': The search query.// Returns suggestions for each prefix of the query.vector< vector<string> > phoneDirectory(vector<string> contactList, string query) { // 1. Creation of Trie. Trie* T = new Trie();
// 2. Insert all contact names into the Trie. for(int i = 0; i < contactList.size(); i++) { T->insertNode(contactList[i]); }
// 3. Get and return the suggestions based on the query. return getSuggestions(T->root, query);}
int main() { vector<string> contactList; // To store contact names. string query; // To store the search query. vector< vector<string> > suggestions; // To store the final results.
string temp_input_str; cout << "Enter the contact strings (enter -1 to stop) : "; cin >> temp_input_str; while(temp_input_str != "-1") { contactList.push_back(temp_input_str); cin >> temp_input_str; }
cout << "Enter the query string : "; cin >> query;
suggestions = phoneDirectory(contactList, query); // Get suggestions.
// Print the suggestions for each prefix. cout << "Suggestions: " << endl; for(vector<string> x : suggestions) { // Iterate through each list of suggestions (for a prefix). if (x.empty()) { cout << "No suggestions found." << endl; } else { for(string y : x) { // Iterate through each suggestion in the list. cout << y << " "; } cout << endl; } }
return 0;}1. Problem Statement
- Goal: Implement an autocomplete or phone directory-like functionality. Given a list of contact names (strings) and a search query string, for each prefix of the query, return all contacts that start with that prefix.
- Example:
contactList = ["geek", "geeks", "geometry", "apple"],query = "ge"- For “g”: [“geek”, “geeks”, “geometry”]
- For “ge”: [“geek”, “geeks”, “geometry”]
2. Approach: Trie Data Structure
- Core Idea: A Trie is ideal for prefix-based searches. By storing all contact names in a Trie, we can efficiently traverse paths corresponding to query prefixes and collect all words within the subtrees.
- TrieNode Structure:
char data: The character stored in the node.TrieNode* children[26]: An array of pointers to child nodes, representing the next possible characters (e.g., ‘a’ through ‘z’).bool isTerminal: A flag indicating if a complete word ends at this node.
3. Key Functions & Logic
a. TrieNode Class:
- Standard constructor to initialize
data,isTerminaltofalse, and allchildrenpointers toNULL.
b. Trie Class:
- Constructor: Initializes the
rootnode of the Trie (typically with a null character'\0'). insertUtil(TrieNode* root, string word)(Recursive Helper):- Base Case: If
wordis empty, setroot->isTerminal = true. - Recursive Step:
- Determine
indexforword[0](e.g.,word[0] - 'a'for lowercase). - If
children[index]isNULL, create a newTrieNodeand link it. - Recursively call
insertUtilon thechildnode withword.substr(1).
- Determine
- Base Case: If
insertNode(string word): Public wrapper forinsertUtil.
c. printSuggetions(TrieNode* curr, vector<string> &temp, string prefix) (Recursive DFS)
- Purpose: This function performs a Depth-First Search (DFS) starting from
currnode to find all complete words in its subtree. temp: Avector<string>passed by reference to collect the suggestions.prefix: The string built so far from the root to thecurrnode.- Base Case: If
curr->isTerminalistrue, it means theprefixcurrently built forms a complete word. Addprefixtotemp. - Recursive Step:
- Iterate through all possible characters (‘a’ to ‘z’).
- If
curr->children[index](for characterch) is notNULL:- Append
chto the currentprefix. - Recursively call
printSuggetionsoncurr->children[index]. - After the recursive call returns,
pop_backfromprefixto backtrack (removech) for exploring other branches.
- Append
d. getSuggestions(TrieNode* root, string query)
- Purpose: Orchestrates the suggestion process for the entire
query. - It iterates through each character of the
query(from left to right):- For the current character
lastChar, it updates theprefixstring. - It attempts to move down the Trie from the
prevnode (initiallyroot) tocurr(the child corresponding tolastChar). - If
curr == NULL: This means the currentqueryprefix is not found in any contact. No further suggestions can be made for longer prefixes. The loopbreaks. - If
curris found: CallprintSuggetions(curr, temp, prefix)to gather all words starting with the currentprefix. Add thesetempsuggestions to theanswer(which is avector<vector<string>>). Cleartempfor the next prefix. - Update
prev = currto continue traversal for the next character of the query.
- For the current character
- Returns the
answer(a list of suggestion lists for each prefix length).
e. phoneDirectory(vector<string> contactList, string query)
- This is the top-level function.
- It first constructs a
Trieby inserting allcontactListstrings. - Then, it calls
getSuggestionswith the Trie’srootand thequerystring.
4. Complexity Analysis
-
Let
Nbe the number of contacts incontactList. -
Let
L_avgbe the average length of a contact string. -
Let
Qbe the length of thequerystring. -
Let
S_ibe the number of suggestions for prefix of lengthi, andL_sug_i_avgbe their average length. -
Time Complexity:
O(N * L_avg + Q * (Nodes_in_Subtree_for_matching_prefix + Sum_of_Lengths_of_All_Suggestions_for_that_prefix))- Building Trie:
O(Sum of lengths of all contacts). Each insertion of a string of lengthLisO(L). getSuggestions: The outer loop runsQtimes. Inside the loop,printSuggetionsperforms a DFS on a subtree.- The time for
printSuggetionsis roughly proportional to the number of nodes in the subtree it traverses plus the total length of all strings it collects. - In the worst case, if all contacts share a long common prefix and there are many suggestions, this could be significant.
- A more precise bound is
O(Sum of lengths of all contacts + Sum over each prefix 'p' of query 'Q' of (number of nodes visited in Trie for 'p' + total characters in all suggested words for 'p')). This is typically very efficient for practical autocomplete scenarios.
- The time for
- Building Trie:
-
Space Complexity:
- Trie Storage:
O(Total characters in all distinct contact names). answervector:O(Q * S_max * L_sug_avg)whereS_maxis max suggestions andL_sug_avgis average length, as all suggestions for all prefixes are stored.
- Trie Storage: