First we try, then we trust

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

在这部分内容里,我们用HashTable取代AVLTree以进一步提高系统的检索效率。毕竟我们不需要AVLTree的排序功能。另外,从纯粹的理论上来说,AVLTree的算法复杂度至少是O(Log2(x))级别,而HashTable则接近于O(C)级别。


一、 使用HashTable

在华容道程序中,要搜索以前出现过的布局,及时消除重复布局。早最初的设计中,我使用AVLTree来达到这个目的。AVLTree是基于比较运算来进行搜索的,其算法的复杂度为O(Log2(n))级别,也是基于比较的搜索算法所能达到的最高效率。要想继续提高搜索效率,就得考虑使用非基于比较的运算。Hash算法就是一个非常值得推荐的算法,它理论的平均时间复杂度是1。

关于Hash算法,推荐读一读Wayfarer翻译的《考察数据结构——第二部分:队列、堆栈和哈希表[译] 》。我在这里就不再重复。在华容道程序中,我选择使用链地址法实现HashTable(我没有使用微软提供的HashTable)。尽管从理论上讲,二次探测法的效率应当比链地址法效率高,但在华容道程序测试中,链地址效率要高很多。另外,HashCode的计算方法采用了折叠累加的方法,通过对布局Int64表示进行折叠累加得到HashCode。

链地址法如下图所示:

 

链地址法的数据插入和删除比较简单,如果HashCode的计算是高效的,那么在同一个位置的键值就很少,搜索长度也会很短,而且可以很节省空间。我的Hash算法经验证,一个HashCode下最多出现10个节点,平均为3个节点左右。

HashCode的计算如下:

//===========================================================
// 计算HashCode
//===========================================================
public static int ToHashCode(Int64 layout)
{
  Int64 mask 
= 16383// 二进制 11111111111111
  int hashCode = 0;

  
for(int i = 0; i <= 5; i++)
    hashCode 
+= (int)((layout & (mask << i * 14)) >> i * 14);

  
return hashCode;
}

完整的HashTable代码如下:

using System;

public class HashTable : IHashTable
{
  
protected class Node
  
{
    
public Int64 info = 0;
    
public Node link = null;

    
public Node()
    
{
    }


    
public Node(Int64 layout)
    
{
      
this.info = layout;
    }

  }


  
private const int HashTableCapacity = 66000;
  
private Node[] _hashTable = new Node[HashTableCapacity];

  
//=============================================
  
// 构造函数
  
//=============================================
  public HashTable()
  
{
    
for(int i=0; i<HashTableCapacity; i++)
      _hashTable[i] 
= new Node();
  }


  
//=============================================
  
// 插入测试,判断是否有重复
  
//=============================================
  public bool Insert(Int64 layout)
  
{
    
int hashCode = HashTable.ToHashCode(layout);
    Node current 
= _hashTable[hashCode];

    
while(current.link != null)
    
{
      
if(current.link.info == layout)
        
return false;

      current 
= current.link;
    }


    Node newNode 
= new Node(layout);
    current.link 
= newNode;

    
return true;
  }


  
//=============================================
  
// 释放内存
  
//=============================================
  public void Release()
  
{
    
for(int i=0; i<HashTableCapacity; i++)
      _hashTable[i].link 
= null;
  }


  
//===========================================================
  
// 计算HashCode
  
//===========================================================
  public static int ToHashCode(Int64 layout)
  
{
    Int64 mask 
= 16383// 二进制 11111111111111
    int hashCode = 0;

    
for(int i = 0; i <= 5; i++)
      hashCode 
+= (int)((layout & (mask << i * 14)) >> i * 14);

    
return hashCode;
  }

}


二、 对解题效率的提升

经过修改后,程序的执行效率进一步提升。原来程序中的第四个布局的解题时间从上次优化的1秒缩短到了0.8秒。虽然这个运算时间已经很短了,但是我们还能不能再榨出点时间来呢?当然可以!

"数据结构+算法=程序",在前面的优化里,我重新对数据结构进行了调整:用Int64表示布局、用HashTable检索重复。但是数据结构的改变往往要求算法对应发生变化,这样程序才能显得协调。在《华容到与数据结构 (续 3)》里,将重新对棋盘表示以及走法进行设计,调整后的程序运行时间会缩短到0.6秒。

在《华容到与数据结构 (续 4)》中,我会继续提高执行效率,推出"超级优化版"(如果效率还能提高就叫"超超级优化版"、"超超超级优化版"),程序解题时间会缩短至0.42秒。如果不算屏幕结果显示的时间的化,仅需0.34秒。

posted on 2005-02-24 20:41  吕震宇  阅读(4018)  评论(4编辑  收藏  举报