YAZONG 我的开源

(数据结构)第十一讲-散列查找(11.6 应用实例:通话记录)

  , ,
0 评论0 浏览

题意理解与解法分析

image.png

N=4,下面是4条通话记录,一条通话记录两个号码。

把通话记录最多的号码和次数输出。

解法1-排序(N^2LogN)

image.png

最大的N=10^5,每条记录两个号码。

重复出现的号码肯定都排在一起的。

从头到尾去扫描有序数组,相同号码肯定是连续出现的,需要变量统计出现的次数。

此解法并不好,通话记录并不是一成不变的,每时每刻都有通话,每来一条通话都要找一次的话,都要做一个NLogN的排序,如果要做N次,那么算法复杂度就是N^2LogN。

解法2-直接映射(N^2LogN)

image.png

目前为止电话号码的第一位都是1,电话号码11位,一条通话记录是两个电话号码,所以

2*10^10足够了。

平时用到的整数是unsigned long长度是10位,但电话号码11位,用long long吗?

即使把每个整数都记成短整数,开辟此数组,也大约需要40GB的内存空间。

找号码多花了2*10^5倍的时间。

那么算法复杂度是N^2LogN。

虽然带了散列的概念,但是过分的简单粗暴。

解法3-分离链接法(O(N))

image.png

网络识别号不适合在散列函数中计算,因为种类太少。

地区编码同上。

用户号码是随机的,适合作为散列函数计算,但效果不太好,再取地区编码的最后一位和用户号码一起求散列计算。

散列头素数P存散列表的长度(散列表头结点的个数),应该略大于2N(号码个数)的素数。

比如后五位第五位元素是2,那么插入数组下标2的位置中去,第一个元素直接插入链表,第二个元素后五位如果冲突,那么插入链表的表头,第三个元素如果重复出现,那么会先扫描这个链表,如果找到相同号码,则此号码计数器+1,否则,继续插入表头。当然希望是链表表长不太长,冲突不多,所以整体效率还是线性的,O(N)数量级。

源码

image.png

/*
散列查找-分离链接法-电话号码
*/
#include<stdio.h>
#include<math.h>

/* 关键词字符串的最大长度 */
#define KEYLENGTH 11
/* 
关键词类型用字符串 
C语言字符串末尾的'\0'位置
*/
#define char ElementType[KEYLENGTH + 1];
/* 散列地址类型 */
typedef int Index;

typedef struct LNode *PtrToLNode;
struct LNode{
	ElementType Data;
	PtrToLNode Next;
	int Count;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;

typedef struct TblNode *HashTable;
/* 散列表结点定义 */
struct TblNode{
	/* 表的最大长度 */
	int TableSize;
	/* 指向链表头结点的数组 */
	List Heads;
};

int main(){

	int N, i;
	ElementType Key;

	HashTable H;

	scanf("%d", &N);

	/*创建一个散列表*/
	H = CreateTable(N * 2);
	/*每次读进来一对*/
	for(i = 0; i < N; i++){
		scanf("%s", Key); Insert(H, Key);
		scanf("%s", Key); Insert(H, Key);
	}
	/*
	扫描整个散列表:
	更新最大通话次数.
	更新最小号码+统计人数.
	*/
	ScanAndOutput(H);
	/*释放散列表的空间*/
	DestroyTable(H);

	return 0;
}

#define MAXTABLESIZE 1000000
/* 返回大于N且不超过MAXTABLESIZE的最小素数*/
int NextPrime(int N){
	/*从大于N的下一个奇数开始*/
	int i, p = (N % 2) ? (N + 2) : (N + 1);
	/*要返回大于给定的N,并且不超过指定的素数.*/
	while(p <= MAXTABLESIZE){
		for(i = (int)sqrt(p); i > 2; i--){
			if(!(p % i)){
				/* p不是素数*/
				break;
			}
			if(i == 2){
				/* for正常结束,说明p是素数*/
				break;
			}else{
				/* 否则试探下一个奇数*/
				p += 2;
			}
		}
	}
	return p;
}

/* 
除留余数法法散列函数
Key是整数:手机号码后五位已经截取出来并变成了整数
*/
int Hash(int Key, int P){
	return Key % P;
}

/*
创建一个散列表
生成一个比记录大小更大的素数
传进来的是用户指定的数组大小2N
*/
HashTable CreateTable(int TableSize){
	HashTable H;
	int i;
	H = (HashTable)malloc(sizeof(struct TblNode));
	H->TableSize = NextPrime(TableSize);
	H->Heads = (List)malloc(H->TableSize * sizeof(struct LNode));
	for(i = 0; i < H->TableSize; i++){
		H->Heads[i].Data[0] = '\0';
		H->Heads[i].Next = NULL;
		/*初始化变量头结点的计数器,好的习惯.*/
		H->Heads[i].Count = 0;
	}
	return H;
}

/*参数散列映射计算的字符个数*/
#define MAXD 5
Position Find(HashTable H, ElementType Key){
	Position P;
	Index Pos;
	/* 
	初始散列位置 
	Key指向电话号码的第一位
	KEYLENGTH电话号码一共11位长度
	MAXD字符个数5
	*/
	Pos = Hash(atoi(Key + KEYLENGTH - MAXD), H->TableSize);
	/* 从该链表的第1个结点开始 */
	P = H->Heads[Pos].Next;
	/* 当未到表尾,并且Key未找到时 */
	while(P && strcmp(P->Data, Key)){
		P = P->Next;
	}
	/* 此时P或者指向找到的结点,或者为NULL */
	return P;
}

bool insert(HashTable H, ElementType Key){
	Position P, NewCell;
	Index Pos;
	P = Find(H, Key);
	/* 关键词未找到,可以插入 */
	if(!P){
		NewCell = (Position)malloc(sizeof(struct LNode));
		strcpy(NewCell->Data, Key);
		NewCell->Count = 1;
		/* 
		初始散列位置 
		Key指向电话号码的第一位
		KEYLENGTH电话号码一共11位长度
		MAXD字符个数5
		*/
		Pos = Hash(atoi(Key + KEYLENGTH - MAXD),H->TableSize);
		/* 将NewCell插入为H->Heads[Pos]链表的第1个结点 */
		NewCell->Next = H->Heads[Pos].Next;
		H->Heads[Pos].Next = NewCell;
		return true;
	}else{
		/* 关键词已存在,打印而是计数器+1. */
		P->Count++;
		return false;
	}

}


void ScanAndOutput(HashTable H){
	int i, MaxCnt = PCnt = 0;
	ElementType MinPhone;
	List Ptr;
	MinPhone[0] = '\0';
	/* 扫描链表*/
	for(i = 0; i < H->TableSize; i++){
		Ptr = H->Heads[i].Next;
		while(Ptr){
			/* 更新最大通话次数*/
			if(Ptr->Count > MaxCnt){
				MaxCnt = Ptr->Count;
				strcpy(MinPhone, Ptr->Data);
				PCnt = 1;
			}else if(Ptr->Count == MaxCnt){
				PCnt++;
				if(strcmp(MinPhone, Ptr->Data) > 0)){
					/* 更新狂人的最小手机号码*/
					strcpy(MinPhone, Ptr->Data);
				}
			}
			Ptr = Ptr->Next;
		}
	}
	printf("%s %d", MinPhone, MaxCnt);
	if(PCnt > 1){
		printf("%d", PCnt);
	}
	printf("\n");
}


/*释放散列表的空间*/
void DestroyTable(HashTable H){

	int i;
	Position P, Tmp;
	/* 释放每个链表的结点 */
	for(i = 0; i < H->TableSize; i++){
		P = H->Heads[i].Next;
		while(P){
			Tmp = P->Next;
			free(P);
			P = Tmp;
		}
	}
	/* 释放头结点数组 */
	free(H->Heads);
	/* 释放散列表结点 */
	free(H);

}


标题:(数据结构)第十一讲-散列查找(11.6 应用实例:通话记录)
作者:yazong
地址:https://blog.llyweb.com/articles/2023/10/22/1697987142429.html