数据结构(六)散列查找 —— 编程作业01 :电话聊天狂人

数据结构系列内容的学习目录 → \rightarrow 浙大版数据结构学习系列内容汇总

  题目描述: 给定大量手机用户通话记录,找出其中通话次数最多的聊天狂人。

  输入格式: 输入首先给出正整数N(≤10 ​ 5 ​^5 5​ ​),为通话记录条数。
        随后N行,每行给出一条通话记录。
        简单起见,这里只列出拨出方和接收方的11位数字构成的手机号码,其中以空格分隔。

  输出格式: 在一行中给出聊天狂人的手机号码及其通话次数,其间以空格分隔。
        如果这样的人不唯一,则输出狂人中最小的号码及其通话次数,并且附加给出并列狂人的人数。

  输入样例:

4
13005711862 13588625832
13505711862 13088625832
13588625832 18087925832
15005713862 13588625832

  输出样例:

13588625832 3

  解题思路:

在这里插入图片描述
   ★ \bigstar 解法1:排序
         第1步:读入最多2 × \times × 10 5 ^5 5个电话号码,每个号码存为长度为11的字符串;
         第2步:按字符串非递减顺序排序;
         第3步:扫描有序数组,累计同号码出现的次数,并且更新最大次数。

    优点: 编程简单快捷。
    缺点: 无法拓展解决动态插入的问题。

   ★ \bigstar 解法2:直接映射
          第1步:创建有2 × \times × 10 10 ^{10} 10个单元的整数数组,保证每个电话号码对应唯一的单元下标,数组初始化为0;
          第2步:对读入的每个电话号码,找到以之为下标的单元,数值累计1次;
          第3步:顺序扫描数组,找出累计次数最多的单元。

    优点: 编程简单快捷,动态插入快。
    缺点: 下标超过了unsigned long
         需要2 × \times × 10 10 ^{10} 10 × \times × 2 bytes ≈ \approx 37GB;
         为了2 × \times × 10 5 ^{5} 5个号码扫描2 × \times × 10 10 ^{10} 10 个单元。

   ★ \bigstar 解法3:带智商的散列

在这里插入图片描述
   ★ \bigstar 程序框架搭建:

int main()
{
    创建散列表;
    读入号码插入表中;
    扫描表输出狂人;
    return 0;
}
int main ()
{ 
    int N;
    ElementType Key;
    HashTable H;
    cin >> N;
    H = CreateTable(N*2); // 创建一个散列表
    for (int i = 0; i < N; i++){
        cin >> Key; 
        Insert( H,Key );
        cin >> Key; 
        Insert( H,Key ) ;
    }
    ScanAndOutput(H);
    DestroyTable(H) ;
    return 0;
}

  实现CreateTable()Insert()函数时需要的准备如下图所示。

在这里插入图片描述
  ScanAndOutput()用于扫描整个散列表: 更新最大通话次数;
                    更新最小号码+统计人数。

  代码实现:

#include<iostream>
#include<string>
using namespace std;
#define MAXTABLESIZE 1000000

typedef string ElementType;
typedef struct LNode *List;
struct LNode {  // 单链表 
	ElementType number;  // 电话号码 
	int Count;  // 计数 
	List Next;
};

typedef struct HashTbl *HashTable;
struct HashTbl {   // 哈希表 
	int TableSize;  // 哈希表大小
	List Heads;   // 头结点 
};

// 除留余数法哈希函数 
int Hash(int key, int p) 
{
	return key % p;
}

// 查找素数 
int NextPrime(int N) 
{
	int p = (N % 2) ? N + 2 : N + 1;
	int i;
	while (p <= MAXTABLESIZE) 
	{
		for (i = (int)sqrt(1.0*p); i > 2; i--)
			if (!(p%i))   // 不是素数 
				break;
		if (i == 2)  // 找到了 
			break;
		p += 2;  // 去找下个素数 
	}
	return p;
}

// 初始化哈希表 
HashTable CreateTable(int TableSize) 
{
	HashTable H;
	H = (HashTable)malloc(sizeof(struct HashTbl));
	H->TableSize = NextPrime(TableSize);

	// 结构体中含字符串必须动态分配地址空间 
	H->Heads = new LNode[H->TableSize];
	for (int i = 0; i < H->TableSize; i++) 
	{
		H->Heads[i].Next = NULL;
		H->Heads[i].Count = 0;
	}
	return H;
}

// 查找 
List Find(HashTable H, ElementType key)
{
	// 以电话号码最后五位散列
	int pos = Hash(atoi(key.substr(6, 5).c_str()), H->TableSize);

	List p = H->Heads[pos].Next;
	while (p && key != p->number)
		p = p->Next;
	return p;
}

// 插入 
bool Insert(HashTable H, ElementType key) 
{
	List p, NewCell;
	int pos;
	p = Find(H, key);
	if (!p)  // p为空,关键词未找到,可以插入
	{  
		NewCell = new LNode();
		NewCell->number = key;
		NewCell->Count = 1;
		pos = Hash(atoi(key.substr(6, 5).c_str()), H->TableSize);  // 找到散列地址 

		// 将新结点插入到链表头 
		NewCell->Next = H->Heads[pos].Next;
		H->Heads[pos].Next = NewCell;
		return true;
	}
	else 
	{
		p->Count++;   // 已经存在,计数+1 
		return false;
	}
}

// 扫描整个散列表
void ScanAndOutput(HashTable H) 
{
	List p;
	int MaxCnt = 0;  // 最大通话次数 
	string MinPhone; // 最小号码 
	int PCnt = 0;   // 并列人数 
	// 扫描链表 
	for (int i = 0; i < H->TableSize; i++) 
	{
		p = H->Heads[i].Next;
		while (p) 
		{
			if (p->Count > MaxCnt)   // 更新最大通话次数
			{
				MaxCnt = p->Count;
				MinPhone = p->number;
				PCnt = 1;
			}
			else if (p->Count == MaxCnt) 
			{
				PCnt++;  //狂人计数
				if (p->number < MinPhone)  //更新狂人的最小手机号码
					MinPhone = p->number;
			}
			p = p->Next;

		}
	}
	cout << MinPhone << " " << MaxCnt;
	if (1 < PCnt)
		cout << " " << PCnt;
	cout << endl;
}

int main() 
{
	HashTable H;
	int N;
	ElementType key;
	cin >> N;
	H = CreateTable(2 * N);
	for (int i = 0; i < N; i++) 
	{
		cin >> key;		
		Insert(H, key);
		cin >> key;		
		Insert(H, key);
	}
	ScanAndOutput(H);
	system("pause");
	return 0;
}

  测试: 输入样例的测试效果如下图所示。

在这里插入图片描述

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值