CLRS 思考题 12-2 利用字典树处理字符串排序
Description
给定两个串a = 'a0a1……ap'和b = 'b0b1……bq',其中每一个 ai 和每一个 bj 都属于某个有序字符集,如果下面两条规则之一成立,则说串a按字典序小于串b:
1)存在一个整数 j,0 <= j <= min(p,q),使得ai = bi,i = 0,1,……,j-1,且aj < bj;
2)p < q,且ai = bi,对所有的i = 0,1,……,p 成立
例如,如果a和b是位串,则根据规则1)(设j=3),有10100 < 10110;根据规则2),有10100 < 101000。这与英语字典中的排序很相似。
下图显示的是基数树(radix tree)数据结构,其中存储了位串1011、10、011、100和0。当查找某个关键字a = a0a1……ap时,在深度为i的一个结点处,若ai = 0则向左转;若ai = 1则向右转。设S为一组不同的二进制串构成的集合,各串的长度之和为n。说明如何利用基数树,在O(n)时间内对S按字典序排序。例如,对图12-5中每个结点的关键字,排序的输出应该是序列0、011、10、100、1011
思路
大量字符串统计问题通常都是用 trie 树 or 哈希表,但是 哈希表排序的话 O(n) 有点难,所以选择 trie 树。
从题目的 hint 中很容易知道用 trie 树 + 前序遍历即可实现字典排序。其中 insert 的时间为 O(n),print 的时间为 O(n),所以算法的时间复杂度为 O(n)
在ASCLL码与字符的问题上 WA 了一发,C++ 没有数组边界检查导致查了很久,下次建 trie 树需要多多注意这个点。
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; //利用trie存储位串 struct Node{ Node *next[2]; //一个结点的next结点有两种可能 bool flag; string str; int cnt; Node() { for (int i = 0; i <2; i++) next[i] = NULL; flag = false; str = ""; cnt = 0; } }; Node *root; void initTrie(){ root = new Node(); } void insert(const string& s){ int len = s.size(); Node *now = root; for (int i = 0; i < len; i++) { int to = s[i] - '0'; //需要依据ASCLL码做减法,否则隐式转换to将等于48/49 if (!now->next[to]) now->next[to] = new Node(); now = now->next[to]; } if (now->flag) { ++now->cnt; return; } else { now->flag = true; now->str = s; now->cnt = 1; } } void preOderPrint (Node *now) { if (!now) return; if(now->flag) { for (int i = 0; i < now->cnt; i++) cout << now->str << endl; } preOderPrint(now->next[0]); preOderPrint(now->next[1]); } int main(void) { initTrie(); string tmp = ""; while(cin >> tmp) { insert(tmp); } preOderPrint(root); return 0; }
————全心全意投入,拒绝画地为牢