Phone List

Phone List

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述

Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let's say the phone catalogue listed these numbers:

  • Emergency 911
  • Alice 97 625 999
  • Bob 91 12 54 26

In this case, it's not possible to call Bob, because the central would direct your call to the emergency line as soon as you had dialled the first three digits of Bob's phone number. So this list would not be consistent.

输入
The first line of input gives a single integer, 1 ≤ t ≤ 10, the number of test cases. Each test case starts with n, the number of phone numbers, on a separate line, 1 ≤ n ≤ 100000. Then follows n lines with one unique phone number on each line. A phone number is a sequence of at most ten digits.
输出
For each test case, output "YES" if the list is consistent, or "NO" otherwise.
样例输入
2
3
911
97625999
91125426
5
113
12340
123440
12345
98346
样例输出
NO
YES
来源

POJ

解析:题意大致是输入n组的电话号码(也就是字符串),让你查找一下有没有字符串是另外一个字符串的前缀,如果有的话输出NO反之输出YES,英文题看懂题意后还是蛮简单的哈。

首先,大家想到就是排序然后查找嘛,但是会不会超时类?首先讨论为什么要排序,按照ASCII排序后如果输入的字符串中有是另外一个字符串的前缀则这两个字符串必然在排序后是相邻的,比如:ab , ad , e,abc,排序后是ab,abc,ad,e,这样只需要检查排序后相邻字符串是否有前缀即可。于是有了我下面的代码,代码用的是sort,很可惜测试是超时的。注意这种大量的输入尽量不要用cin了,太费时间了哈!

//超时代码
#include <stdio.h>
#include <string>
#include <vector>
#include <algorithm>
using std::string;
using std::vector;
bool cmp(const string &a,const string &b)
{
	return a<b;
}
bool Judge(const string &a,const string &b)
{
	int i;
	for(i=0;i<a.length();++i)
	{
		if(a[i]!=b[i])
		{
			return true;
			break;
		}
	}
	if(i>=a.length())
		return false;
}
int main()
{
	int T;
	scanf("%d",&T);
	vector<string> vstr;
	while(T--)
	{
		bool Flag=true;
		vstr.clear();
		int N;
		scanf("%d",&N);
		char phone[100000][11];
		for(int i=0;i<N;++i)
		{
			scanf("%s",phone[i]);
			vstr.push_back(phone[i]);
		}
		//对vector进行排序
		sort(vstr.begin(),vstr.end(),cmp);
		//判断前一个字符串是否为后一个字符串的字串
		for(size_t i=0;i<vstr.size()-1;++i)
		{
			//为了避免复杂度进行减枝
			//如果第一个字符不相等的话则肯定不是字串
			if(vstr.at(i)[0]!=vstr.at(i+1)[0])
			{
				continue;
			}
			//如果前一个字符串的长度大于等于后一个字符串的长度则一定也不可能为后一个的字串
			//因为题意说了输入的字符串不相等
			if(vstr.at(i).length()>=vstr.at(i+1).length())
			{
				continue;
			}
			//除了上面两种情况外,开始判断
			Flag = Judge(vstr.at(i),vstr.at(i+1));
			if(!Flag)
				break;
		}
		if(Flag)
          printf("YES\n");
		else
		  printf("NO\n");
	}
	return 0;
}

然后就开始改进,当然肯定是从排序了,然后我将排序换成了stl中的快速排序qsort

功 能: 使用 快速排序例程进行排序
头文件:stdlib.h
用 法: void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));
参数: 1 待排序 数组首地址
2 数组中待排序元素数量
3 各元素的占用空间大小
4 指向函数的 指针,用于确定排序的顺序
于是有了下面的代码

//将cin和cout换成scanf和printf可以ac
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//按照ASCII码进行排序
int cmp(const void *a,const void *b)
{
	//cmp函数返回值小于0则第一个参数排在前面
	return strcmp((char*)a,(char*)b);
}
bool Judge(const char *a,const char *b)
{
	while(*a==*b)
	{
		a++;
		b++;
	}
	if(*a!='\0')
		return true;
	else
		return false;
}
int main()
{
	int T;
	scanf("%d",&T);
	//定义二维数组用qsort来处理排序
	char phone[100000][11];
	while(T--)
	{
		bool Flag=true;
		int N;
		scanf("%d",&N);
		for(int i=0;i<N;++i)
		{
			scanf("%s",phone[i]);
		}
		//对vector进行排序
		qsort(phone, N,11,cmp);
		//判断前一个字符串是否为后一个字符串的字串
		for(int i=0;i<N-1;++i)
		{
			//为了避免复杂度进行减枝
			//如果第一个字符不相等的话则肯定不是字串
			if(phone[i][0]!=phone[i+1][0])
			{
				continue;
			}
			//如果前一个字符串的长度大于等于后一个字符串的长度则一定也不可能为后一个的字串
			//因为题意说了输入的字符串不相等
		   if(strlen(phone[i])>=strlen(phone[i+1]))
		   {
			   continue;
		   }
			//除了上面两种情况外,开始判断
		   Flag=Judge(phone[i],phone[i+1]);
		   if(!Flag)
			   break;
		}
		if(Flag)
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}

开始我用了快速排序,用cin,cout输入输出,果然超时哈!一看n那么大,有可能超时是因为输入的问题,然后将输入输出改了以后就AC了,内存相对来说占用差不多,时间上也不少哈!

当然并不是只有快速排序也可以过,然后有了下面的归并排序(知道归并排序的复杂度是多少么?)算了,还是说一下各种排序的时间复杂度

排序法 平均时间 最差情形 稳定度 额外空间 备注
冒泡 O(n2) O(n2) 稳定 O(1) n小时较好
交换 O(n2) O(n2) 不稳定 O(1) n小时较好
选择 O(n2) O(n2) 不稳定 O(1) n小时较好
插入 O(n2) O(n2) 稳定 O(1) 大部分已排序时较好
基数 O(logRB) O(logRB) 稳定 O(n)

B是真数(0-9),

R是基数(个十百)

Shell

O(nlogn)

O(n^1.25)

???

O(ns) 1<s<2 不稳定 O(1) s是所选分组
快速 O(nlogn) O(n2) 不稳定 O(nlogn) n大时较好
归并 O(nlogn) O(nlogn) 稳定 O(1) n大时较好
O(nlogn) O(nlogn) 不稳定 O(1) n大时较好

由上表可见,快速排序和归并排序的平均时间一样,但是最差情况没有归并排序的好,并且归并排序是稳定的(上面是我网上找的)。但是我的测试结果可是快速排序用的时间可是比归并排序少的多哈!可能是服务器反应的问题哈!

贴一个归并排序的代码喽,(注明:归并排序的代码不是我写的哈)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010

using namespace std;

char list[N][15];
char temp[N][15];

void merge(int s1,int e1,int s2,int e2)
{

    int i,j,k;
    i=s1;j=s2;k=0;
    while(i<=e1&&j<=e2)
    {
        if(strcmp(list[i],list[j])<0) strcpy(temp[k++],list[i++]);
        else strcpy(temp[k++],list[j++]);
    }
    while(i<=e1)  strcpy(temp[k++],list[i++]);
    while(j<=e2)  strcpy(temp[k++],list[j++]);
    for(k=0,i=s1;i<=e2;i++,k++)    strcpy(list[i],temp[k]);
}

void mergesort(int s,int e)
{
    int m=0;
    if(s<e)
    {
        m=(s+e)>>1;
        mergesort(s,m);
        mergesort(m+1,e);
        merge(s,m,m+1,e);
    }
}

bool judge(char *s1,char *s2)
{
    int i=0,j=0,len1,len2;
    len1=strlen(s1);len2=strlen(s2);
    while(i<len1&&j<len2)
    {
        if(s1[i]==s2[j])
        {
            i++;j++;
        }
        else  return false;
    }
    if(i==len1||j==len2) return true;
}

int main()
{
    int test;
    scanf("%d",&test);
    while(test--)
    {
        int n;
        scanf("%d",&n);
        int i;
        for(i=0;i<n;i++)
        {
            scanf("%s",list[i]);
        }
        mergesort(0,n-1);
        bool yes=true;
        for(i=0;i<=n-2;i++)
        {
            if(judge(list[i],list[i+1]))
            {
                yes=false;break;
            }
        }
        if(yes) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

也许有人急了,这什么排序不排序的,很明显是字典树嘛,哇靠,牛逼哈,我没接触过字典树前也是想排序做的,排序速度慢我就改进,后来看了网上的解析,原来是字典树哈,真是高端,下面就说一下字典树的做法

字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。

搜索字典项目的方法为:
(1) 从根结点开始一次搜索;
(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
(3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
(4) 迭代过程……
(5) 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。
其他操作类似处理
贴一下字典树AC的代码哈
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <memory.h>
//定义字典树子节点的最大个数
#define MAX 10
//字典树节点的数据结构
typedef struct TrieNode{
	int flag;
	struct TrieNode *next[MAX];
}TrieNode;
//定义根节点
TrieNode *T;
//建立字典树的节点
TrieNode* CreateNode()
{
	TrieNode *node;
	node=(TrieNode*)malloc(sizeof(TrieNode));
	node->flag=1;
	memset(node->next,NULL,sizeof(node->next));
// 	for(int i=0;i<MAX;++i)
// 	{
// 		node->next[i]=NULL;
// 	}
	return node;
}
//插入节点判断当前输入的字符串是否是以前输入的前缀
void Insert(const char *str,bool &Flag)
{
	TrieNode *p;
	p=T;
	int len=strlen(str);
	for(int i=0;i<len;++i)
	{
		if(i!=len-1)
		{
			if(p->next[str[i]-'0']==NULL)
			{
				p->next[str[i]-'0']=CreateNode();
				p=p->next[str[i]-'0'];
			}else{
				p=p->next[str[i]-'0'];
				if(p->flag==2)
				{
					Flag=false;
				}
			}
		}else
		{//最后一个字符
			if(p->next[str[i]-'0']==NULL)
			{
				p->next[str[i]-'0']=CreateNode();
				p=p->next[str[i]-'0'];
				p->flag=2;
			}else{
				Flag=false;
			}
		}
		
	}
}
//释放字典树的空间
void dealTrie(TrieNode *T)
{
	if(T==NULL)
		return;
	for(int i=0;i<MAX;++i)
	{
		dealTrie(T->next[i]);
	}
	//释放T
	free(T);
}
int main()
{
	int cases;
	char s[11];
	//输入数据的组数
	scanf("%d",&cases);
	while(cases--)
	{
		//对根节点分配空间
		T=CreateNode();
		bool Flag=true;
		int N;
		scanf("%d",&N);
		for(int i=0;i<N;++i)
		{
			//输入字符串
			scanf("%s",s);
			//如果当前已经查到有前缀则不用再插入
			if(Flag==false)
				continue;
			Insert(s,Flag);
		}
		if(Flag)
			printf("YES\n");
		else
			printf("NO\n");
		//释放字典树占用的内存空间
		dealTrie(T);
	}
	return 0;
}
字典树的代码写的好烂,大家凑活着当反面教材吧,写字典树的时候卡到了一个地方,现在感觉还不理解,有大神给解释下么?
如果我在插入节点遍历字符串的时候先p=p->next[str[i]-'0']然后再判断p是否为空,这样本人感觉理论是没区别哈,但是实际调试的是,每次运行到这里的话以前建立的节点就不存在,感觉在此p总会重置为NULL,不知道这样为什么。这指针指的都晕乎乎的了,唉……菜鸟继续修炼了哈!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值