poj 2001 Shortest Prefixes(Trie树)

题目:http://poj.org/problem?id=2001

转自:http://hi.baidu.com/gdut_sai/item/3e953a13b0d381573b176ea3

描述:给你那么多字符串,然后让你找出最短的前缀字符串。但不能跟下面的单词的前缀相同。

样例:

输入:
       carbohydrate
       cart
       carburetor
       caramel
       caribou
       carbonic
       cartilage
       carbon
       carriage
       carton
       car
       carbonate
输出:
       carbohydrate carboh
       cart cart
       carburetor carbu
       caramel cara
       caribou cari
       carbonic carboni
       cartilage carti
       carbon carbon
       carriage carr
       carton carto
       car car
       carbonate carbona

 

思路:trie建树,设置一个变量num,计算每个单词出现的次数。查找的时候如果num = 1则说明这是第一次出现过的单词,直接返回。

              在建tire的时候,设置一个num变量记录孩子节点的个数,如果孩子节点为1表示是一个单词节点,如果num>1表示是一个中间节点,继续搜索。

 

代码:

1)采用采用链表的方式建Trie

#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;

char str[1005][25];

struct Trinode{
	int num;//记录孩子节点的个数
	Trinode *link[26];
	Trinode(int _num=0):num(_num)
	{
		for(int i=0;i<26;i++){
			link[i] = NULL;
		}
	}
};

class Trie{
public:
	Trie()
	{
		root = new Trinode(0);	
	}
	void insert(char *tar)
	{
		char ch;
		Trinode *ptr = root;
		while(*tar){
			ch = *tar-'a';
			if(ptr->link[ch]==NULL){
				ptr->link[ch] = new Trinode(1);//单词节点的num为1
			}
			else
				ptr->link[ch]->num++;			
			ptr = ptr->link[ch];
			tar++;
		}
	}

	void seach(char *tar)
	{
         int i = 0;
		 char ch;
		 Trinode* ptr = root;
		 while(ch = *tar){
			 cout<<ch;
			 if(ptr->link[ch-'a']->num == 1)return;
			 ptr = ptr->link[ch-'a'];
			 tar++;
		 }
	} 
private:
	Trinode *root;
};


class ShortestPrefixes{
public:
	void solution()
	{
		int n,i;
        Trie tree;
		while(scanf("%s",str[n])!=EOF)
		//cin>>n;
		//for(int i=0;i<n;i++)
		{
			//scanf("%s",str[i]);
			tree.insert(str[n]);
			n++;
		}
		for(i=0;i<n;++i)
		{
			printf("%s ",str[i]);
			tree.seach(str[i]);
			printf("\n");
		}
	}
};

int main()
{
     ShortestPrefixes poj2001;
	 poj2001.solution();
	 system("pause");
	 return 0;
}


2)采用一维数组的方式建立Trie

我的字典树有点慢诶 , 47ms
Posted by jufusong at 2013-03-20 15:28:41 on Problem 2001 
--------------------------------------------------------------------------------
# include <cstdio>
# include <iostream>

using namespace std ;

char s [ 1100 ] [ 21 ] ;

struct Dic
{
    int time ;
    int next [ 30 ] ;
} dic [ 1000000 ] ;
int cnt ;//记录了当前数组中插入了多节点

void addword ( char * s )
{
    int len = strlen ( s ) ;
    int num = 0 ;//num用来遍历trie树
    for ( int i = 0 ; i < len ; i ++ )
    {
        int c =  s [ i ] - 'a' ;
        if ( dic [ num ] . next [ c ] )
        {
            num = dic [ num ] . next [ c ] ;
            dic [ num ] . time ++ ;
        }
        else
        {
            dic [ num ] . next [ c ] = cnt ;
            num = cnt ++ ;
            dic [ num ] . time ++ ;
        }
    }
}

void findword ( char * s )
{
    int len = strlen ( s ) ;
    int num = 0 ;
    for ( int i = 0 ; i < len ; i ++ )
    {
        int c =  s [ i ] - 'a' ;
        if ( dic [ num ] . time == 1 )
            break ;
        putchar ( s [ i ] ) ;
        num = dic [ num ] . next [ c ] ;
    }
}

int main ( )
{
    cnt = 1 ;
    int n = 0 ;
    while ( gets ( s [ n ] ) )
    {
        addword ( s [ n ++ ] ) ;
    }
    dic [ 0 ] . time = n ;
    for ( int i = 0 ; i < n ; i ++ )
    {
        printf ( "%s " , s [ i ] ) ;
        findword ( s [ i ] ) ;
        printf ( "\n" ) ;
    }
}



讨论区的这个哥们也是用这么建的

#include <string>
#include <cstring>
#include <cstdlib>
#include <iostream>

using namespace std;

struct node
{
    int cnt;
    short al[26];
};

int ptr=0;
char s[1001][21];
node word[20010];

void Insert(char*s)
{
    int now=0; 
    int len=strlen(s);
    for(int i=0;i<len;i++)
    {
        if(word[now].al[s[i]-'a']==0)
        {
            word[now].al[s[i]-'a']=++ptr;
            now=ptr;
        }
        else now=word[now].al[s[i]-'a'];
        word[now].cnt++;
    }
}

void print(char*s)
{
    int now=0; 
    int len=strlen(s);
    printf("%s ",s);
    for(int i=0;i<len;i++)
    {
        now=word[now].al[s[i]-'a'];
        printf("%c",s[i]);
        if(word[now].cnt==1) break;
    }
    printf("\n");
}
                                                                         
int main()
{
    int i=0;
    while(gets(s[i])&&s[i][0]!='\0')
        Insert(s[i++]);
    for(int ii=0;ii<i;ii++)
        print(s[ii]);
    return 0;
}

3)另解:其实这道题大可不必如此费事,简单的比较就能搞定。给初学C++的兄弟们提供个思路:基本思路:先给字符串排序,之后对于每个字符串,分别和上下两个字符串比较(如果有的话),找到第一个字符不同,或者字符串终止的位置记录下来,则两个位置中比较靠后的,就是最短前缀串的位置。这样比较只需对相邻两字符串比较一次,算法复杂度为O(n),已经非常好了输出的时候注意:不要把'\0'也输出去,会WA的!

/*Source: < C++ Memory: 240K Time: 0ms>*/

#include<iostream>
#include<cstring>
#include<algorithm>      //排序提速用的,为了0ms不惜一切代价
using namespace std;
const int MAX = 1001;    //要稍微开大一点,防止输入数据中有空行
struct Prefix{
	char ch[24];     //字串,24个长度,也是适当的冗余
	int PrefixPosition;      //最短前缀位置
}str[MAX];

Prefix* pointer[MAX] = {NULL};    //用指针数组操作,排序时只交换指针
                                  //不影响原来的字符串先后顺序

bool cmp(Prefix*a,Prefix*b){      //要返回bool类型,int不行
	return strcmp(a->ch,b->ch) < 0;
}

int main(){ 

	int count = 0,pos1 = 0,pos2 = 0;
	while(cin>>str[count].ch){
		pointer[count] = &str[count];
		++count;
	}
	//sort the strings pointer in alphabetical order

	sort(pointer,pointer+count,cmp);

        //Compare
	for(int i = 0;i < count;++i){
		//和前后比较,a[i]与a[i+1]的比较在a[i]的时候能用到,同样循环到a[i+1]的时候也能用到
		if(i > 0)pos1 = pos2; //这样可减少一半的重复
		pos2 = 0;
		if(i < count - 1)
                while(pointer[i]->ch[pos2] && pointer[i]->ch[pos2] == pointer[i+1]->ch[pos2])
                    ++pos2;

		//用最大的
		pointer[i]->PrefixPosition = max(pos1,pos2);
	}

	for(int i = 0;i < count;++i){
		printf("%s ",str[i].ch);
		for(int j = 0;j <= str[i].PrefixPosition;++j)

                        //不要把'\0'打进去
			if(str[i].ch[j])putchar(str[i].ch[j]);
		printf("\n");
	}
	return 0;
}

后记:菜鸟刚学会了Trie树的构造方法,还有就是最后这个哥们的想法独具匠心啊,膜拜。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值