热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

哈希表的C实现(三)传说中的暴雪版

关于哈希表C实现,写了两篇学习笔记,不过似乎网上流传最具传奇色彩的莫过于暴雪公司的魔兽文件打包管理器里的hashTable的实现了;在冲突

关于哈希表C实现,写了两篇学习笔记,不过似乎网上流传最具传奇色彩的莫过于暴雪公司的魔兽文件打包管理器里的hashTable的实现了;在冲突方面的处理方面,采用线性探测再散列。在添加和查找过程中进行了三次哈希,第一个哈希值用来查找,后两个哈希值用来校验,这样可以大大减少冲突的几率。

在网上找了相关代码,但不知道其来源是否地道:

StringHash.h

1 #include
2 #include
3
4 using namespace std;
5
6 #pragma once
7
8 #define MAXTABLELEN 1024 // 默认哈希索引表大小
9 //
10 // 哈希索引表定义
11 typedef struct _HASHTABLE
12 {
13 long nHashA;
14 long nHashB;
15 bool bExists;
16 }HASHTABLE, *PHASHTABLE ;
17
18 class StringHash
19 {
20 public:
21 StringHash(const long nTableLength = MAXTABLELEN);
22 ~StringHash(void);
23 private:
24 unsigned long cryptTable[0x500];
25 unsigned long m_tablelength; // 哈希索引表长度
26 HASHTABLE *m_HashIndexTable;
27 private:
28 void InitCryptTable(); // 对哈希索引表预处理
29 unsigned long HashString(const string &lpszString, unsigned long dwHashType); // 求取哈希值
30 public:
31 bool Hash(string url);
32 unsigned long Hashed(string url); // 检测url是否被hash过
33 };

StringHash.cpp

#include "StdAfx.h"
#include "StringHash.h"

StringHash::StringHash(const long nTableLength /*= MAXTABLELEN*/)
{
InitCryptTable();
m_tablelength = nTableLength;
//初始化hash表
m_HashIndexTable = new HASHTABLE[nTableLength];
for ( int i = 0; i {
m_HashIndexTable[i].nHashA = -1;
m_HashIndexTable[i].nHashB = -1;
m_HashIndexTable[i].bExists = false;
}
}

StringHash::~StringHash(void)
{
//清理内存
if ( NULL != m_HashIndexTable )
{
delete []m_HashIndexTable;
m_HashIndexTable = NULL;
m_tablelength = 0;
}
}

/************************************************************************/
/*函数名:InitCryptTable
/*功 能:对哈希索引表预处理
/*返回值:无
/************************************************************************/
void StringHash::InitCryptTable()
{
unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;

for( index1 &#61; 0; index1 <0x100; index1&#43;&#43; )
{
for( index2 &#61; index1, i &#61; 0; i <5; i&#43;&#43;, index2 &#43;&#61; 0x100 )
{
unsigned long temp1, temp2;
seed &#61; (seed * 125 &#43; 3) % 0x2AAAAB;
temp1 &#61; (seed & 0xFFFF) <<0x10;
seed &#61; (seed * 125 &#43; 3) % 0x2AAAAB;
temp2 &#61; (seed & 0xFFFF);
cryptTable[index2] &#61; ( temp1 | temp2 );
}
}
}

/************************************************************************/
/*函数名&#xff1a;HashString
/*功 能&#xff1a;求取哈希值
/*返回值&#xff1a;返回hash值
/************************************************************************/
unsigned long StringHash::HashString(const string& lpszString, unsigned long dwHashType)
{
unsigned char *key &#61; (unsigned char *)(const_cast(lpszString.c_str()));
unsigned long seed1 &#61; 0x7FED7FED, seed2 &#61; 0xEEEEEEEE;
int ch;

while(*key !&#61; 0)
{
ch &#61; toupper(*key&#43;&#43;);

seed1 &#61; cryptTable[(dwHashType <<8) &#43; ch] ^ (seed1 &#43; seed2);
seed2 &#61; ch &#43; seed1 &#43; seed2 &#43; (seed2 <<5) &#43; 3;
}
return seed1;
}

/************************************************************************/
/*函数名&#xff1a;Hashed
/*功 能&#xff1a;检测一个字符串是否被hash过
/*返回值&#xff1a;如果存在&#xff0c;返回位置&#xff1b;否则&#xff0c;返回-1
/************************************************************************/
unsigned long StringHash::Hashed(string lpszString)

{
const unsigned long HASH_OFFSET &#61; 0, HASH_A &#61; 1, HASH_B &#61; 2;
//不同的字符串三次hash还会碰撞的几率无限接近于不可能
unsigned long nHash &#61; HashString(lpszString, HASH_OFFSET);
unsigned long nHashA &#61; HashString(lpszString, HASH_A);
unsigned long nHashB &#61; HashString(lpszString, HASH_B);
unsigned long nHashStart &#61; nHash % m_tablelength,
nHashPos &#61; nHashStart;

while ( m_HashIndexTable[nHashPos].bExists)
{
if (m_HashIndexTable[nHashPos].nHashA &#61;&#61; nHashA && m_HashIndexTable[nHashPos].nHashB &#61;&#61; nHashB)
return nHashPos;
else
nHashPos &#61; (nHashPos &#43; 1) % m_tablelength;

if (nHashPos &#61;&#61; nHashStart)
break;
}

return -1; //没有找到
}

/************************************************************************/
/*函数名&#xff1a;Hash
/*功 能&#xff1a;hash一个字符串
/*返回值&#xff1a;成功&#xff0c;返回true&#xff1b;失败&#xff0c;返回false
/************************************************************************/
bool StringHash::Hash(string lpszString)
{
const unsigned long HASH_OFFSET &#61; 0, HASH_A &#61; 1, HASH_B &#61; 2;
unsigned long nHash &#61; HashString(lpszString, HASH_OFFSET);
unsigned long nHashA &#61; HashString(lpszString, HASH_A);
unsigned long nHashB &#61; HashString(lpszString, HASH_B);
unsigned long nHashStart &#61; nHash % m_tablelength,
nHashPos &#61; nHashStart;

while ( m_HashIndexTable[nHashPos].bExists)
{
nHashPos &#61; (nHashPos &#43; 1) % m_tablelength;
if (nHashPos &#61;&#61; nHashStart) //一个轮回
{
//hash表中没有空余的位置了,无法完成hash
return false;
}
}
m_HashIndexTable[nHashPos].bExists &#61; true;
m_HashIndexTable[nHashPos].nHashA &#61; nHashA;
m_HashIndexTable[nHashPos].nHashB &#61; nHashB;

return true;
}

关于其中的实现原理&#xff0c;我觉得没有比 inside MPQ说得清楚的了&#xff0c;于是用我蹩脚的E文&#xff0c;将该文的第二节翻译了一遍&#xff08;将原文和译文都贴出来&#xff0c;请高手指正&#xff09;&#xff1a;

原理

Most of the advancements throughout the history of computers have been because of particular problems which required solving. In this chapter, we&#39;ll take a look at some of these problems and their solutions as they pertain to the MPQ format.

贯穿计算机发展历史&#xff0c;大多数进步都是源于某些问题的解决&#xff0c;在这一节中&#xff0c;我们来看一看与MPQ 格式相关问题及解决方案&#xff1b;

 

Hashes

哈希表

Problem: You have a very large array of strings. You have another string and need to know if it is already in the list. You would probably begin by comparing each string in the list with the string other, but when put into application, you would find that this method is far too slow for practical use. Something else must be done. But how can you know if the string exists without comparing it to all the other strings?

问题&#xff1a;你有一个很大的字符串数组&#xff0c;同时&#xff0c;你另外还有一个字符串&#xff0c;需要知道这个字符串是否已经存在于字符串数组中。你可能会对数组中的每一个字符串进行比较&#xff0c;但是在实际项目中&#xff0c;你会发现这种做法对某些特殊应用来说太慢了。必须寻求其他途径。那么如何才能在不作遍历比较的情况下知道这个字符串是否存在于数组中呢&#xff1f;

 

Solution: Hashes. Hashes are smaller data types (i.e. numbers) that represent other, larger, data types (usually strings). In this scenario, you could store hashes in the array with the strings. Then you could compute the hash of the other string and compare it to the stored hashes. If a hash in the array matches the new hash, the strings can be compared to verify the match. This method, called indexing, could speed things up by about 100 times, depending on the size of the array and the average length of the strings.

解决方案&#xff1a;哈希表。哈希表是通过更小的数据类型表示其他更大的数据类型。在这种情况下&#xff0c;你可以把哈希表存储在字符串数组中&#xff0c;然后你可以计算字符串的哈希值&#xff0c;然后与已经存储的字符串的哈希值进行比较。如果有匹配的哈希值&#xff0c;就可以通过字符串比较进行匹配验证。这种方法叫索引&#xff0c;根据数组的大小以及字符串的平均长度可以约100倍。 

unsigned long HashString(char *lpszString)
{
unsigned long ulHash &#61; 0xf1e2d3c4;
while (*lpszString !&#61; 0)
{
ulHash <<&#61; 1;
ulHash &#43;&#61; *lpszString&#43;&#43;;
}
return ulHash;
}

 

The previous code function demonstrates a very simple hashing algorithm. The function sums the characters in the string, shifting the hash value left one bit before each character is added in. Using this algorithm, the string "arr\units.dat" would hash to 0x5A858026, and "unit\neutral\acritter.grp" would hash to 0x694CD020. Now, this is, admittedly, a very simple algorithm, and it isn&#39;t very useful, because it would generate a relatively predictable output, and a lot of collisions in the lower range of numbers. Collisions are what happen when more than one string hash to the same value.

上面代码中的函数演示了一种非常简单的散列算法。这个函数在遍历字符串过程中&#xff0c;将哈希值左移一位&#xff0c;然后加上字符值&#xff1b;通过这个算法&#xff0c;字符串"arr\units.dat" 的哈希值是0x5A858026&#xff0c;字符串"unit\neutral\acritter.grp" 的哈希值是0x694CD020&#xff1b;现在&#xff0c;众所周知的&#xff0c;这是一个基本没有什么实用价值的简单算法&#xff0c;因为它会在较低的数据范围内产生相对可预测的输出&#xff0c;从而可能会产生大量冲突&#xff08;不同的字符串产生相同的哈希值&#xff09;。

 

The MPQ format, on the other hand, uses a very complicated hash algorithm (shown below) to generate totally unpredictable hash values. In fact, the hashing algorithm is so effective that it is called a one-way hash. A one-way hash is a an algorithm that is constructed in such a way that deriving the original string (set of strings, actually) is virtually impossible. Using this particular algorithm, the filename "arr\units.dat" would hash to 0xF4E6C69D, and "unit\neutral\acritter.grp" would hash to 0xA26067F3.

MPQ格式&#xff0c;使用了一种非常复杂的散列算法&#xff08;如下所示&#xff09;&#xff0c;产生完全不可预测的哈希值&#xff0c;这个算法十分有效&#xff0c;这就是所谓的单向散列算法。通过单向散列算法几乎不可能通过哈希值来唯一的确定输入值。使用这种算法&#xff0c;文件名 "arr\units.dat" 的哈希值是0xF4E6C69D&#xff0c;"unit\neutral\acritter.grp" 的哈希值是 0xA26067F3。 

unsigned long HashString(char *lpszFileName, unsigned long dwHashType)
{
unsigned char *key &#61; (unsigned char *)lpszFileName;
unsigned long seed1 &#61; 0x7FED7FED, seed2 &#61; 0xEEEEEEEE;
int ch;

while(*key !&#61; 0)
{
ch &#61; toupper(*key&#43;&#43;);
seed1 &#61; cryptTable[(dwHashType <<8) &#43; ch] ^ (seed1 &#43; seed2);
seed2 &#61; ch &#43; seed1 &#43; seed2 &#43; (seed2 <<5) &#43; 3;
}
return seed1;
}

 

Hash Tables

哈希表

Problem: You tried using an index like in the previous sample, but your program absolutely demands break-neck speeds, and indexing just isn&#39;t fast enough. About the only thing you could do to make it faster is to not check all of the hashes in the array. Or, even better, if you could only make one comparison in order to be sure the string doesn&#39;t exist anywhere in the array. Sound too good to be true? It&#39;s not.

 

问题&#xff1a;您尝试在前面的示例中使用相同索引&#xff0c;您的程序一定会有中断现象发生&#xff0c;而且不够快 。如果想让它更快&#xff0c;您能做的只有让程序不去查询数组中的所有散列值。或者 您可以只做一次对比就可以得出在列表中是否存在字符串。 听起来不错&#xff0c;真的么&#xff1f;  不可能的啦

 

Solution: A hash table. A hash table is a special type of array in which the offset of the desired string is the hash of that string. What I mean is this. Say that you make that string array use a separate array of fixed size (let&#39;s say 1024 entries, to make it an even power of 2) for the hash table. You want to see if the new string is in that table. To get the string&#39;s place in the hash table, you compute the hash of that string, then modulo (division remainder) that hash value by the size of that table. Thus, if you used the simple hash algorithm in the previous section, "arr\units.dat" would hash to 0x5A858026, making its offset 0x26 (0x5A858026 divided by 0x400 is 0x16A160, with a remainder of 0x26). The string at this location (if there was one) would then be compared to the string to add. If the string at 0x26 doesn&#39;t match or just plain doesn&#39;t exist, then the string to add doesn&#39;t exist in the array. The following code illustrates this:

 

解决&#xff1a;一个哈希表就是以字符串的哈希值作为下标的一类数组。我的意思是&#xff0c;哈希表使用一个固定长度的字符串数组&#xff08;比如1024&#xff0c;2的偶次幂&#xff09;进行存储&#xff1b;当你要看看这个字符串是否存在于哈希表中&#xff0c;为了获取这个字符串在哈希表中的位置&#xff0c;你首先计算字符串的哈希值&#xff0c;然后哈希表的长度取模。这样如果你像上一节那样使用简单的哈希算法&#xff0c;字符串"arr\units.dat" 的哈希值是0x5A858026,偏移量0x26&#xff08;0x5A858026 除于0x400等于0x16A160&#xff0c;模0x400等于0x26&#xff09;。因此&#xff0c;这个位置的字符串将与新加入的字符串进行比较。如果0X26处的字符串不匹配或不存在&#xff0c;那么表示新增的字符串在数组中不存在。下面是示意的代码&#xff1a;

int GetHashTablePos(char *lpszString, SOMESTRUCTURE *lpTable, int nTableSize)
{
int nHash &#61; HashString(lpszString), nHashPos &#61; nHash % nTableSize;
if (lpTable[nHashPos].bExists && !strcmp(lpTable[nHashPos].pString, lpszString))
return nHashPos;
else
return -1; //Error value
}

Now, there is one glaring flaw in that explanation. What do you think happens when a collision occurs (two different strings hash to the same value)? Obviously, they can&#39;t occupy the same entry in the hash table. Normally, this is solved by each entry in the hash table being a pointer to a linked list, and the linked list would hold all the entries that hash to that same value.

上面的说明中存在一个刺眼的缺陷。当有冲突&#xff08;两个不同的字符串有相同的哈希值&#xff09;发生的时候怎么办&#xff1f;显而易见的&#xff0c;它们不能占据哈希表中的同一个位置。通常的解决办法是为每一个哈希值指向一个链表&#xff0c;用于存放所有哈希冲突的值&#xff1b;

MPQs use a hash table of filenames to keep track of the files inside, but the format of this table is somewhat different from the way hash tables are normally done. First of all, instead of using a hash as an offset, and storing the actually filename for verification, MPQs do not store the filename at all, but rather use three different hashes: one for the hash table offset, two for verification. These two verification hashes are used in place of the actual filename. Of course, this leaves the possibility that two different filenames would hash to the same three hashes, but the chances of this happening are, on average, 1:18889465931478580854784, which should be safe enough for just about anyone.

MPQs使用一个存放文件名的哈希表来跟踪文件内部&#xff0c;但是表的格式与通常方法有点不同&#xff0c;首先不像通常的做法使用哈希值作为偏移量&#xff0c;存储实际的文件名。MPQs 根本不存储文件名&#xff0c;而是使用了三个不同的哈希值&#xff1a;一个用做哈希表偏移量&#xff0c;两个用作核对。这两个核对的哈希值用于替代文件名。当然从理论上说存在两个不同的文件名得到相同的三个哈希值&#xff0c;但是这种情况发送的几率是&#xff1a;1:18889465931478580854784,这应该足够安全了。

 

The other way that an MPQ&#39;s hash table differs from the conventional implementation is that instead of using a linked list for each entry, when a collision occurs, the entry will be shifted to the next slot, and the process repeated until a free space is found. Take a look at the following illustrational code, which is basically the way a file is located for reading in an MPQ:

MPQ&#39;s的哈希表的实现与传统实现的另一个不同的地方是&#xff0c;相对与传统做法&#xff08;为每个节点使用一个链表&#xff0c;当冲突发生的时候&#xff0c;遍历链表进行比较&#xff09;&#xff0c;看一下下面的示范代码&#xff0c;在MPQ中定位一个文件进行读操作&#xff1a;

 

int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize)
{
const int HASH_OFFSET &#61; 0, HASH_A &#61; 1, HASH_B &#61; 2;
int nHash &#61; HashString(lpszString, HASH_OFFSET),nHashA &#61; HashString(lpszString, HASH_A),nHashB &#61; HashString(lpszString, HASH_B), nHashStart &#61; nHash % nTableSize,nHashPos &#61; nHashStart;
while (lpTable[nHashPos].bExists)
{
if (lpTable[nHashPos].nHashA &#61;&#61; nHashA && lpTable[nHashPos].nHashB &#61;&#61; nHashB)
return nHashPos;

else
nHashPos &#61; (nHashPos &#43; 1) % nTableSize;
if (nHashPos &#61;&#61; nHashStart)
break;
}
return -1; //Error value

}


However convoluted that code may look,  the theory behind it isn&#39;t difficult. It basically follows this process when looking to read a file:

Compute the three hashes (offset hash and two check hashes) and store them in variables.

Move to the entry of the offset hash

Is the entry unused? If so, stop the search and return &#39;file not found&#39;.

Do the two check hashes match the check hashes of the file we&#39;re looking for? If so, stop the search and return the current entry.

Move to the next entry in the list, wrapping around to the beginning if we were on the last entry.

Is the entry we just moved to the same as the offset hash (did we look through the whole hash table?)? If so, stop the search and return &#39;file not found&#39;.

Go back to step 3.

 

无论代码看上去有多么复杂&#xff0c;其背后的理论并不难。读一个文件的时候基本遵循下面这样一个过程&#xff1a;

1、计算三个哈希值&#xff08;一个哈希偏移量和两个验证值&#xff09;并保存到变量中&#xff1b;

2、移动到哈希偏移量对应的值&#xff1b;

3、对应的位置是否尚未使用&#xff1f;如果是&#xff0c;则停止搜寻&#xff0c;并返回“文件不存在”&#xff1b;

4、这两个验证值是否与我们要找的字符串验证值匹配&#xff0c;如果是&#xff0c;停止搜寻&#xff0c;并返回当前的节点&#xff1b;

5、移动到下一个节点&#xff0c;如果到了最后一个节点则返回开始&#xff1b;

6、Is the entry we just moved to the same as the offset hash (did we look through the whole hash table?)? If so, stop the search and return &#39;file not found&#39;.

7、回到第3步&#xff1b;

 

If you were paying attention, you might have noticed from my explanation and sample code is that the MPQ&#39;s hash table has to hold all the file entries in the MPQ. But what do you think happens when every hash-table entry gets filled? The answer might surprise you with its obviousness: you can&#39;t add any more files. Several people have asked me why there is a limit (called the file limit) on the number of files that can be put in an MPQ, and if there is any way around this limit. Well, you already have the answer to the first question. As for the second; no, you cannot get around the file limit. For that matter, hash tables cannot even be resized without remaking the entire MPQ from scratch. This is because the location of each entry in the hash table may well change due to the resizing, and we would not be able to derive the new position because the position is the hash of the file name, and we may not know the file name.

 

如果您注意的话&#xff0c;您可能已经从我们的解释和示例代码注意到&#xff0c;MPQ的哈希表已经将所有的文件入口放入MPQ中&#xff1b;那么当哈希表的每个项都被填充的时候&#xff0c;会发生什么呢&#xff1f;答案可能会让你惊讶&#xff1a;你不能添加任何文件。有些人可能会问我为什么文件数量上有这样的限制&#xff08;文件限制&#xff09;&#xff0c;是否有办法绕过这个限制&#xff1f;就此而言&#xff0c;如果不重新创建MPQ 的项&#xff0c;甚至无法调整哈希表的大小。这是因为每个项在哈希表中的位置会因为跳闸尺寸而改变&#xff0c;而我们无法得到新的位置&#xff0c;因为这些位置值是文件名的哈希值&#xff0c;而我们根本不知道文件名是什么。

 

一连总结了3篇关于哈希表的C实现&#xff0c;都是来源于网络&#xff0c;整理学习&#xff0c;以备忘之&#xff1b;不能说都搞得很清楚&#xff0c;大致知道了哈希表是怎么实现的&#xff1b;当然还有很多开源项目都有自己的实现&#xff0c;如LUA、Redis、Apache等&#xff0c;精力有限&#xff0c;先挖个坑&#xff0c;日后有时间再补充吧。不管怎么说&#xff0c;有点孔乙己的嫌疑&#xff0c;呵呵&#xff01;



推荐阅读
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
author-avatar
Angle健少
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有