PTA 电话聊天狂人 思路分析及代码解析v1.0
一、前导
1. 需要掌握的知识
通过分离链接法实现Hash/哈希表
2. 题目信息
2.1 题目来源:PTA / 拼题A
2.2 题目地址: 电话聊天狂人
二、解题思路分析
1. 题意理解
对输入的通话记录进行整理,找到出现次数最多的电话号码、通话次数;若不唯一,则需要找到最小号码、通话次数和并列人数
1. 1 输入数据
4 //通话记录条数
13005711862 13588625832 //具体的通话记录
13505711862 13088625832
13588625832 18087925832
15005713862 13588625832
1.2 输出数据
13588625832 3 // 出现次数最多的电话号码 通话次数
......
2. 思路分析
与陈越老师讲解的思路一致:首先创建Hash表(链表方式),然后将录入的数据存储在Hash表中,最后扫描Hash表找到目标数据
三、具体实现
1. 弯路和bug
Hash表编码的熟练度低
2. 代码框架
2.1 采用的数据结构
结构体、链表
typedef char ElementType[KeyLength + 1]; //关键字类型是字符串 电话号码11位+'\0'
//链表定义
typedef struct LinkTableNode* PtrLinkTableNode;
typedef PtrLinkTableNode List;
struct LinkTableNode
{
ElementType Data; //电话号码
PtrLinkTableNode Next; //相同哈希值的下一个号码
int Count; //Count用于指示电话号码出现的次数
};
//散列表结点定义
typedef struct HashTableNode* HashTable;
struct HashTableNode
{
int HashTableSize;//表最大长度
List Heads; //指向链表头结点的数组
};
2.2 程序主体框架
程序伪码描述
int main()
{
1.创建链表形式的Hash表
2.将电话号码插入到Hash表中
3.扫描Hash表找到目标数据并打印
4.释放Hash表占用的内存空间
}
2.3 各分支函数
2.3.1 CreateHashTable( ) 创建Hash表,属于基本功
HashTable CreateHashTable(int HashTableSize)
{
HashTable H;
H = (HashTable)malloc(sizeof(HashTableNode));
H->HashTableSize = NextPrime(HashTableSize); //Hash表大小:大于存储元素个数的素数
//链表头结点数组
H->Heads = (List)malloc(sizeof(struct LinkTableNode) * H->HashTableSize);
for (int i = 0; i < H->HashTableSize; i++) //初始化 链表头结点数组各数组元素
{
H->Heads[i].Next = NULL;
H->Heads[i].Data[0] = '\0';
H->Heads[i].Count = 0;//计数
}
return H;
}
2.3.2 Find( ) 在Hash表中搜索元素,若为空,返回Null;若找到,返回该链接点的位置。Find函数被Insert函数调用
Position Find(HashTable H, ElementType Key)
{
Position P;
Index Pos;
Pos = Hash(Key, H->HashTableSize); //初始化散列位置:求余
//strcmp(str1,str2),若str1=str2,返回零;若str1<str2,返回负数;若str1>str2,返回正数
P = H->Heads[Pos].Next;//从链表的第一个结点开始寻找
while (P && strcmp(P->Data, Key))
P = P->Next;
return P;
}
2.3.3 Insert( ) 在Hash表中插入元素
bool Insert(HashTable H, ElementType Key)
{
Position P, NewNode;
Index Pos;
P = Find(H, Key);
if (!P) //P=NULL 关键词未找到,直接插入
{
NewNode = (Position)malloc(sizeof(struct LinkTableNode));
strcpy(NewNode->Data, Key);
NewNode->Count = 1;
Pos = Hash(Key, H->HashTableSize); //初始化散列位置
NewNode->Next = H->Heads[Pos].Next;//每次在头结点后增加,若插入顺序是1 2 3,链表顺序就是 head-3-2-1
H->Heads[Pos].Next = NewNode;
return true;
}
else
{
P->Count++;
return false;
}
}
2.3.4 ScanAndOutput( ) 扫描Hash表找到目标数据并打印
void ScanAndOutput(HashTable H)
{
int MaxCount = 0, PeopleCount = 0; //PeopleCount代表结果不唯一时的并列人数
ElementType MinPhoneNumber;
List Ptr;
MinPhoneNumber[0] = '\0';
for (int i = 0; i < H->HashTableSize; i++)
{
Ptr = H->Heads[i].Next;
while (Ptr)
{
if (Ptr->Count > MaxCount)
{
MaxCount = Ptr->Count;
strcpy(MinPhoneNumber, Ptr->Data);
PeopleCount = 1;
}
else if (Ptr->Count == MaxCount)
{
PeopleCount++;
if (strcmp(MinPhoneNumber, Ptr->Data) > 0)//strcmp(str1,str2),若str1>str2,返回正数
strcpy(MinPhoneNumber, Ptr->Data); //MinPhoneNumber=Ptr->Data
}
Ptr = Ptr->Next;
}
}
cout << MinPhoneNumber << ' ' << MaxCount;
if (PeopleCount > 1)
cout << ' ' << PeopleCount << endl;
return;
}
2.3.5 DestoryHashTable( ) 释放Hash表占用的内存空间
void DestoryHashTable(HashTable H)
{
Position P, Tmp;
/* 释放每个链表的结点 */
for (int i = 0; i < H->HashTableSize; i++)
{
P = H->Heads[i].Next;
while (P)
{
Tmp = P->Next;
free(P);
P = Tmp;
}
}
free(H->Heads); /* 释放头结点数组 */
free(H); /* 释放散列表结点 */
}
2.3.6 NextPrime( ) 和 Hash( )属于基础编码,在此不单独列出
3. 完整AC编码
如有建议或问题,欢迎留言
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
#define MAX 300000
#define KeyLength 11 //关键字字符串最大长度
typedef char ElementType[KeyLength + 1]; //关键字类型是字符串 11位+'\0'
typedef int Index;//散列地址类型
/*单链表定义*/
typedef struct LinkTableNode* PtrLinkTableNode;
typedef PtrLinkTableNode List;
typedef PtrLinkTableNode Position;
struct LinkTableNode
{
ElementType Data;
PtrLinkTableNode Next;
int Count;
};
//散列表结点定义
typedef struct HashTableNode* HashTable;
struct HashTableNode
{
int HashTableSize;//表最大长度
List Heads; //指向链表头结点的数组
};
HashTable CreateHashTable(int HashTableSize);
Position Find(HashTable H, ElementType Key);
bool Insert(HashTable H, ElementType Key);
int NextPrime(int n);
int Hash(ElementType Key, int TableSize);
void ScanAndOutput(HashTable H);
void DestoryHashTable(HashTable H);
int main()
{
int N; //N represent record number
ElementType Key; //typedef char ElementType[KeyLength+1];
HashTable H;//Define HashTable H
cin >> N;
H = CreateHashTable(N * 2);//Create a Hash Table
for (int i = 0; i < N; i++)
{
cin >> Key; Insert(H, Key); //put Key in HashTable H
cin >> Key; Insert(H, Key); // a record contain two number
}
ScanAndOutput(H);
DestoryHashTable(H);
return 0;
}
void DestoryHashTable(HashTable H)
{
Position P, Tmp;
/* 释放每个链表的结点 */
for (int i = 0; i < H->HashTableSize; i++)
{
P = H->Heads[i].Next;
while (P)
{
Tmp = P->Next;
free(P);
P = Tmp;
}
}
free(H->Heads); /* 释放头结点数组 */
free(H); /* 释放散列表结点 */
}
void ScanAndOutput(HashTable H)
{
int MaxCount = 0, PeopleCount = 0;
ElementType MinPhoneNumber;
List Ptr;
MinPhoneNumber[0] = '\0'; //MinPhoneNumber[0]
for (int i = 0; i < H->HashTableSize; i++)
{
Ptr = H->Heads[i].Next;
while (Ptr)
{
if (Ptr->Count > MaxCount)
{
MaxCount = Ptr->Count;
strcpy(MinPhoneNumber, Ptr->Data);
PeopleCount = 1;
}
else if (Ptr->Count == MaxCount)
{
PeopleCount++;
if (strcmp(MinPhoneNumber, Ptr->Data) > 0)
strcpy(MinPhoneNumber, Ptr->Data);
}
Ptr = Ptr->Next;
}
}
cout << MinPhoneNumber << ' ' << MaxCount;
if (PeopleCount > 1)
cout << ' ' << PeopleCount << endl;
return;
}
HashTable CreateHashTable(int HashTableSize)
{
HashTable H;
H = (HashTable)malloc(sizeof(HashTableNode));
H->HashTableSize = NextPrime(HashTableSize);
//链表头结点数组
H->Heads = (List)malloc(sizeof(struct LinkTableNode) * H->HashTableSize);
for (int i = 0; i < H->HashTableSize; i++)
{
H->Heads[i].Next = NULL;
H->Heads[i].Data[0] = '\0';
H->Heads[i].Count = 0;//计数
}
return H;
}
Position Find(HashTable H, ElementType Key)
{
Position P;
Index Pos;
Pos = Hash(Key, H->HashTableSize); //初始化散列位置
P = H->Heads[Pos].Next;//从链表的第一个结点开始寻找
while (P && strcmp(P->Data, Key))
P = P->Next;
return P;
}
bool Insert(HashTable H, ElementType Key)
{
Position P, NewNode;
Index Pos;
P = Find(H, Key);
if (!P) //P=NULL 关键词未找到,直接插入
{
NewNode = (Position)malloc(sizeof(struct LinkTableNode));
strcpy(NewNode->Data, Key);
NewNode->Count = 1;
Pos = Hash(Key, H->HashTableSize); //初始化散列位置
NewNode->Next = H->Heads[Pos].Next;//每次在头结点后增加,若插入顺序是1 2 3,链表顺序就是 head-3-2-1
H->Heads[Pos].Next = NewNode;
return true;
}
else
{
P->Count++;
return false;
}
}
int Hash(ElementType Key, int TableSize)
{
int k;
k = atoi(Key + KeyLength - 5); //ASCIItoInt,使用11位电话号码的后5位进行哈希
return k % TableSize;
}
int NextPrime(int n)//返回大于n 且 不超过MAX的最小素数
{
int p, i;
p = (n % 2) ? n + 2 : n + 1; //p一定是奇数且比n大
while (p < MAX)
{
for (i = (int)sqrt(p); i > 2; i--)
if (!(p % i))
break;
if (i == 2) break;
else
p += 2;
}
return p;
}