散列表(Hash
table,也叫哈希表),是根据关键字(Key
value)而直接访问在内存存储位置的数据结构。线性表查找的时间复杂度为O(n)而平衡二叉树的查找的时间复杂度为O(log(n))。无论是采用线程表或是树进行存储,都面临面随着数据量的增大,查找速度将不同程度变慢的问题。而哈希表正好解决了这个问题。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash)
函数
函数f (key)常用的对应关系:
- 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即或,其中为常数(这种散列函数叫做自身函数)
- 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
- 平方取中法:取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
- 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
- 随机数法
- 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即, 。不仅可以对关键字直接取模,也可在折叠法、平方取中法等运算之后取模。对p的选择很重要,一般取素数或m,若p选择不好,容易产生碰撞。
由于C语言中,并没有对hash表这类的高级数据结构进行支持,即使在目前通用的C++中,也只支持栈、队列等几个数据结构,对于map,其实是以树结构来实现的,而不是以hash表实现。
uthash是一个C语言的hash表实现。它以宏定义的方式实现hash表,不仅加快了运行的速度,而且与关键类型无关的优点。
uthash使用起来十分方便,只要将头文件uthash.h包含进去就可以使用。
目前,uthash的最新版本(1.9)支持如下平台:
- Linux
- Mac OS X
- Windows using vs2008 and vs 2010
- Solaris
- OpenBSD
通常这足够通用了。唯一的不足是在windows下,在uthash这旧版本中,并不支持vs2008和2010,而是支持MinGW。
uthash支持:增加、查找、删除、计数、迭代、排序、选择等操作。
#include "uthash.h" typedef struct g_hashElement { int key; char name[10]; UT_hash_handle hh; } tTestHashElement; tTestHashElement *element = nullptr; // 添加 void add_user(int userID,char*name) { tTestHashElement*starget = (tTestHashElement*)malloc(sizeof(tTestHashElement)); starget->key = userID; strcpy(starget->name, name); HASH_ADD_INT(element, key, starget);/* id: name of key field */ } //查找 tTestHashElement* findUser(int userID) { tTestHashElement* sTarget = nullptr; HASH_FIND_INT(element, &userID, sTarget); return sTarget; } //删除 void deleteUser(tTestHashElement*User) { if (!User) { return; } HASH_DEL(element, User); free(User); } //计数 void printHash() { unsigned int num_user; num_user = HASH_COUNT(element); printf("there are %u element\n",num_user); tTestHashElement*sww = element; for (; sww!=nullptr; sww = (tTestHashElement*)sww->hh.next) { printf("element key %d:name %s\n",sww->key,sww->name); } } //比较 int name_sort(tTestHashElement*a,tTestHashElement*b) { return strcmp(a->name, b->name); } int id_sort(tTestHashElement*a,tTestHashElement*b) { return a->key - b->key; } //排序 void sortByName(tTestHashElement*a,tTestHashElement*b) { HASH_SORT(element, name_sort); } void sortByID(tTestHashElement*a,tTestHashElement*b) { HASH_SORT(element, id_sort); }
在Cocos2d-x里面有几处使用的
复杂的用法就可以参考Cocos2d-x ActionManager 的应用了
此处贴出cpp源码;
#include "CCActionManager.h" #include "CCNode.h" #include "CCScheduler.h" #include "ccMacros.h" #include "ccCArray.h" #include "uthash.h" NS_CC_BEGIN // // singleton stuff // typedef struct _hashElement { struct _ccArray *actions;//动作列表 Node *target;//动作目标 int actionIndex;//动作id Action *currentAction;//当前动作 bool currentActionSalvaged;//当前动画是否被回收。 bool paused;//是否暂停 UT_hash_handle hh;//哈希表查询句柄。 } tHashElement; ActionManager::ActionManager(void) : _targets(nullptr), _currentTarget(nullptr), _currentTargetSalvaged(false) { } ActionManager::~ActionManager(void) { CCLOGINFO("deallocing ActionManager: %p", this); //清空所有的action removeAllActions(); } // private //删除相应的哈希表项 void ActionManager::deleteHashElement(tHashElement *element) { //释放pElement存储的动画集 ccArrayFree(element->actions); //从哈希表m_pTargets中删除pElement项 HASH_DEL(_targets, element); //释放element element->target->release(); free(element); } //为哈希表项pElement申请内存,以存放动画集 void ActionManager::actionAllocWithHashElement(tHashElement *element) { // 默认每个哈希表项存四个动画,所以如果当前哈希表项的动画集为空, // 则为其申请可存放4个动画指针的内存大小。 if (element->actions == nullptr) { element->actions = ccArrayNew(4); }else if (element->actions->num == element->actions->max) {//如果哈希表项的动作集需要扩大,则扩增为原来的2倍直至达到最大容量。 ccArrayDoubleCapacity(element->actions); } } //通过下标移除动作 void ActionManager::removeActionAtIndex(ssize_t index, tHashElement *element) { Action *action = (Action*)element->actions->arr[index];//索引到action //若移除的时当前运行的动作 先保留 下移循环再移除 if (action == element->currentAction && (! element->currentActionSalvaged)) { element->currentAction->retain(); element->currentActionSalvaged = true; } //从动作列表移除这个动作 ccArrayRemoveObjectAtIndex(element->actions, index, true); // update actionIndex in case we are in tick. looping over the actions //更新动作下标 前移 if (element->actionIndex >= index) { element->actionIndex--; } //哈希表内没有动作了 若是当前哈希表 暂时不删除这个表 标记下 //若不是这个当前表 直接删除这个哈希表 if (element->actions->num == 0) { if (_currentTarget == element) { _currentTargetSalvaged = true; } else { deleteHashElement(element); } } } // pause / resume void ActionManager::pauseTarget(Node *target) { tHashElement *element = nullptr; HASH_FIND_PTR(_targets, &target, element); if (element) { element->paused = true; } } void ActionManager::resumeTarget(Node *target) { tHashElement *element = nullptr; HASH_FIND_PTR(_targets, &target, element); if (element) { element->paused = false; } } Vector<Node*> ActionManager::pauseAllRunningActions() { Vector<Node*> idsWithActions; for (tHashElement *element=_targets; element != nullptr; element = (tHashElement *)element->hh.next) { if (! element->paused) { element->paused = true; idsWithActions.pushBack(element->target); } } return idsWithActions; } void ActionManager::resumeTargets(const Vector<Node*>& targetsToResume) { for(const auto &node : targetsToResume) { this->resumeTarget(node); } } // run void ActionManager::addAction(Action *action, Node *target, bool paused) {//指针作为参数 断言是个好习惯 CCASSERT(action != nullptr, ""); CCASSERT(target != nullptr, ""); tHashElement *element = nullptr;/* zero fill! */ // we should convert it to Ref*, because we save it as Ref* Ref *tmp = target; HASH_FIND_PTR(_targets, &tmp, element);//通过target 找到对应的哈希表项返回给pElement; if (! element) {//如果找不到。则node对应的哈希表还不存在 则申请内存创建此哈希表项,并将其加入哈希表中。 //创建一个哈希表项 element = (tHashElement*)calloc(sizeof(*element), 1); //设置暂停状态 element->paused = paused; //引用计数器加1,代表被管理器使用中。 target->retain(); //设置哈希表项中的演员为参数指定的演员 element->target = target; HASH_ADD_PTR(_targets, target, element);//将哈希表项添加到哈希表 } //为哈希表申请内存 用来存放action actionAllocWithHashElement(element); //动作已经在动作列表 中断 //判断pAction是否在pElement的动画集中。确保只放入一次。 CCASSERT(! ccArrayContainsObject(element->actions, action), ""); ccArrayAppendObject(element->actions, action);//添加到动作列表 //动作绑定目标 //设置是哪个CCNode要进行当前动画 action->startWithTarget(target); } // remove void ActionManager::removeAllActions() { for (tHashElement *element = _targets; element != nullptr; ) {//遍历哈希表 auto target = element->target;//得到演员 element = (tHashElement*)element->hh.next;//哈希表项后移 removeAllActionsFromTarget(target);//移除target上的所有action } } void ActionManager::removeAllActionsFromTarget(Node *target) { // explicit null handling if (target == nullptr) {// 有效性判断 return; } //定义一个哈希表项指针,并通过哈希表的查询宏取得对应项地址返回给指针。 tHashElement *element = nullptr; HASH_FIND_PTR(_targets, &target, element); if (element) //如果找到此项 { //如果此哈希表项有正在播放的动画并且这个动画并未被设为要回收。 if (ccArrayContainsObject(element->actions, element->currentAction) && (! element->currentActionSalvaged)) {//将当前正在播放的动画的引用计数器加1并设置其回收标记为true。 //引用计数加1的目的是人为使其暂不能被正常释放,需要待后面再减1后才可以被释放。 element->currentAction->retain(); element->currentActionSalvaged = true; } //清空当前哈希表项的动画集。 ccArrayRemoveAllObjects(element->actions); if (_currentTarget == element) { //如果当前哈希表项正处于使用中,暂不释放,只将其要回收的标记设为true。 _currentTargetSalvaged = true; } else {//如果当前哈希表项没有处于使用中 释放当前哈希表项 deleteHashElement(element); } } else { // CCLOG("cocos2d: removeAllActionsFromTarget: Target not found"); } } void ActionManager::removeAction(Action *action) { // explicit null handling if (action == nullptr) {//有效性判断 return; } //定义一个哈希表项指针,并通过哈希表的查询宏取得对应项地址返回给指针。 tHashElement *element = nullptr; Ref *target = action->getOriginalTarget(); HASH_FIND_PTR(_targets, &target, element); if (element) {//如果找到此项 //取得pAction处于当前项的动画集的索引 auto i = ccArrayGetIndexOfObject(element->actions, action); if (i != CC_INVALID_INDEX) { //如果这个索引是有效的,调用函数将pElement的指定索引的动画移除。 removeActionAtIndex(i, element); } } else { CCLOG("cocos2d: removeAction: Target not found"); } } //将指定演员的指定动画删除 void ActionManager::removeActionByTag(int tag, Node *target) { //有效性判断 CCASSERT(tag != Action::INVALID_TAG, ""); CCASSERT(target != nullptr, ""); //定义哈希表项指针变量,并通过哈希表处理宏取得相应哈希表项 tHashElement *element = nullptr; HASH_FIND_PTR(_targets, &target, element); //如果能找到哈希表项 if (element) { //取得哈希表项的动画集中的动画数量 auto limit = element->actions->num; for (int i = 0; i < limit; ++i) {//遍历动画集中的所有动画 //取得每一项动画 Action *action = (Action*)element->actions->arr[i]; //查看是否是指定演员的指定动画。 if (action->getTag() == (int)tag && action->getOriginalTarget() == target) { //如果是,则删除此项。 removeActionAtIndex(i, element); break; } } } } // get //取得指定演员的指定动画 // XXX: Passing "const O *" instead of "const O&" because HASH_FIND_IT requries the address of a pointer // and, it is not possible to get the address of a reference Action* ActionManager::getActionByTag(int tag, const Node *target) const {//断言 有效性判断 CCASSERT(tag != Action::INVALID_TAG, ""); //定义哈希表项指针变量 tHashElement *element = nullptr; HASH_FIND_PTR(_targets, &target, element);//查找哈希表 //如果能找到哈希表项 if (element) { //如果此哈希表项的动画集不为空 if (element->actions != nullptr) { //取得哈希表项的动画集中的动画数量 auto limit = element->actions->num; for (int i = 0; i < limit; ++i) {//遍历动画集中的所有动画 //取得每一项动画 Action *action = (Action*)element->actions->arr[i]; //查看是否是指定动画。 if (action->getTag() == (int)tag) { return action; } } } CCLOG("cocos2d : getActionByTag(tag = %d): Action not found", tag); } else { // CCLOG("cocos2d : getActionByTag: Target not found"); } //找不到要找的动画。返回NULL。 return nullptr; } // XXX: Passing "const O *" instead of "const O&" because HASH_FIND_IT requries the address of a pointer //取得指定演员的动画集中的动画数量 // and, it is not possible to get the address of a reference ssize_t ActionManager::getNumberOfRunningActionsInTarget(const Node *target) const {//定义哈希表项指针变量,并通过哈希表处理宏取得相应哈希表项 tHashElement *element = nullptr; HASH_FIND_PTR(_targets, &target, element); if (element) { //如果找到了,判断动画集是否为空,如果不为空返回动画数量,否则返回零。 return element->actions ? element->actions->num : 0; } return 0; } // main loop //动画管理器的更新函数 void ActionManager::update(float dt) {//遍历哈希表的所有项 for (tHashElement *elt = _targets; elt != nullptr; ) { //将当前哈希表项保存到变量m_pCurrentTarget中。并将此项对应的回收标记m_bCurrentTargetSalvaged 设为false, _currentTarget = elt; _currentTargetSalvaged = false; //当前动作没有暂停 if (! _currentTarget->paused) {//遍历当前项的动画集中的所有动画 // The 'actions' MutableArray may change while inside this loop. for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num; _currentTarget->actionIndex++) {//取得遍历项的动画 _currentTarget->currentAction = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex]; if (_currentTarget->currentAction == nullptr) {//如果遍历项动画为空,跳过后遍历下一个动画。 continue; } //设置回收标记为false。 _currentTarget->currentActionSalvaged = false; //更新当前动画的播放 _currentTarget->currentAction->step(dt); if (_currentTarget->currentActionSalvaged) { //如果当前项的回收标记为true,则进行释放处理。 // The currentAction told the node to remove it. To prevent the action from // accidentally deallocating itself before finishing its step, we retained // it. Now that step is done, it's safe to release it. _currentTarget->currentAction->release(); } else if (_currentTarget->currentAction->isDone()) { //如果当前动画处于结束状态,则停止动画 _currentTarget->currentAction->stop(); //为了在removeAction中正确释放动画,这里先创建一个临时变量pAction记录一下要释放的动画。 Action *action = _currentTarget->currentAction; // Make currentAction nil to prevent removeAction from salvaging it. //在removeAction之前将当前哈希表项中的当前动画设为NULL,否则不能释放。 _currentTarget->currentAction = nullptr; removeAction(action); } _currentTarget->currentAction = nullptr; } } //使for循环能够继续 // elt, at this moment, is still valid // so it is safe to ask this here (issue #490) elt = (tHashElement*)(elt->hh.next); // 如果当前哈希表项处于回收状态且其动画集为空,删除此哈希表项。 // only delete currentTarget if no actions were scheduled during the cycle (issue #481) if (_currentTargetSalvaged && _currentTarget->actions->num == 0) { deleteHashElement(_currentTarget); } } // issue #635 _currentTarget = nullptr; } NS_CC_END
【STL】哈希表 uthash.h
时间: 2024-10-12 17:28:16