trie tree(字典树)的基础知识
1、trie tree的数据结构,trie tree的建立(实现trie tree)
先序遍历查找整个trie-树
struct trie_tree_node {
trie_tree_node *child[trie_max_child_size];
bool is_end_node; //确定是否是末尾结点
trie_tree_node() :is_end_node(false) {
for (int i = 0; i < trie_max_child_size; i++) {
child[i] = NULL;
}
}
};
void preorder_trie_tree(trie_tree_node* root, int layer) {
//trie_tree 的root为空。
for (int i = 0; i < trie_max_child_size; i++) {
if (root->child[i]) {
for (int j = 0; j < layer; j++) {
printf("---");
}
printf("%c", i + 'a');
if (root->child[i]->is_end_node) {
printf("(end)");
}
printf("\n");
preorder_trie_tree(root->child[i], layer+1);
}
}
}
导出trie-树中所有的单词
TIPS:node->child[i]中存放的是子结点的指针,需要用对应的数字编号 i + 'a'才能变成所需要的字母。
Trie-数的整体功能
class TrieTree {
public:
TrieTree() {
}
~TrieTree()
{
for (int i = 0; i < node_vec.size(); i++) {
delete node_vec[i];
}
}
void insert(const char* word) {
//将word插入至trie之中
}
bool search(const char* word) {
//搜索trie中是够存在word
}
bool startsWith(const char * prefix) {
//确定trie中是够有前缀是prefix的单词
}
TrieNode* root() {
return &_root;
//一开始return &root 因为函数名也是root所以发生了冲突 ,要注意函数名和变量名不要发生冲突
}
private:
vector<TrieNode*> node_vec;
TrieNode _root; //trie树的根节点
//trie-树节点维护的方案:
//用vector来维护每次new出来的结点指针
//每一次new出来的结点,并不是把结点连接,而是把这个结点push_back到vector里面,然后再返回这个指针
//好处在于:
//在析构的时候,我们只需要遍历这个vector就可以把所有的结点进行析构了
//就不用考虑在前序遍历的时候再去析构每一个结点
TrieNode * new_node() {
TrieNode* node = new TrieNode();
node_vec.push_back(node);
return node;
}
};
void insert(const char* word) {
//将word插入至trie之中
TrieNode* ptr = &_root;
while (*word)
{
int pos = *word - 'a';
if (!ptr->child[pos]) {
ptr->child[pos] = new_node();
}
ptr = ptr->child[pos];
word++;
}
ptr->is_end_node = true;
}
bool search(const char* word) {
//搜索trie中是够存在word
TrieNode* ptr = &_root;
while (*word) {
int pos = *word - 'a';
if (ptr->child[pos]->is_end_node) {
return false;
}
ptr = ptr->child[pos];
word++;
}
return ptr->is_end_node;
}
判断trie树中是否有以*prefix为前缀的单词
bool startsWith(const char * prefix) {
//确定trie树中是够有前缀是prefix的单词
TrieNode* ptr = &_root;
while (*prefix) {
int pos = *prefix - 'a';
if (!ptr->child[pos]) {
return false;
}
ptr = ptr->child[pos];
prefix++;
}
return true;
}
208、实现Trie-树(前缀树)
const int trie_max_child_size = 26;
struct TrieNode{
TrieNode* child[trie_max_child_size];
bool is_end;
TrieNode():is_end(false){
for(int i =0;i<trie_max_child_size;i++){
child[i] = NULL;
}
}
};
class TrieTree
{
public:
TrieTree(){}
~ TrieTree(){
for(int i = 0 ; i <node_vec.size();i++ ){
delete node_vec[i];
}
}
void insert(string word){
TrieNode* ptr = &root;
for(auto it = word.begin();it!= word.end();it++){
int pos = *it - 'a';
if(!ptr->child[pos]){
ptr->child[pos] = new_node();
}
ptr = ptr->child[pos];
}
//在插入的最后,肯定要把这个单词标记出来,所以最后一个字母应该in_end,否则在search时候会出错。
ptr->is_end = true;
}
bool search(string word){
TrieNode* ptr = &root;
for(auto it = word.begin();it!=word.end();it++){
int pos = *it - 'a';
if(!ptr->child[pos]){
return false;
}
ptr = ptr->child[pos];
}
return ptr->is_end;
}
bool startsWith(string prefix) {
TrieNode* ptr = &root;
for(auto it = prefix.begin();it!=prefix.end();it++){
int pos = *it - 'a';
if(!ptr->child[pos]){
return false;
}
ptr = ptr->child[pos];
}
return true;
}
private:
TrieNode root;
vector<TrieNode*> node_vec;
TrieNode* new_node(){
TrieNode* ptr = new TrieNode();
node_vec.push_back(ptr);
return ptr;
}
};
class Trie {
public:
/** Initialize your data structure here. */
Trie() {
}
/** Inserts a word into the trie. */
void insert(string word) {
trie_tree.insert(word);
}
/** Returns if the word is in the trie. */
bool search(string word) {
return trie_tree.search(word);
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
return trie_tree.startsWith(prefix);
}
private:
TrieTree trie_tree;
};
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* bool param_2 = obj.search(word);
* bool param_3 = obj.startsWith(prefix);
*/
211、添加和搜索单词-数据结构设计
思考:简单的深搜就可以解决。
const int trie_tree_child_max_size = 26;
struct TrieNode{
bool is_end;
TrieNode* child[trie_tree_child_max_size];
TrieNode():is_end(false){
for(int i = 0 ; i <trie_tree_child_max_size;i++ ){
child[i] = NULL;
}
}
};
class TrieTree{
public:
~TrieTree(){
for(int i = 0 ; i <node_vec.size();i++ ){
delete node_vec[i];
}
}
void insert(string word){
TrieNode* ptr = &_root;
for(auto it = word.begin();it!= word.end();it++){
int pos = *it - 'a';
if(!ptr->child[pos]){
ptr->child[pos] = new_node();
}
ptr = ptr->child[pos];
}
ptr->is_end = true;
}
bool search_trie_tree(TrieNode* node,const char* word){
if(*word == '\0'){
//无语了!!!! 一开始写成 word == '\0'!!!这是指针! = =
if(node->is_end){
return true;
}
return false;
}
if(*word == '.'){
for(int i = 0 ; i <trie_tree_child_max_size;i++ ){
if(node->child[i] && search_trie_tree(node->child[i],word+1)){
//if(node->child[i] && search_trie_tree(node->child[i],word++)){
//++ 和 +1
return true;
}
}
}
else{
int pos = *word - 'a';
if(node->child[pos] &&search_trie_tree(node->child[pos],word+1)){
return true;
}
}
return false;
}
TrieNode* root(){
return &_root;
}
private:
TrieNode _root;
vector<TrieNode*> node_vec;
TrieNode* new_node(){
TrieNode* ptr = new TrieNode();
node_vec.push_back(ptr);
return ptr;
}
};
class WordDictionary {
public:
/** Initialize your data structure here. */
WordDictionary() {
}
/** Adds a word into the data structure. */
void addWord(string word) {
_trie_tree.insert(word);
}
/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
bool search(string word) {
// const char* word_ptr = &word;
// const char* word_ptr = word.c_str();
return _trie_tree.search_trie_tree(_trie_tree.root(), word.c_str());
}
private:
TrieTree _trie_tree;
};
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary obj = new WordDictionary();
* obj.addWord(word);
* bool param_2 = obj.search(word);
*/
疑问:trie-树中间的pos是怎么存储的?需要用vs跳出来看看 !!!我就说 不是if(!pos) 而是if(ptr->child[pos]) 这个儿子当中存储不是不存在而是为空!
547、朋友圈
方法1:使用深度搜索,把每个圈子看做是一个连通分支,就是来检查图中有几个连通分支数。
void DFS(vector<vector<int>>& Graph,vector<int> & visit,int i ){
visit[i] = 1;
for(int j = 0 ; j <Graph[i].size();j++){
if(!visit[j] &&Graph[i][j]){
DFS(Graph,visit,j);
}
}
}
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
int count = 0;
vector<int> visit(M.size(),0);
for(int i = 0 ; i <M[0].size();i++){
if(!visit[i]){
DFS(M,visit,i);
count++;
}
}
return count;
}
};
方法2:并查集
方法:最短路(传递闭包)
//数组实现并查集
class DisJointSet {
public:
DisJointSet(int n) {
for (int i = 0; i < n; i++) {
_id.push_back(i);
}
}
int find(int p) {
return _id[p];
}
void union_(int p, int q) {
int pid = find(p);
int qid = find(q);
if (pid == qid) {
return;
}
for (int i = 0; i < _id.size(); i++) {
if (_id[i] == pid) {
_id[i] = qid;
}
}
}
void print_set() {
printf("元素:");
for (int i = 0; i < _id.size(); i++) {
cout << i<<" ";
}
cout << endl;
printf("集合:");
for (auto item : _id) {
cout << item << " ";
}
cout << endl;
}
private:
vector<int> _id;
};
class DisjointSet_forest {
public:
DisjointSet_forest(int n) {
for (int i = 0; i < n; i++) {
_id.push_back(i);
_size.push_back(1);
}
_count = n;
}
int find(int p) {
while (p != _id[p]) {
//将p的父节点id[p]更新为id[p]的父节点id[id[p]]
_id[p] = _id[_id[p]];
p = _id[p];
}
return p;
}
void union_(int p, int q) {
int i = find(p);
int j = find(q);
if (i == j) {
return;
}
if (_size[i] > _size[j]) {
_id[j] = i;
_size[i] += _size[j];
}
else {
_id[i] = j;
_size[j] += _size[j];
}
_count--;
}
void print_set() {
printf("元素: ");
for (int i = 0; i < _id.size(); i++) {
printf("%d ", i);
}
printf("\n");
printf("集合: ");
for (int i = 0; i < _id.size(); i++) {
printf("%d ", _id[i]);
}
printf("\n");
}
private:
vector<int> _id;//存放i的父节点
vector<int> _size; //存储i所在子树的规模
int _count;//子树的个数
};
用并查集解决朋友圈问题
路径压缩:隔一个结点压缩一次。
class DisJointSet {
public:
DisJointSet(int n) {
for (int i = 0; i < n; i++) {
_id.push_back(i);
_size.push_back(1);
}
tree_count = n;
}
int find(int p) {
while (p != _id[p]) {
_id[p] = _id[_id[p]];
p = _id[p];
}
return p;
}
void union_(int p,int q ) {
int i = find(p);
int j = find(q);
if (i == j){
return;
}
if (_size[i] > _size[j]) {
_id[j] = i;
_size[i] += _size[j];
tree_count--;
}
else {
_id[i] = j;
_size[j] += _size[i];
tree_count--;
}
}
int count(){
return tree_count;
}
private:
vector<int> _id; //存放i的父节点
vector<int> _size;//i所在子树的规模
int tree_count; //子树的个数
};
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
DisJointSet dis_joint_set(M.size());
for (int i = 0; i < M.size(); i++) {
for (int j = i+1; j < M.size(); j++) {
if (M[i][j]) {
dis_joint_set.union_(i, j);
}
}
}
return dis_joint_set.count();
}
};
307、区域和检索-数组可修改
线段树构造时间复杂度O(NlogN),查询插入更新为O(logN)
线段树的保存:
由于线段树是完全二叉树,线段树可以使用数组保存。将原始数组保存在存储区间和的线段树中。
跟结点下标为0,设某个节点的下标为i,它的左孩子下标为2i+1,右孩子下标为2i+2。
线段树的构造:
传入的参数:原始数组nums;线段树数组value(存储区间sum);当前线段(节点)在线段树数组(value)中的下标pos;当前线段的左端点left和右端点right。!!!【线段树中存放的都是一个区间,】
如果left = right,则表明到达叶子节点。
赋值value[pos] = nums[left];
计算线段中心mid = (left + right) /2
递归建立左子树线段[left,mid]
递归建立右子树线段[mid+1.right]
计算value[pos],为左右子树代表的区间累加和。
线段树的求和:
线段树的更新:一个递归的操作,首先更新叶子节点,然后再逐层的返回,把上面的每个线段都更新了。
有个问题一开始没搞明白:首先要判断是不是叶子节点(left == right) ,其次要判断这个叶子节点是不是我们要更新的那个,就要和index进行比较。~~~~额,但是在实际操作的时候,我发现不写也一样可以过OJ,是不是在这个递归查找的过程由于这个判断已经相当于有了自己的搜索范围,所以自然的到了想要更新的节点?。。还是写上吧
if (left == right && left == index) {
values[pos] = new_value;
return;
}
代码:
void build_segement_tree(vector<int>& nums, vector<int>& values, int pos/*在线段树数组中的位置*/,
int left, int right/*left,right原始数组的左右端点*/) {
if (right == left)/*表示已经到达叶子节点*/ {
values[pos] = nums[left];
return;
}
int mid = (left + right) / 2;
build_segement_tree(nums, values, 2 * pos + 1, left, mid);
build_segement_tree(nums, values, 2 * pos + 2, mid + 1, right);
values[pos] = values[2 * pos + 1] + values[2 * pos + 2];
}
void print(vector<int>& values, int pos/*在线段树数组中的位置*/, int left, int right, int layer) {
//首先输出当前是线段树的第几层
for (int i = 0; i < layer; i++) {
printf("---");
}
//打印出当前层的信息
printf("[%d %d] [%d]:%d", left, right, pos, values[pos]);
if (left == right) {
return;
}
int mid = (left + right) / 2;
print(values, 2 * pos + 1, left, mid, layer + 1);
print(values, 2 * pos + 2, mid + 1, right, layer + 1);
}
int sum_Range_segement_tree(vector<int>& values, int pos, int left, int right, int qleft, int qright) {
if (qleft > right || qright < left) {
return 0;
}
if (qleft <= left && qright >= right) {
return values[pos];
}
int mid = (left + right) / 2;
return sum_Range_segement_tree(values, 2 * pos + 1, left, mid, qleft, qright)
+ sum_Range_segement_tree(values, 2 * pos + 2, mid + 1, right, qleft, qright);
}
void updata_segement_tree(vector<int>& values, int pos, int left, int right, int index, int new_value) {
if (left == right && left == index) {
values[pos] = new_value;
return;
}
int mid = (left + right) / 2;
if (index > mid) {
updata_segement_tree(values, 2 * pos + 2, mid + 1, right, index, new_value);
}
else {
updata_segement_tree(values, 2 * pos + 1, left, mid, index, new_value);
}
values[pos] = values[2 * pos + 1] + values[2 * pos + 2];
}
class segement_tree {
public:
segement_tree(vector<int>& nums) {
if (!nums.size()) {
return;
}
int n = nums.size() * 4; //线段树的value数组大小是nums的4倍
_right_end = nums.size() - 1; //线段树的右端点
for (int i = 0; i < n; i++) {
values.push_back(0);
}
build_segement_tree(nums, values, 0, 0, _right_end);
}
void updata(int i, int val) {
updata_segement_tree(values, 0, 0, _right_end, i, val);
}
int sum_range(int qleft, int qright) {
return sum_Range_segement_tree(values, 0, 0, _right_end, qleft, qright);
}
private:
vector<int> values;
int _right_end;
};
总结:
- 线段树的线段数组values和原始数组nums别弄混淆了。
- 线段树的长度是原始数组的4倍
- 线段树组中的位置是pos,左右子树分别为2*pos+1,2*pos+2;在updata的时候传入的index是原始数组中的位置。
- 递归的时候左右子树的pos不要弄错了。并且在求和时候是给出原始数组的整体范围,以及一个原始数组的subset范围。
终于是把这个课程上完了,很多地方一点点自己调试还是花了很多时间,不过有些很难的也是跟着课程的思路在写,总体来说还是收获比较大把。原本定的10周,结果11周弄完了。emmm~~~还是不要太浮躁太懒散了吧!感觉是如果你真心想做一件事,一声不吭的就做完了,反而是有时候心浮气躁老是去抱怨,即影响了自己,又拖慢了进度!
汗!还是跟研一考试的感觉一样!
不争之争,反而是我激流勇进吧!只用把自己该掌握的都掌握了!做加法步步为营,做减法患得患失!