学院首页 软件应用 编程开发 创意设计 认证培训 软件论坛
ASP ASP.NET PHP JSP SQL MYSQL Java VB

您的位置:学院 >> 编程开发 >> ASP >> 介绍一种效率极高的分类算法


介绍一种效率极高的分类算法


   我常常面试一些程序员,而且我几乎毫无例外地要问他们一些关于分类算法的问题。下面的举几个我常常询问的问题。你认为你可以很轻松地回答么^_^.
1、 分类算法常常表现为树的表示和遍历问题。那么,请问:如果用数据库中的一个Table来表达树型分类,应该有几个字段?
2、 如何快速地从这个Table恢复出一棵树;
3、 如何判断某个分类是否是另一个分类的子类;
4、 如何查找某个分类的所有产品;
5、 如何生成分类所在的路径。
6、 如何新增分类;

  在不限制分类的级数和每级分类的个数时,这些问题并不是可以轻松回答的。本文试图解决这些问题。
分类的数据结构
  我们知道:分类的数据结构实际上是一棵树。在《数据结构》课程中,大家可能学过Tree的算法。由于在网站建设中我们大量使用数据库,所以我们将从Tree在数据库中的存储谈起。
  为简化问题,我们假设每个节点只需要保留Name这一个信息。我们需要为每个节点编号。编号的方法有很多种。在数据库中常用的就是自动编号。这在Access、SQL Server、Oracle中都是这样。假设编号字段为ID。
  为了表示某个节点ID1是另外一个节点ID2的父节点,我们需要在数据库中再保留一个字段,说明这个分类是属于哪个节点的儿子。把这个字段取名为FatherID。如这里的ID2,其FatherID就是ID1。
这样,我们就得到了分类Catalog的数据表定义:
Create Table [Catalog](
[ID] [int] NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[FatherID] [int] NOT NULL
);
  约定:我们约定用-1作为最上面一层分类的父亲编码。编号为-1的分类。这是一个虚拟的分类。它在数据库中没有记录。
如何恢复出一棵树
  上面的Catalog定义的最大优势,就在于用它可以轻松地恢复出一棵树-分类树。为了更清楚地展示算法,我们先考虑一个简单的问题:怎样显示某个分类的下一级分类。我们知道,要查询某个分类FID的下一级分类,SQL语句非常简单:
select Name from catalog where FatherID=FID
显示这些类别时,我们简单地用$#@60;LI$#@62;来做到:

$#@60;%
REM oConn---数据库连接, 肎etChildren时已经打开
REM FID-----当前分类的编号

Function GetChildren(oConn,FID)
strSQL = "select ID,Name from catalog where FatherID="&FID
set rsCatalog = oConn.Execute(strSQL)
%$#@62;
$#@60;UL$#@62;
$#@60;%
Do while not rsCatalog.Eof
%$#@62;
$#@60;LI$#@62;$#@60;%=rsCatalog("Name")%$#@62;
$#@60;%
Loop
%$#@62;
$#@60;/UL$#@62;
$#@60;%
rsCatalog.Close
End Function
%$#@62;
  现在我们来看看如何显示FID下的所有分类。这需要用到递归算法。我们只需要在GetChildren函数中简单地对所有ID进行调用:GetChildren(oConn,Catalog("ID"))就可以了。
$#@60;%
REM oConn---数据库连接,已经打开
REM FID-----当前分类的编号

Function GetChildren(oConn,FID)
strSQL = "select Name from catalog where FatherID="&FID
set rsCatalog = oConn.Execute(strSQL)
%$#@62;
$#@60;UL$#@62;
$#@60;%
Do while not rsCatalog.Eof
%$#@62;
$#@60;LI$#@62;$#@60;%=rsCatalog("Name")%$#@62;
$#@60;%=GetChildren(oConn,Catalog("ID"))%$#@62;

$#@60;%
Loop
%$#@62;
$#@60;/UL$#@62;
$#@60;%
rsCatalog.Close
End Function
%$#@62;
  修改后的GetChildren就可以完成显示FID分类的所有子分类的任务。要显示所有的分类,只需要如此调用就可以了:
$#@60;%
REM strConn--连接数据库的字符串,请根据情况修改
set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open strConn
=GetChildren(oConn,-1)
oConn.Close
%$#@62;

如何查找某个分类的所有产品;
  现在来解决我们在前面提出的第四个问题。第三个问题留作习题。我们假设产品的数据表如下定义:
Create Table Product(
[ID] [int] NOT NULL,
[Name] [nvchar] NOT NULL,
[FatherID] [int] NOT NULL
);
  其中,ID是产品的编号,Name是产品的名称,而FatherID是产品所属的分类。
  对第四个问题,很容易想到的办法是:先找到这个分类FID的所有子类,然后查询所有子类下的所有产品。实现这个算法实际上很复杂。代码大致如下:
$#@60;%
Function GetAllID(oConn,FID)
Dim strTemp

If FID=-1 then
  strTemp = ""
 else
  strTemp =","
end if

 strSQL = "select Name from catalog where FatherID="&FID
 set rsCatalog = oConn.Execute(strSQL)
 Do while not rsCatalog.Eof
  strTemp=strTemp&rsCatalog("ID")&GetAllID(oConn,Catalog("ID")) REM 递归调用
 Loop
 rsCatalog.Close

 GetAllID = strTemp
End Function

REM strConn--连接数据库的字符串,请根据情况修改
  set oConn = Server.CreateObject("ADODB.Connection")
  oConn.Open strConn

  FID = Request.QueryString("FID")

  strSQL = "select top 100 * from Product where FatherID in ("&GetAllID(oConn,FID)&")"
  set rsProduct=oConn.Execute(strSQL)
%$#@62;
$#@60;UL$#@62;$#@60;%
Do while not rsProduct.EOF
%$#@62;
$#@60;LI$#@62;$#@60;%=rsProduct("Name")%$#@62;
$#@60;%
Loop
%$#@62;
$#@60;/UL$#@62;
$#@60;%rsProduct.Close
oConn.Close
%$#@62;

这个算法有很多缺点。试列举几个如下:
  1、 由于我们需要查询FID下的所有分类,当分类非常多时,算法将非常地不经济,而且,由于要构造一个很大的strSQL,试想如果有1000个分类,这个strSQL将很大,能否执行就是一个问题。
  2、 我们知道,在SQL中使用In子句的效率是非常低的。这个算法不可避免地要使用In子句,效率很低。

  我发现80%以上的程序员钟爱这样的算法,并在很多系统中大量地使用。细心的程序员会发现他们写出了很慢的程序,但苦于找不到原因。他们反复地检查SQL的执行效率,提高机器的档次,但效率的增加很少。
最根本的问题就出在这个算法本身。算法定了,能够再优化的机会就不多了。我们下面来介绍一种算法,效率将是上面算法的10倍以上。
分类编码算法
  问题就出在前面我们采用了顺序编码,这是一种最简单的编码方法。大家知道,简单并不意味着效率。实际上,编码科学是程序员必修的课程。下面,我们通过设计一种编码算法,使分类的编号ID中同时包含了其父类的信息。一个五级分类的例子如下:

  此例中,用32(4+7+7+7+7)位整数来编码,其中,第一级分类有4位,可以表达16种分类。第二级到第五级分类分别有7位,可以表达128个子分类。
  显然,如果我们得到一个编码为 1092787200 的分类,我们就知道:由于其编码为
0100 0001001 0001010 0111000 0000000
  所以它是第四级分类。其父类的二进制编码是0100 0001001 0001010 0000000 0000000,十进制编号为1092780032。依次我们还可以知道,其父类的父类编码是0100 0001001 0000000 0000000 0000000,其父类的父类的父类编码是0100 0000000 0000000 0000000 0000000。(我是不是太罗嗦了J,但这一点很重要。再回头看看我们前面提到的第五个问题。哈哈,这不就已经得到了分类1092787200所在的分类路径了吗?)。
现在我们在一般的情况下来讨论类别编码问题。设类别的层次为k,第i层的编码位数为Ni, 那么总的编码位数为N(N1+N2+..+Nk)。我们就得到任何一个类别的编码形式如下:
2^(N-(N1+N2+…+Ni))*j + 父类编码
其中,i表示第i层,j表示当前层的第j个分类。
这样我们就把任何分类的编码分成了两个部分,其中一部分是它的层编码,一部分是它的父类编码。
由下面公式定一的k个编码我们称为特征码:(因为i可以取k个值,所以有k个)
2^N-2^(N-(N1+N2+…+Ni))
  对于任何给定的类别ID,如果我们把ID和k个特征码"相与",得到的非0编码,就是其所有父类的编码!

位编码算法
对任何顺序编码的Catalog表,我们可以设计一个位编码算法,将所有的类别编码规格化为位编码。在具体实现时,我们先创建一个临时表:
Create TempCatalog(
[OldID] [int] NOT NULL,
[NewID] [int] NOT NULL,
[OldFatherID] [int] NOT NULL,
[NewFatherID] [int] NOT NULL
);
  在这个表中,我们保留所有原来的类别编号OldID和其父类编号OldFatherID,以及重新计算的满足位编码要求的相应编号NewID、NewFatherID。
程序如下:
$#@60;%
REM oConn---数据库连接,已经打开
REM OldFather---原来的父类编号
REM NewFather---新的父类编号
REM N---编码总位数
REM Ni--每一级的编码位数数组
REM Level--当前的级数

sub FormatAllID(oConn,OldFather,NewFather,N,Nm,Ni byref,Level)
strSQL = "select CatalogID , FatherID from Catalog where FatherID=" & OldFather
set rsCatalog=oConn.Execute( strSQL )

j = 1
do while not rsCatalog.EOF
i = 2 ^(N - Nm) * j
if Level then i= i + NewFather


OldCatalog = rsCatalog("CatalogID")
NewCatalog = i

REM 写入临时表
strSQL = "Insert into TempCatalog (OldCatalogID , NewCatalogID , OldFatherID , NewFatherID)"
strSQL = strSQL & " values(" & OldCatalog & " , " & NewCatalog & " , " & OldFather & " , " & NewFather & ")"

Conn.Execute strSQL

REM 递归调用FormatAllID
Nm = Nm + Ni(Level+1)
FormatAllID oConn,OldCatalog , NewCatalog ,N,Nm,Ni,Level + 1

rsCatalog.MoveNext

j = j+1
loop

rsCatalog.Close
end sub
%$#@62;

调用这个算法的一个例子如下:
$#@60;%
REM 定义编码参数,其中N为总位数,Ni为每一级的位数。
Dim N,Ni(5)

Ni(1) = 4

N = Ni(1)

for i=2 to 5
Ni(i) = 7
N = N + Ni(i)
next

REM 打开数据库,创建临时表
strSQL = "Create TempCatalog( [OldID] [int] NOT NULL, [NewID] [int] NOT NULL, [OldFatherID] [int] NOT NULL, [NewFatherID] [int] NOT NULL);"
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open Application("strConn")
Conn.Execute strSQL

REM 调用规格化例程
FormatAllID Conn,-1,-1,N,Ni(1),Ni,0

REM ------------------------------------------------------------------------
REM 在此处更新所有相关表的类别编码为新的编码即可。
REM ------------------------------------------------------------------------

REM 关闭数据库
strSQL= "drop table TempCatalog;"
Conn.Execute strSQL
Conn.Close
%$#@62;

第四个问题
  现在我们回头看看第四个问题:怎样得到某个分类下的所有产品。由于采用了位编码,现在问题变得很简单。我们很容易推算:某个产品属于某个类别的条件是Product.FatherID&(Catalog.ID的特征码)=Catalog.ID。其中"&"代表位与算法。这在SQL Server中是直接支持的。
  举例来说:产品所属的类别为:1092787200,而当前类别为1092780032。当前类别对应的特征值为:4294950912,由于1092787200&4294950912=8537400,所以这个产品属于分类8537400。
我们前面已经给出了计算特征码的公式。特征码并不多,而且很容易计算,可以考虑在Global.asa中Application_OnStart时间触发时计算出来,存放在Application("Mark")数组中。
  当然,有了特征码,我们还可以得到更加有效率的算法。我们知道,虽然我们采用了位编码,实际上还是一种顺序编码的方法。表现出第I级的分类编码肯定比第I+1级分类的编码要小。根据这个特点,我们还可以由FID得到两个特征码,其中一个是本级位特征码FID0,一个是上级位特征码FID1。而产品属于某个分类FID的充分必要条件是:
Product.FatherID$#@62;FID0 and Product.FatherID$#@60;FID1
下面的程序显示分类FID下的所有产品。由于数据表Product已经对FatherID进行索引,故查询速度极快:
$#@60;%
REM oConn---数据库连接,已经打开
REM FID---当前分类
REM FIDMark---特征值数组,典型的情况下为Application("Mark")
REM k---数组元素个数,也是分类的级数
Sub GetAllProduct(oConn,FID,FIDMark byref,k)
REM 根据FID计算出特征值FID0,FID1
for i=k to 1
if (FID and FIDMark = FID ) then exit
next

strSQL = "select Name from Product where FatherID$#@62;"FIDMark(i)&" and FatherID$#@60;"FIDMark(i-1)
set rsProduct=oConn.Execute(strSQL)%$#@62;
$#@60;UL$#@62;$#@60;%
Do While Not rsProduct.Eof%$#@62;
$#@60;LI$#@62;$#@60;%=rsProduct("Name")
Loop%$#@62;
$#@60;/UL$#@62;$#@60;%
rsProduct.Close
End Sub
%$#@62;

关于第5个问题、第6个问题,就留作习题吧。有了上面的位编码,一切都应该迎刃而解。

技术文章快速查找

栏目导航
软件应用
·操作系统 ·杀毒防黑 ·应用软件
·聊天软件 ·网络软件  
Web开发
·ASP ·JavaScript ·CGI
·JSP ·VbScript ·Web服务器
·PHP ·XML  
开发语言
·VB ·VC ·ASP.NET
·Java ·C/C++ ·Delphi
数据库开发
·MySQL ·SQL/Access ·PowerBuilder
·Oracle ·DB2  
网站设计
·Flash ·Dreamweaver ·HTML/CSS
·Fireworks ·FrontPage  
平面设计
·Photoshop ·CorelDraw ·AutoCAD
·FreeHand ·Illustrator ·3DsMAX
媒体动画
·Director ·Authorware ·Maya
·视频处理    


相关软件 产品库推荐
·笔记本 ·台式机 ·服务器
·数码相机 ·手机 ·GPS
·DV摄像机 ·MP3 ·MP4
·CPU ·硬盘 ·内存
·主板 ·显卡 ·显示器
·打印机 ·投影机 ·路由器

还没人留言,抢个先,哈哈!
对"介绍一种效率极高的分类算法"的评论 - 快速回贴
内容:
  [完成后可按Ctrl+Enter发布]

百度中 介绍一种效率极高的分类算法 相关内容
Google搜索中 介绍一种效率极高的分类算法 相关内容
雅虎中 介绍一种效率极高的分类算法 相关内容
Sogou搜索中 介绍一种效率极高的分类算法 相关内容

相关软件 最新回复帖子:

·Windows Vista 中卸载软件的不同方式
·没有mysql支持时的替代方案
·一个可以发送附件及HTML格式邮件的PHP类
·AutoCAD打造精致三维鸟笼实例详解
·Photoshop自定义水晶字特效样式
·AutoCAD三维基础实例教程
·PS为黑背景长发美女照片抠图换背
·用Photoshop自制个性摩托车贴花小经验
·轻松几步将美女照片处理为手工素描
·巧用Photoshop画笔轻松绘制创意特效


  相关软件 介绍一种效率极高的分类算法相关文章
ASP中检查没有数据提交的页面Ⅱ ASP中检查没有数据提交的页面Ⅰ
创建服务器端的ASP搜索组件(二) 创建服务器端的ASP搜索组件(一)
如何增强ASP程序性能(4) 如何增强ASP程序性能(3)
如何增强ASP程序性能(2) 如何增强ASP程序性能(1)
高级表单验证 使用ASP加密算法加密你的数据(二)
使用ASP加密算法加密你的数据(一) 用ASP实现网页保密的两种方法
通过asp入侵web server,窃取文件毁坏 用ASP判断Email地址是否有效
ASP常数 ASP中实用的广告交替组件
如何处理ASP中的图象 用ASP实现网站的“目录树”管理
ASP组件中的安全问题 配置IIS4实现应用程序隔离