递归在C++应用中的利与弊

原创 2003年05月11日 11:31:00

“递归”在C++中主要解决具有树型特征的算法或数据结构,递归的利用可以使算法或数据结构大大简化,代码简洁明了,相同一个具有该特性的课题采用递归或其他算法,所要求的预定义及相应的结果都将不一样,用了递归可能使用减少部份定义,代码实现部份大大减少,一看便知。下面是一个从数据库中取数的例子对比:

实现中所使用的数据结构(表结构)

序号

英文名

中文名

类型

说明

1

Id

权限ID

Int

 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

2

ParentId

父权限ID

Int

用于指定父结点

3

Name

权限名称

Varchar(32)

 

4

IdCode

菜单项ID

int

权限与菜单项关联

由数据结构可以看出,通过ParentId,实现权限的树状结构,来描述权限的层次关系,这是一个典型的树型特征的数据结构,采用递归可以简化程序的实现过程,但通过实验证明简单的采用递归将导致性能上的不足,运行效果无法满足用户的基本操作,在实现递归算法的后面将描述本程序在实现递归中作了相应的处理。

 

1、通过对树结点的记忆来实现假递归

     DWORD dwFunction = 0;       //功能ID

     HTREEITEM hItemLayer[5][2]; //用于保存当前正在操作的结点,用于回溯

     int nIdCollection[5][2];    //保留父层结点的ID,用于识别下一个结点的父层所属

     // 设置树根

     hItemLayer[0][0] = m_treeOperatorPermission.InsertItem(_T("权限设置"),3,3);

     m_treeOperatorPermission.SetItemData (hItemLayer[0][0] , dwFunction);

     hItemLayer[0][1] = hItemLayer[0][0];

     nIdCollection[0][0] = 0;    //父层ID

     nIdCollection[0][1] = 0;    //当前层ID

 

     int nCurParentLay = 0;

     CADORecordset collection(&m_conn);        //ADO对象,用于从数据库取出记录集

     CString strSQLString("select id ,ParentId , Name , IdCode from tbl_function order by id , parentid");

     if(collection.Open (strSQLString))

     {

         int nCount = collection.GetRecordCount ();

         CString strFunctionName;

         for(int i = 0;i <nCount;i ++)

         {

              //从数据库中取出结点数据

              collection.GetFieldValue ("Name" , strFunctionName);

              int nId;

              int nParentId;

              collection.GetFieldValue ("Id" , nId);

              collection.GetFieldValue ("ParentId" , nParentId);

              do

              {

                   //判断其保留的父结点是否一致,用于判断是否从当前插入子结点,还是从父结点插入子结点

                   if(nParentId == nIdCollection[nCurParentLay][0])

                   {

                       //向父层插入子结点,并保留当前结点数据,用于回溯

                       hItemLayer[nCurParentLay][1] = m_treeOperatorPermission.InsertItem ((LPCTSTR)strFunctionName , 0 , 1 , hItemLayer[nCurParentLay][0]);

                       nIdCollection[nCurParentLay][1] = nId;

                       m_treeOperatorPermission.SetHalfChecked (hItemLayer[nCurParentLay][1]);

                        dwFunction = nId;

                       m_treeOperatorPermission.SetItemData (hItemLayer[nCurParentLay][1] , dwFunction);

                   }

                   else if(nParentId == nIdCollection[nCurParentLay][1])

                   {

                       //在当前层建立子层

                       hItemLayer[nCurParentLay + 1][1] = m_treeOperatorPermission.InsertItem ((LPCTSTR)strFunctionName , 0 , 1 , hItemLayer[nCurParentLay][1]);

                       hItemLayer[nCurParentLay + 1][0] = hItemLayer[nCurParentLay][1];

                       nIdCollection[nCurParentLay + 1][0] = nParentId;

                       nIdCollection[nCurParentLay + 1][1] = nId;

                       m_treeOperatorPermission.SetChecked (hItemLayer[nCurParentLay + 1][1] , FALSE);

                       dwFunction = nId;

                       m_treeOperatorPermission.SetItemData (hItemLayer[nCurParentLay + 1][1] , dwFunction);

                       nCurParentLay ++;

                   }

                   else

                   {

                       //回溯,用于找到相匹配的父结点,便于插入结点

                       nCurParentLay --;

                       continue;

                   }

                   break;

              }while(true);

              collection.MoveNext ();

         }

         m_treeOperatorPermission.Expand (hItemLayer[0][0] , TVE_EXPAND);

     }

     collection.Close ();

     m_treeOperatorPermission.ClearALLCheck ();

     return 0;

点评:这种方法是通过状态的方法来实现递归的变相方法,可以看出在代码实现方面相当复杂,程序员必须详细注明其实现过程,才能够使其他程序员读懂(当然注释本来就是应该的,这里所说的是如何让其他程序更容易看懂代码)。

本程序中采用保留从父结点到当前结点的路径,用于回溯找到下一个结点的父结点,程序员是费尽心机,在他走过的足上做个标签,便于他回去是可以认得路,也便于摸索下一条路时不会重复走同样的一条分支(形成死循环)。

优点:该程序只用到一条检索语句即实现权限树的初始化,减少数据库连接数,从而在性能上将会是最优,即实现最其本的数据操作。

缺点:在点评中已经说到,代码的复杂性,给代码隐患的存在带来了很大的可能性,另外对数据也有一定的要求,必须符合一不的顺序才能够被正确执行。

2、递归算法的应用

long InitDefaultPermissionTree(int nParentId ,HTREEITEM hItem)

{

     CString strSQLString;

     strSQLString.Format ("select id , name from tbl_function where parentid = %d" , nParentId);

     CADORecordset collection(&m_conn);

     if(collection.Open (strSQLString))

     {

         //将所有数据取出

         CArray <int  , int >        nIdArray;

         CArray <CString , CString>  strNameArray;

         int nCount = collection.GetRecordCount ();

         for(int i = 0;i < nCount ;i ++)

         {

              int nId;

              CString strName;

              collection.GetFieldValue ("id" , nId);

              collection.GetFieldValue ("name" , strName);

              collection.MoveNext ();

              nIdArray.Add (nId);

              strNameArray.Add (strName);

         }

         collection.Close ();

          //将从数据库中取出的数据插入到树图上

         for(i = 0;i < nCount;i ++)

         {

              int nId = nIdArray.GetAt (i);

              HTREEITEM hSonItem = m_treeOperatorPermission.InsertItem (strNameArray.GetAt (i) , 0 , 0 , hItem);

              m_treeOperatorPermission.SetItemData (hSonItem , nId);

               //后面讲述采用m_TreeDataMap(CMap<int , int &  ,HTREEITEM, HTREEITEM&>)的目的

              m_TreeDataMap.SetAt(nId , hSonItem);

               //对当前结点进行递归插入子结点数据

              InitDefaultPermissionTree(nIdArray.GetAt (i) , hSonItem);

         }

     }

     return 0;

}

点评:在本程序中简单地看去,只用了一个循环即完成数据的读取与显示(本程序采用两个循环只是想减少由于递归而增加数据库连接数),显而易见,代码清晰易懂。不需要太多的注释便可明白其中的实现过程。

在实现过程中没有象第一个例子的那样具有相当多的辅助变量来帮助记忆树的结构,这个实例由递归的特性来完成。

优点:简洁明了,通俗易懂,最大的特点就是执行递归时对其实现的默认,这也是在编写递归程序时应该具备的基本思想认识,不然程序员绝对想不到该算法是可以用递归来实现的。

缺点:第一例中已经说到的优点,其实也就是本例的缺点,递归所产生相应的出入栈操作及相当的其他数据(如数据库连接数等)都将对程序的性能产生负面影响,特别对于层次较多的情况则更为严重,当然对于非树型特征的不提倡采用递归的实现算法,如求1100的累加时,虽然可以用递归算法可以实现,但它仍然可以用常规算法来实现,这里就不提倡递归算法。

正常算法

Int Sum(int nMax)

{

       int nSum = 0;

       for(int I = 1;I <= nMax;I ++)

       {

              nSum += I;

       }

       return nSum;

}

递归算法

Int Sum(int nMax)

{

       if(nMax > 0)

       {

              return Sum(nMax – 1) + nMax;

       }

       else

       {

              return 0;

       }

}

综上所述,递归算法应该用于某些采用常规算法实现较为困难、并且具有递归特征的算法才会采用递归算法,否则不建议变相应用递归算法,如后面所述的计算1100的累加,这里就是坚决否定递归算法的应用。

编写代码应该考虑多方面因素,包括代码的可读性、可理解性、简单性(这个特性有一定的局限性)、执行性能等因素决定。

对于一个性能要求不高但采用递归可以提高代码的可读性与可理解性并且可以大大简化代码的实现过程,递归将是首选。

对于执行性能要求较高时,可能要求程序员采用其他类似的算法来替代,确保性能优先,但部份情况,采用其他算法来替代递归未必能够提高算法的性能,反而递归是最佳算法(一般指需要的递归层次较少)。

总之,使用递归有其利,也有其弊,程序员在实现过程中是否应该采用递归算法,应考虑采用递归算法是否会影响相关模块或系统的整体要求。

递归算法在实际项目中的应用

概述: 最近发现,递归算法在实际项目开发中的应用非常广泛,就最近的一个项目中四次用到了递归算法,因此举出项目开发中的一个例子作为讲解。 递归算法的思想: 递归算法是把问题转化为规模缩小了的同类问题的子...
  • u013456964
  • u013456964
  • 2014年01月14日 18:10
  • 1417

C++递归算法经典实例详解

小白博客,记录多于科普
  • junloin
  • junloin
  • 2017年03月02日 22:25
  • 6056

Java数据结构-线性表之栈的应用-递归及其应用

递归函数的定义:把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数(递归函数必须有一个结束的条件,以免陷入无穷尽的递归中)。迭代和递归的区别是: (1).迭代使用的是循环结构,...
  • yannanying
  • yannanying
  • 2015年08月27日 21:12
  • 751

生活中的递归?

好久没有写了,因为事情太多了。老是觉得有很多事情要做,就是不知道到底在忙什么。然而,最近看了一个故事,我觉得对我触动蛮大的,不知道自己已经想故事里面那样陷入一个递归当中了,写下来提醒大家和我自己吧!故...
  • rainsia_
  • rainsia_
  • 2006年12月21日 10:54
  • 1531

递归函数的应用(一)

递归函数的应用(一)
  • qq_36016407
  • qq_36016407
  • 2017年02月15日 16:14
  • 723

(一)实际项目中树形数据结构与递归算法应用

在每个项目中都涉及到菜单的展示,在这里普遍用到了树形数据结构和递归算法: 举例如下: 建表: 1.模拟效果图: 2.代码片段: 3.数据库中运用sql进行树形查询: mysql比较麻烦...
  • zzw222222
  • zzw222222
  • 2016年10月19日 11:25
  • 1312

C++递归算法:我的理解

递归算法,总结起来具有以下几个特点:     1  它有一个基本部分,即直接满足条件,输出     2  它有一个递归部分,即 通过改变基数(即n),来逐步使得n满足基本部分的条件,从而输出   ...
  • jinghouxiang
  • jinghouxiang
  • 2016年01月26日 00:00
  • 8047

C++抽象编程——回溯算法(4)——回溯在游戏中的应用

回溯法与游戏虽然回溯算法是最容易在迷宫的背景下进行说明的,但这个策略却应用的相当普遍。例如,我们可以对大多数双人游戏应用回溯。第一个玩家有一些初步选择。根据选择哪个动作,第二玩家则具有一组特定的响应。...
  • redRnt
  • redRnt
  • 2017年05月09日 18:46
  • 815

【C++】学习笔记三十七——递归

递归  C++函数可以自己调用自己,与C语言不同的是,C++不允许main()调用自己,这种功能被称为递归。包含一个递归调用的递归  如果递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去...
  • budf01
  • budf01
  • 2017年03月10日 14:35
  • 799

如何从深层递归中跳出?

例如我用递归求一个解,当求到解时我想直接跳到主函数结束程序,怎么做?  可以用什么方法及详细使用方法?谢谢   int   haveSolution   =   0;         void   p...
  • lne818
  • lne818
  • 2008年04月29日 21:09
  • 3697
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:递归在C++应用中的利与弊
举报原因:
原因补充:

(最多只允许输入30个字)