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
,isTerminal
tofalse
, and allchildren
pointers toNULL
.
b. Trie
Class:
- Constructor: Initializes the
root
node of the Trie (typically with a null character'\0'
). insertUtil(TrieNode* root, string word)
(Recursive Helper):- Base Case: If
word
is empty, setroot->isTerminal = true
. - Recursive Step:
- Determine
index
forword[0]
(e.g.,word[0] - 'a'
for lowercase). - If
children[index]
isNULL
, create a newTrieNode
and link it. - Recursively call
insertUtil
on thechild
node 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
curr
node 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 thecurr
node.- Base Case: If
curr->isTerminal
istrue
, it means theprefix
currently built forms a complete word. Addprefix
totemp
. - Recursive Step:
- Iterate through all possible characters (‘a’ to ‘z’).
- If
curr->children[index]
(for characterch
) is notNULL
:- Append
ch
to the currentprefix
. - Recursively call
printSuggetions
oncurr->children[index]
. - After the recursive call returns,
pop_back
fromprefix
to 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 theprefix
string. - It attempts to move down the Trie from the
prev
node (initiallyroot
) tocurr
(the child corresponding tolastChar
). - If
curr == NULL
: This means the currentquery
prefix is not found in any contact. No further suggestions can be made for longer prefixes. The loopbreak
s. - If
curr
is found: CallprintSuggetions(curr, temp, prefix)
to gather all words starting with the currentprefix
. Add thesetemp
suggestions to theanswer
(which is avector<vector<string>>
). Cleartemp
for the next prefix. - Update
prev = curr
to 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
Trie
by inserting allcontactList
strings. - Then, it calls
getSuggestions
with the Trie’sroot
and thequery
string.
4. Complexity Analysis
-
Let
N
be the number of contacts incontactList
. -
Let
L_avg
be the average length of a contact string. -
Let
Q
be the length of thequery
string. -
Let
S_i
be the number of suggestions for prefix of lengthi
, andL_sug_i_avg
be 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 lengthL
isO(L)
. getSuggestions
: The outer loop runsQ
times. Inside the loop,printSuggetions
performs a DFS on a subtree.- The time for
printSuggetions
is 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)
. answer
vector:O(Q * S_max * L_sug_avg)
whereS_max
is max suggestions andL_sug_avg
is average length, as all suggestions for all prefixes are stored.
- Trie Storage: