11-散列3 QQ帐户的申请与登陆
输入格式:
输入首先给出一个正整数N(≤105),随后给出N行指令。每行指令的格式为:“命令符(空格)QQ号码(空格)密码”。其中命令符为“N”(代表New)时表示要新申请一个QQ号,后面是新帐户的号码和密码;命令符为“L”(代表Login)时表示是老帐户登陆,后面是登陆信息。QQ号码为一个不超过10位、但大于1000的整数。密码为不小于6位、不超过16位、且不包含空格的字符串。
输出格式:
针对每条指令,给出相应的信息:
1)若新申请帐户成功,则输出“New: OK”;
2)若新申请的号码已经存在,则输出“ERROR: Exist”;
3)若老帐户登陆成功,则输出“Login: OK”;
4)若老帐户QQ号码不存在,则输出“ERROR: Not Exist”;
5)若老帐户密码错误,则输出“ERROR: Wrong PW”。
输入样例:
5 |
---|
L 1234567890 myQQ@qq.com |
N 1234567890 myQQ@qq.com |
N 1234567890 myQQ@qq.com |
L 1234567890 myQQ@qq |
L 1234567890 myQQ@qq.com |
输出样例:
ERROR: Not Exist |
---|
New: OK |
ERROR: Exist |
ERROR: Wrong PW |
Login: OK |
实现思路:
这是一道比较基础的题,可以通过构造哈希表或者排序来查找账号;这里使用构造哈希表的方法,冲突的解决办法是采用分离链接法(Separate Chaining)。
实际的代码只是稍微改造了一下浙江大学数据结构mooc中的分离链接法的代码,详情可以观看分离链接法。
首先,需要引入头文件以及定义各种参数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/*账号与密码最大长度的定义
它们的最大长度需要比题目所给的大一位
这是因为还需要一个位置来储存'\0'来判断字符串的结尾*/
#define Max_Password_Len 17
#define Max_Account_Len 11
#define MaxTableSize 1000000
/*各种状态的定义
最好用正数表示成功的状态
用负数或0表示失败的状态
这样会让强迫症看了舒服一点*/
#define ERROR_WrongPW -2
#define ERROR_Exist -1
#define ERROR_NOTExist 0
#define New_OK 1
#define Login_OK 2
然后,需要构造哈希表
typedef char AccountType[Max_Account_Len];//账号类型定义
typedef char PasswordType[Max_Password_Len];//密码类型定义
typedef int Index;
typedef enum{New, Log} Pattern;//两种模式,新建账号与登入账号
typedef struct
{
AccountType Account;
PasswordType Password;
}ElemType;//数据类型的定义,每个对应一个用户,内含用户的账号和密码
//链表指针的定义
typedef struct LNode *PtrToLNode;
//链表结点的定义
struct LNode
{
PtrToLNode Next;
ElemType Data;
};
typedef PtrToLNode List;//链表的定义
typedef PtrToLNode Position;//哈希表中结点位置的定义
//哈希表的定义
typedef struct TblNode *HashTable;
struct TblNode
{
int TableSize;//哈希表的大小
List Heads;//储存各个列表头节点的数组
};
接着,需要定义各种函数
int NextPrime(int N)//返回N的下一个素数
{
int i, P;
P = N%2? N + 2 : N + 1;
//P为N之后的第一个奇数
while(P < MaxTableSize)
{
for(i = (int)sqrt(P);i > 2;i--)//因为只考虑奇数,所以i为2时就结束了
if(P % i == 0)
break;
if(i == 2)
break;//i为2说明P为素数
else
P += 2;//i!=2说明P不是素数,则P指向下一个奇数
}
return P;
}
int Hash(int Key, int TableSize)
{//返回Key值相对应的哈希值,即其在哈希表中的储存下标
return Key % TableSize;
}
HashTable CreateTable(int TableSize)
{ //构造空的哈希表
HashTable H;
int i;
H = (HashTable)malloc(sizeof(TblNode));
H->TableSize = NextPrime(TableSize);
H->Heads = (List)malloc(sizeof(LNode) * H->TableSize);
for(i = 0;i<H->TableSize;i++)
{
H->Heads[i].Data.Account[0] = '\0';
H->Heads[i].Data.Password[0] = '\0';
H->Heads[i].Next = NULL;
}
return H;
}
Position Find(HashTable H, ElemType Key)
{
Position Pos;
Index p;
if(strlen(Key.Account) > 5) //账号大于5位时取最后5位
p = Hash(atoi(Key.Account +
strlen(Key.Account) - 5), H->TableSize);
else//账号不大于5位则等于它本身
p = Hash(atoi(Key.Account), H->TableSize);
Pos = H->Heads[p].Next;
while(Pos && strcmp(Key.Account, Pos->Data.Account))
Pos = Pos->Next;
return Pos;//Pos指向用户数据的位置,没有注册就返回NULL
}
int NewOrLog(HashTable H, ElemType Key, Pattern P)
{ //返回状态参数
Position Pos, NewPos;
Index p;
Pos = Find(H, Key);
switch(P)
{
case Log:
if(Pos == NULL)
return ERROR_NOTExist;//登陆时不存在账号
else if(strcmp(Pos->Data.Password, Key.Password) ||
(strlen(Key.Password) > 16 || strlen(Key.Password) < 6))
return ERROR_WrongPW; //密码错误或格式错误
else
return Login_OK;//账号和密码均正确,可以登录
case New:
if(Pos != NULL)
return ERROR_Exist; //新建账号时发现已经存在这样的账号了
else
{
NewPos = (Position)malloc(sizeof(LNode));
strcpy(NewPos->Data.Account, Key.Account);
strcpy(NewPos->Data.Password, Key.Password);
if(strlen(Key.Account) > 5)
p = Hash(atoi(Key.Account +
strlen(Key.Account) - 5), H->TableSize);
else
p = Hash(atoi(Key.Account), H->TableSize);
NewPos->Next = H->Heads[p].Next;
H->Heads[p].Next = NewPos;
return New_OK; //插入新值并返回插入成功
}
}
}
void DestroyTable(HashTable H)
{ //销毁哈希表
PtrToLNode p, q;
int i;
for(i = 0;i<H->TableSize;i++)
{
q = H->Heads[i].Next;
while(q)
{
p = q->Next;
free(q);
q = p;
}
}
free(H);
}
最后是主程序
int main(void)
{
int N, i;
ElemType Key;
char Input;
Pattern P;
HashTable H;
scanf("%d", &N);
H = CreateTable(2 * N);
for(i = 0;i<N;i++)
{
scanf("\n%c %s %s", &Input, Key.Account, Key.Password);
P = (Input == 'L') ? Log : New;
switch(NewOrLog(H, Key, P))
{//最后根据不同返回值输出对应状态即可
case ERROR_Exist:
printf("ERROR: Exist\n");
break;
case ERROR_NOTExist:
printf("ERROR: Not Exist\n");
break;
case ERROR_WrongPW:
printf("ERROR: Wrong PW\n");
break;
case Login_OK:
printf("Login: OK\n");
break;
case New_OK:
printf("New: OK\n");
break;
}
}
DestroyTable(H);
return 0;
}