數據結構---散列

散列

此處還未補充關於散列知識

整型關鍵字的散列映射   (25分)

給定一系列整型關鍵字和素數PPP,用除留餘數法定義的散列函數將關鍵字映射到長度爲PPP的散列表中。用線性探測法解決衝突。

輸入格式:

輸入第一行首先給出兩個正整數NNN≤1000\le 10001000)和PPP≥N\ge NN的最小素數),分別爲待插入的關鍵字總數、以及散列表的長度。第二行給出NNN個整型關鍵字。數字間以空格分隔。

輸出格式:

在一行內輸出每個整型關鍵字在散列表中的位置。數字間以空格分隔,但行末尾不得有多餘空格。

輸入樣例:

4 5
24 15 61 88

輸出樣例:

4 0 1 3

解題思路:

按照題中所說的散列函數以及衝突解決方式解題。

注意相同關鍵字時的處理。

提交代碼:

編譯器:g++

#include <iostream>
#include <stdio.h>
using namespace std;
const int MAXN = 1009;
const int INF = -(1<<30);
long long num[MAXN], List[MAXN];
bool Index[MAXN] = {false};
int main()
{
	int n, p;
	scanf("%d%d",&n,&p);
	for(int i = 0; i < p; ++i)
		List[i] = INF;
	for(int i = 0; i < n; ++i)
	{
		scanf("%d",&num[i]);
		int index = num[i] % p;
		if(index < 0) index = (index + p) % p; //除留餘數法
		for(int j = 0; Index[index] && List[index] != num[i]; ++j) //該循環是線性探測
			index = (index + 1) % p;
		List[index] = num[i];
		Index[index] = true;
		num[i] = index;
	}
	for(int i = 0; i < n; ++i)
	{
		if(i) printf(" ");
		printf("%d", num[i]);
	}
	return 0;
} 

字符串關鍵字的散列映射   (25分)

給定一系列由大寫英文字母組成的字符串關鍵字和素數PPP,用移位法定義的散列函數H(Key)H(Key)H(Key)將關鍵字KeyKeyKey中的最後3個字符映射爲整數,每個字符佔5位;再用除留餘數法將整數映射到長度爲PPP的散列表中。例如將字符串AZDEG插入長度爲1009的散列表中,我們首先將26個大寫英文字母順序映射到整數0~25;再通過移位將其映射爲3×322+4×32+6=32063\times 32^2 + 4 \times 32 + 6 = 32063×322+4×32+6=3206;然後根據表長得到,即是該字符串的散列映射位置。

發生衝突時請用平方探測法解決。

輸入格式:

輸入第一行首先給出兩個正整數NNN≤500\le 500500)和PPP≥2N\ge 2N2N的最小素數),分別爲待插入的關鍵字總數、以及散列表的長度。第二行給出NNN個字符串關鍵字,每個長度不超過8位,其間以空格分隔。

輸出格式:

在一行內輸出每個字符串關鍵字在散列表中的位置。數字間以空格分隔,但行末尾不得有多餘空格。

輸入樣例1:

4 11
HELLO ANNK ZOE LOLI

輸出樣例1:

3 10 4 0

輸入樣例2:

6 11
LLO ANNA NNK ZOJ INNK AAA

輸出樣例2:

3 0 10 9 6 1

解題思路:

按照題中例子所說,模擬即可。

注意平方探測法,索引值出現負數時的情況以及關鍵字相同的情況

提交代碼:

編譯器:g++

#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int MAXN = 1009;
struct infi{
	char str[9];
	bool ishave;	
}Hash[MAXN];
int location[MAXN];
int Move[3] = {0, 5, 10};
int main()
{
	int n, p;
	char str[8];
	scanf("%d%d", &n, &p);
	for(int i = 0; i < n; ++i)
	{
		scanf("%s", str);
		int len = strlen(str);
		int key = 0;
		for(int j = 0; j < 3 && len - j - 1 >= 0; ++j) //映射到一個數字
		{
			key += (str[len - j - 1] - 'A') * (1 << Move[j]);
		}
		key = key % p; //除留餘數法
		for(int j = 0; true; ++j) //平方探測法
		{
			int di = (j * j) % p; //係數
			int tmpkey = (key + di) %p; //正方向探測
			if(!Hash[tmpkey].ishave)
			{
				location[i] = tmpkey, Hash[tmpkey].ishave = true;
				strcpy(Hash[tmpkey].str, str);
				break;
			}
			else if(strcmp(Hash[tmpkey].str, str) == 0)
			{
				location[i] = tmpkey;
				break;
			}
			tmpkey = (key - di) % p; //負方向探測
			if(tmpkey < 0) tmpkey = (tmpkey + p) % p;
			if(!Hash[tmpkey].ishave)
			{
				location[i] = tmpkey, Hash[tmpkey].ishave = true;
				strcpy(Hash[tmpkey].str, str);
				break;
			}
			else if(strcmp(Hash[tmpkey].str, str) == 0)
			{
				location[i] = tmpkey;
				break;
			}
		}
	}
	for(int i = 0; i < n; ++i)
	{
		if(i) printf(" ");
		printf("%d", location[i]);
	}
	return 0;
}

電話聊天狂人   (25分)

給定大量手機用戶通話記錄,找出其中通話次數最多的聊天狂人。

輸入格式:

輸入首先給出正整數NNN≤105\le 10^5105),爲通話記錄條數。隨後NNN行,每行給出一條通話記錄。簡單起見,這裏只列出撥出方和接收方的11位數字構成的手機號碼,其中以空格分隔。

輸出格式:

在一行中給出聊天狂人的手機號碼及其通話次數,其間以空格分隔。如果這樣的人不唯一,則輸出狂人中最小的號碼及其通話次數,並且附加給出並列狂人的人數。

輸入樣例:

4
13005711862 13588625832
13505711862 13088625832
13588625832 18087925832
15005713862 13588625832

輸出樣例:

13588625832 3

解題思路:

用除留餘數法定義散列函數。

此處使用拉鍊法解決衝突,因此使用數組指針。

提交代碼:

編譯器:g++

#include <iostream>
using namespace std;
const int MAXN = 100003;
typedef struct infi node;
struct infi{
    long long num;
    int cnt;
    node * next;
};
node *L[MAXN];
int Find(long long n);
int main()
{
    int n;
    int peo = 0, cnt = 0;
    long long n1, n2, Num = 0;
    scanf("%d", &n);
    for(int i = 0; i < n; ++i)
    {
        scanf("%lld %lld", &n1, &n2);
        int cnt1 = Find(n1);
        int cnt2 = Find(n2); //散列操作
        if(cnt1 < cnt2) //一下兩個if語句確定最大值
            n1 = n2, cnt1 = cnt2;
        if(cnt1 > cnt)
            cnt = cnt1, Num = n1;
    }
    for(int i = 0; i < MAXN; ++i)
    {
        while(L[i])
        {
            if(L[i]->cnt == cnt)
            {
                peo++;
                if(Num > L[i]->num)
                    Num = L[i]->num;
            }
            node *tmp = L[i];
            L[i] = L[i]->next;
            delete tmp;
        }
    }
    if(peo <= 1) printf("%lld %d\n", Num, cnt);
    else printf("%lld %d %d\n", Num, cnt, peo);
    return 0;
}
int Find(long long n)
{
    int index = n % MAXN, cnt = 0;
    node *tmp = L[index];
    if(tmp == NULL)
    {
        tmp = new node;
        tmp->next = NULL;
        tmp->cnt = 1;
        tmp->num = n;
        L[index] = tmp;
        return 1;
    }
    while(tmp->next != NULL && tmp->num != n)
    {
        tmp = tmp->next;
    }
    if(tmp == NULL)
    {
        node *p = new node;
        p->num = n;
        p->next = NULL;
        p->cnt = 1;
        tmp->next = p;
        cnt = 1;
    }
    else if(tmp->num == n)
    {
        tmp->cnt++;
        cnt = tmp->cnt;
    }
    return cnt;
}

QQ帳戶的申請與登陸   (25分)

實現QQ新帳戶申請和老帳戶登陸的簡化版功能。最大挑戰是:據說現在的QQ號碼已經有10位數了。

輸入格式:

輸入首先給出一個正整數NNN≤105\le 10^5105),隨後給出NNN行指令。每行指令的格式爲:“命令符(空格)QQ號碼(空格)密碼”。其中命令符爲“N”(代表New)時表示要新申請一個QQ號,後面是新帳戶的號碼和密碼;命令符爲“L”(代表Login)時表示是老帳戶登陸,後面是登陸信息。QQ號碼爲一個不超過10位、但大於1000(據說QQ老總的號碼是1001)的整數。密碼爲不小於6位、不超過16位、且不包含空格的字符串。

輸出格式:

針對每條指令,給出相應的信息:

1)若新申請帳戶成功,則輸出“New: OK”;
2)若新申請的號碼已經存在,則輸出“ERROR: Exist”;
3)若老帳戶登陸成功,則輸出“Login: OK”;
4)若老帳戶QQ號碼不存在,則輸出“ERROR: Not Exist”;
5)若老帳戶密碼錯誤,則輸出“ERROR: Wrong PW”。

輸入樣例:

5
L 1234567890 [email protected]
N 1234567890 [email protected]
N 1234567890 [email protected]
L 1234567890 myQQ@qq
L 1234567890 [email protected]

輸出樣例:

ERROR: Not Exist
New: OK
ERROR: Exist
ERROR: Wrong PW
Login: OK

解題思路:

本題參照上題思路,

具體還要考慮題中的L操作,對應可能有三種結果

題中的N操作,對應有兩種結果。

提交代碼:

編譯器:g++

#include <iostream>
#include <string.h>
using namespace std;
const int MOD = 100005;
typedef struct infi info;
struct infi{
	long long ID;
	char pass[21];
	info *next;
};
infi *count[MOD];
int cnt[MOD];
int main()
{
	int n;
	char com[3], pw[15];
	long long num;
	scanf("%d", &n);
	for(int i = 0; i < n; ++i)
	{
		scanf("%s %lld %s", com, &num, pw);
		int index = num % MOD; //除留餘數法
		if(com[0] == 'L')
		{
			info *tmp = count[index];
			bool isexit = false;
			bool islaw = true;
			while(tmp && !isexit) //查找是否存在賬號
			{
				if(tmp->ID == num) //檢驗用戶名
				{
					isexit = true;
					if(strcmp(tmp->pass, pw) != 0) //判斷密碼是否正確
						islaw = false;
				}
				tmp = tmp->next;
			}
			if(!isexit) printf("ERROR: Not Exist\n"); //賬號不存在
			else
			{
				if(islaw) printf("Login: OK\n"); //賬號密碼都正確
				else printf("ERROR: Wrong PW\n"); //賬號正確,密碼錯誤
			}
		}
		else
		{
			info *tmp = count[index];
			bool isexit = false;
			while(tmp && !isexit) //查找賬號
			{
				if(tmp->ID == num)
					isexit = true;
				tmp = tmp->next;
			}
			if(isexit) printf("ERROR: Exist\n");
			else //新建賬號
			{
				info *p = new info;
				p->ID = num;
				strcpy(p->pass, pw);
				p->next = NULL;
				if(cnt[index])
				{
					tmp = count[index];
					while(tmp->next)
						tmp = tmp->next;
					tmp->next = p;
				}
				else
					count[index] = p;
				cnt[index]++;
				printf("New: OK\n");
			}
		}
        }
	return 0;
}

航空公司VIP客戶查詢   (25分)

不少航空公司都會提供優惠的會員服務,當某顧客飛行里程累積達到一定數量後,可以使用里程積分直接兌換獎勵機票或獎勵升艙等服務。現給定某航空公司全體會員的飛行記錄,要求實現根據身份證號碼快速查詢會員里程積分的功能。

輸入格式:

輸入首先給出兩個正整數NNN≤105\le 10^5105)和KKK≤500\le 500500)。其中KKK是最低里程,即爲照顧乘坐短程航班的會員,航空公司還會將航程低於KKK公里的航班也按KKK公里累積。隨後NNN行,每行給出一條飛行記錄。飛行記錄的輸入格式爲:18位身份證號碼(空格)飛行里程。其中身份證號碼由17位數字加最後一位校驗碼組成,校驗碼的取值範圍爲0~9和x共11個符號;飛行里程單位爲公里,是(0, 15 000]區間內的整數。然後給出一個正整數MMM≤105\le 10^5105),隨後給出MMM行查詢人的身份證號碼。

輸出格式:

對每個查詢人,給出其當前的里程累積值。如果該人不是會員,則輸出No Info。每個查詢結果佔一行。

輸入樣例:

4 500
330106199010080419 499
110108198403100012 15000
120104195510156021 800
330106199010080419 1
4
120104195510156021
110108198403100012
330106199010080419
33010619901008041x

輸出樣例:

800
15000
1000
No Info

解題思路:

此題中將身份證作爲關鍵字,將身份證編號轉化爲11進制的數字(X代表10)

然後用除留餘數法定義散列函數,用拉鍊法解決衝突。

提交代碼:

編譯器:g++

#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int MAXN = 100009;
typedef struct infi info;
struct infi{
    char ID[21];
    int totKM;
    info *next;
};
info peo[MAXN];
long long getIndex(char *str); //將身份證映射爲數字
int main()
{
    int n, k, m;
    int KM;
    char ID[21];
    scanf("%d %d", &n, &k);
    for(int i = 0; i < n; ++i)
    {
        scanf("%s %d", ID, &KM);
        if(KM < k) KM = k;
        int index = (int) (getIndex(ID) % MAXN); //除留餘數法
        if(peo[index].next == NULL) //拉鍊法
        {
            info *tmp = new info;
            tmp->next = NULL;
            strcpy(tmp->ID, ID);
            tmp->totKM = KM;
            peo[index].next = tmp;
        }
        else
        {
            info *tmp = peo[index].next;
            while(strcmp(tmp->ID, ID) && tmp->next)
                tmp = tmp->next;
            if(strcmp(tmp->ID, ID) == 0)
                tmp->totKM += KM;
            else
            {
                info *p = new info;
                p->next = NULL;
                p->totKM = KM;
                strcpy(p->ID, ID);
                tmp->next = p;
            }
        }
    }
    scanf("%d", &m); //查找
    for(int i = 0; i < m; ++i)
    {
        scanf("%s", ID);
        int index = (int)(getIndex(ID) % MAXN);
        info *tmp = peo[index].next;
        while(tmp)
        {
        	if(strcmp(tmp->ID, ID) == 0)
                break;
            tmp = tmp->next;
		}
        if(tmp) printf("%d\n", tmp->totKM);
        else printf("No Info\n");
    }
    return 0;
}
long long getIndex(char *str)
{
    long long num = 0;
    for(int i = 0; i < 17; ++i)
        num = num * 10 + (str[i] - '0');
    if(str[17] != 'x') num += (str[17] - '0');
    else num += 10;
    return num;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章