整型关键字的散列映射

题目: https://pintia.cn/problem-sets/15/problems/889

思路: 简单的散列映射,直接上代码

 

#include <iostream>
using namespace std;

int hashTable[1005];
int main() {
    int n,p; cin>>n>>p;
    for(int i = 0;i < n;i++){
        int c;cin>>c;
        int s = c % p;
        while(hashTable[s] != c && hashTable[s]) s = (s+1) % p;
        hashTable[s] = c;
        cout<<s<<(i == n-1 ? "\n" : " ");
    }
    return 0;
}

 

<think>好的,我现在需要帮助用户解决如何在C语言中实现整型关键字散列映射的问题。首先,用户提到了使用除留余数法作为散列函数,线性探测解决冲突,并且参考了PTA的相关内容。我需要结合提供的引用资料,特别是引用[1]和[3],来构建一个正确的实现。 首先,我得回忆一下散列表的基本结构。散列表需要一个数组来存储数据,可能还需要记录每个位置的状态(比如是否为空、是否被删除)。根据引用[3],表长应选择大于且最接近元素数量的质数,这里用户给定的素数P可能就是表长,所以数组的大小应该为P。 接下来是散列函数的确定。根据引用[1],H(Key) = Key % P。这很简单,直接将关键字对P取模即可得到初始位置。 然后处理冲突的方法是用线性探测,也就是当发生冲突时,依次检查下一个位置,直到找到空槽。需要注意的是,当探测到数组末尾时,需要绕回到开头继续查找。这可以通过(pos + 1) % P来实现。 现在考虑数据结构。需要定义一个结构体来表示散列表的每个单元,包括存储的值和状态(例如,空、已占用、已删除)。不过用户的问题中可能只需要处理插入和查找,状态可能简化为是否存在值。但根据线性探测的要求,必须区分空槽和已被占用的槽,特别是当删除存在时,但根据问题描述,可能只需要插入和查找,所以状态可能用0表示空,1表示已占用。或者,可以用一个特殊值(比如-1)表示空,假设关键字都是非负整数,但题目中没有说明,所以可能需要更通用的方法。 不过引用[1]中的输入格式未提及删除操作,所以可能不需要处理删除状态,只需初始化为一个不可能的值,比如-1,表示空槽。初始化时将所有位置设为-1。 然后插入过程的步骤应该是: 1. 计算初始位置H(Key) = Key % P。 2. 如果该位置为空(值为-1),则插入。 3. 如果不为空,则线性探测下一个位置,直到找到空槽或已经存在相同的Key(根据PTA题目要求可能需要处理重复关键字的情况?比如是否需要覆盖或忽略重复?这里可能需要看题目具体要求。例如,在PTA的问题中,可能要求如果关键字已存在,则不插入,或者如何处理。但用户没有具体说明,所以可能假设关键字可以重复,或者按照题目中的示例处理。比如,假设题目中要求如果关键字已经存在,则仍然需要插入,但根据线性探测的规则,可能需要找到下一个可用位置?或者可能题目要求相同的关键字应视为冲突,但用户的问题中可能没有明确这一点。这里可能需要根据常规做法,即当插入的key已经存在于散列表中时,是否需要处理。例如,PTA可能要求如果存在相同的Key,则不需要再次插入,或者如何处理?例如,题目可能要求将所有相同的Key都存储,但根据线性探测的规则,可能不会,因为相同的Key经过散列函数会得到相同的位置,所以可能遇到冲突,需要探测下一个位置。但具体要看题目的要求。假设题目中允许重复的Key存在,但需要正确存储,这可能不太合理,因为通常散列表的Key应该是唯一的。可能用户的问题中的关键字是唯一的,或者处理冲突时不考虑重复,只是存储。这时候需要明确如何处理。但根据用户给出的引用[1]中的描述,可能题目中的输入是一系列整型关键字,可能存在重复,但需要将它们插入到散列表中。例如,当插入重复的关键字时,是否需要覆盖,或者仍然按照冲突处理,存储到下一个位置?比如,假设题目中的要求是按线性探测处理,无论是否重复,都存储到下一个可用位置。这可能更符合一般情况。例如,即使Key相同,冲突时仍然继续探测,直到找到空槽。但这时候查找的时候,可能会存在相同的Key分布在不同的位置。这可能不符合散列表的常规用法,但根据题目要求可能需要这样处理。例如,假设题目中的输入可能包含重复的关键字,每个都需要插入到表中,但根据散列函数和冲突解决策略找到位置。这可能不太常见,但需要按照用户的问题来处理。 不过可能题目中的情况是,不同的关键字可能映射到同一位置,因此需要处理冲突,而相同的Key应该被视为同一个,所以当插入相同的Key时,可能不需要重复插入。例如,在PTA的问题中,可能输入的关键字可能有重复,但需要将它们视为相同的元素,因此当插入时,如果Key已经存在,则不需要插入。或者可能题目要求无论是否重复,都要插入到散列表中。这需要进一步分析引用[1]的内容。根据引用[1]中的描述:“输入格式: [^1]”,可能原题中的输入可能有重复的关键字,但如何处理需要看题目具体要求。例如,假设题目中要求,如果关键字已经存在于散列表中,则不需要再次存储,或者按照冲突处理继续存储。这会影响实现的逻辑。例如,在代码中,当插入一个Key时,需要检查当前位置的Key是否等于当前要插入的Key,如果是,则返回或跳过;否则继续探测。 例如,假设PTA题目要求的是,如果插入的Key已经存在于散列表中,则忽略该Key,否则插入到找到的第一个空槽。或者,可能题目允许重复的Key存在,此时需要正确探测。因此,在代码中,当进行插入时,需要遍历散列表的位置,如果在探测过程中遇到相同的Key,则可能不需要插入(如题目要求不允许重复),或者继续插入(允许重复)。但根据常规的散列表设计,Key应该是唯一的。所以这可能取决于题目具体要求。由于用户的问题没有明确这一点,可能需要假设处理重复的情况,即如果Key已经存在,则不需要插入,或者需要覆盖。这时候可能需要参考引用[1]中的问题描述。 但用户给出的引用[1]中的描述是:“整型关键字散列映射。给定一系列整型关键字和素数P,用除留余数法定义的散列函数H(Key)=Key将关键字映射到长度为P的散列表中。用线性探测法解决冲突。”输入格式可能包含重复的关键字,如何处理? 例如,假设在PTA问题中,当遇到重复的关键字时,只需要输出其位置,无需插入。因此,在实现时,当插入过程中发现某个位置已经有相同的Key,则直接返回该位置,不进行插入。或者,每次插入都强制将Key放入散列表中,不管是否重复。这可能影响代码逻辑。 例如,假设用户的问题中的输入可能包含重复的关键字,但每个都需要插入到散列表中,此时必须找到下一个可用的位置,即使已经存在相同的Key。这可能不符合常规,但可能题目要求如此。或者,如果题目不允许重复,则遇到相同的关键字时,需要停止探测并返回已有位置。因此,我需要更仔细地分析问题。 可能用户的问题来自PTA的题目,例如,题目可能要求,在插入时,如果关键字已经存在,则不需要再次插入,并返回其已经存在的位置。例如,在PTA的题目描述中,输入可能是一系列关键字,要求将每个关键字插入到散列表中,并输出其位置,如果该关键字已经存在,则输出其第一次出现的位置。或者,当插入时,如果遇到相同的Key,是否要覆盖或者继续处理? 例如,假设在PTA题目中,当遇到相同的Key时,只需要记录第一次出现的位置,之后出现的相同Key不需要插入,直接输出第一次的位置。这会影响插入函数的逻辑。例如,在插入时,检查整个探测路径,如果发现相同的Key,则返回该位置,否则插入到空的位置。 这种情况下,插入函数需要遍历可能的探测位置,检查是否有相同的Key,如果有,则返回该位置,否则插入到第一个空的位置。因此,在代码实现中,插入函数需要先查找Key是否已经存在,如果存在则返回其位置,否则找到空的位置插入。 例如,实现步骤如下: 对于每个要插入的Key: 1. 计算初始位置pos = Key % P。 2. 从pos开始,线性探测,即依次检查pos, pos+1, ..., P-1, 0, 1, ..., pos-1。 3. 在探测过程中,如果遇到某个位置存储的值等于Key,则返回该位置,不插入。 4. 如果遇到空的位置(值为-1),则将Key插入到该位置,并返回该位置。 5. 如果整个表已满(根据装填因子,但题目可能保证不会出现这种情况),可能需要处理错误,但题目中的素数P足够大,或者题目保证输入合法。 因此,代码的结构应包括: - 初始化散列表为一个大小为P的数组,初始值为-1(表示空)。 - 插入函数:输入Key,返回其存储的位置。 现在,我需要将这些逻辑转化为C语言代码。 例如,代码的结构可能是: #include <stdio.h> #include <stdlib.h> #define EMPTY -1 typedef struct { int *table; int size; } HashTable; HashTable* createHashTable(int P) { HashTable* ht = (HashTable*)malloc(sizeof(HashTable)); ht->size = P; ht->table = (int*)malloc(P * sizeof(int)); for (int i = 0; i < P; i++) { ht->table[i] = EMPTY; } return ht; } int insert(HashTable* ht, int key) { int pos = key % ht->size; int originalPos = pos; do { if (ht->table[pos] == key) { // 已存在相同关键字 return pos; } if (ht->table[pos] == EMPTY) { ht->table[pos] = key; return pos; } pos = (pos + 1) % ht->size; } while (pos != originalPos); // 防止表满无限循环 // 表已满,无法插入(根据题目假设,可能不会出现这种情况) return -1; } int main() { int P, N; scanf("%d %d", &P, &N); // 假设输入P为表长,N为关键字数量 HashTable* ht = createHashTable(P); for (int i = 0; i < N; i++) { int key; scanf("%d", &key); int pos = insert(ht, key); printf("%d", pos); if (i != N - 1) printf(" "); } free(ht->table); free(ht); return 0; } 但需要验证这段代码的正确性。例如,当插入一个已经存在的Key时,返回已存在的位置。例如,假设插入顺序是10, 20, 10,当第一次插入10,位置是10%P,假设P是足够大的素数。第二次插入20,根据其散列位置。第三次插入10时,会在初始位置找到已存在的10,直接返回该位置。 但需要注意,初始的pos计算是否正确。例如,当key是负数时,key % P的结果可能为负数,这在C语言中取模的结果与处理方式有关。例如,在C语言中,负数的取模结果符号取决于编译器,但通常我们可以调整它,使其变为正数。例如,修改pos的计算方式: pos = key % P; if (pos < 0) pos += P; 或者在散列函数中处理负数的key,确保pos是正数。但用户的问题中的关键字整型,可能包括负数。因此,在代码中需要处理负数的情况。 例如,修改insert函数中的pos计算: int insert(HashTable* ht, int key) { int pos = key % ht->size; if (pos < 0) pos += ht->size; // 处理负数的情况 int originalPos = pos; do { if (ht->table[pos] == key) { return pos; } if (ht->table[pos] == EMPTY) { ht->table[pos] = key; return pos; } pos = (pos + 1) % ht->size; } while (pos != originalPos); return -1; } 这样处理可以确保pos是有效的索引。 此外,关于散列表的大小是否为素数,用户给定的P是素数,所以在创建时直接使用P作为表长。因此,在代码中,创建散列表时,size设为P。 测试案例:假设P=7,插入关键字序列为:16, 23, 40, 23, 16。 散列函数H(key) = key % 7。 16%7=2,23%7=2(余2),40%7=5(余5?40 ÷7=5*7=35余5,是的)。 插入16:位置2,存入。 插入23:位置2冲突,线性探测到3,存入。 插入40:位置5,存入。 插入23:探测到位置2的值为16,不是23;位置3的值为23,存在,返回3。 插入16:位置2的值是16,存在,返回2。 输出顺序应为2 3 5 3 2。 这可能正确。 现在,用户的问题可能还涉及输入输出的格式。例如,输入的第一行是P和N,然后是N个关键字,输出每个关键字插入后的位置,用空格分隔。 上述代码中的main函数处理了这种情况,读取P和N,然后逐个插入并输出位置。 但需要注意,EMPTY的值设为-1,假设所有关键字都是非负的?或者用户的问题中的关键字可能包含负数? 例如,如果关键字-5,那么计算pos = (-5) % P,在C语言中,这取决于具体的实现。例如,在C中,负数取模的结果符号与被除数一致,所以-5%7= -5,但通过pos += ht->size后,pos = 2,这样是正确的。例如,-5 mod 7等于2,所以正确的pos应该是2。因此,在代码中添加pos的调整是必要的,确保pos为正数。 此外,用户的问题中提到使用线性探测法解决冲突,因此在插入时需要循环探测直到找到空位或相同的Key。 综上所述,上述代码应该能够正确实现用户的需求。接下来,需要检查是否满足所有要求: - 使用除留余数法:是的,H(Key)=Key%P。 - 线性探测解决冲突:是的,pos依次+1。 - 散列表大小为素数P:是的,初始化时使用P作为size。 - 处理重复关键字:当插入已存在的Key时,返回现有位置,不插入。 此外,代码的结构清晰,使用结构体封装散列表,符合C语言的最佳实践。 可能还需要考虑错误处理,比如表满的情况,但根据题目中的装填因子和引用[3]中的建议,可能题目中的测试用例不会出现这种情况,因此代码中的处理是返回-1,但实际应用中可能需要扩展。 因此,最终的代码应该如上述所示。</think>### 实现步骤 1. **定义散列表结构** 使用数组存储数据,表长为素数$P$,初始值为`-1`表示空槽: ```c #define EMPTY -1 typedef struct { int *table; // 存储数据的数组 int size; // 散列表长度 } HashTable; ``` 2. **初始化散列表** 创建大小为$P$的数组,并全部初始化为`EMPTY`: ```c HashTable* createHashTable(int P) { HashTable* ht = (HashTable*)malloc(sizeof(HashTable)); ht->size = P; ht->table = (int*)malloc(P * sizeof(int)); for (int i = 0; i < P; i++) { ht->table[i] = EMPTY; } return ht; } ``` 3. **插入操作** 计算初始位置$H(\text{Key}) = \text{Key} \bmod P$,用线性探测解决冲突: ```c int insert(HashTable* ht, int key) { int pos = key % ht->size; if (pos < 0) pos += ht->size; // 处理负数 int originalPos = pos; do { if (ht->table[pos] == key) return pos; // 已存在直接返回 if (ht->table[pos] == EMPTY) { ht->table[pos] = key; return pos; } pos = (pos + 1) % ht->size; // 线性探测 } while (pos != originalPos); // 防止表满死循环 return -1; // 表满(根据题目假设不会触发) } ``` 4. **主函数逻辑** 读取输入并逐个插入关键字,输出位置: ```c int main() { int P, N; scanf("%d %d", &P, &N); HashTable* ht = createHashTable(P); for (int i = 0; i < N; i++) { int key; scanf("%d", &key); printf("%d", insert(ht, key)); if (i != N-1) printf(" "); } free(ht->table); free(ht); return 0; } ``` ### 关键点说明 - **散列函数**:$H(\text{Key}) = \text{Key} \bmod P$确保映射到$[0, P-1]$范围[^1]。 - **冲突解决**:线性探测依次检查下一个位置,表尾绕回表头[^3]。 - **重复关键字**:直接返回已存在的位置,避免重复存储。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值