经典算法题目——选课方案设计

来源:互联网 发布:php strcmp漏洞 ctf 编辑:程序博客网 时间:2024/06/13 23:21
 

【问题描述】

大学里实行学分制。每门课都有一定的学。每个学生均需要修满规定数量的课程才能毕业。其中有些课程可以直接修读,有些课程需要一定的基础知识,必须在选了其他一些课程的基础上才能修读。例如,《数据结构》必须在选修了《高级语言程序设计》之后才能选修。我们称《高级语言程序设计》是《数据结构》的“先修课”。在我们的大学里,假定每门课的直接先修课至多只有一门,两门课可能存在相同的先修课。例如:

 

课号

先修课号

学分

1

0

1

2

1

1

3

2

3

4

0

3

5

2

4

上例中,12的先修课,即如果要选修2,则1必定被选。

学生不可能学完大学里开设的所有课程,因此每个学生必须在入学时选定自己要学的课程。每个学生可选课程的总数目是给定的。现在请你们小组编写一个“学生选课系统”,任意给定一种课程体系(总课程数,课程之间的修读先后制约关系,学生毕业要求修的课程数目),该系统能帮助学生找出一种选课方案,使得他能得到的学分最多,并且必须满足先修课程优先的原则

例如:任意给定某一课程体系,该体系总共有7门课,每个学生必须修满4门课,该课程体系中,课程间的修读制约关系见下表:

2

2

0

1

0

4

2

1

7

1

7

6

1

2

2

   

2

6

7

2

3

 

 

 

 

 

 

 

 

   1表示,该课程体系总共有7门课,每个学生必须修满4门课,每行代表一门课。每行有2个数,第一个数是这门课程的先修课程课号(0表示该课不存在先修课程),第二个数是这门课程的学分(学分是不超过10的整数)。表2表示在此课程体系下,学生选择课号为:2673四门课程得到的学分最多,所得到的学分为13

     上面的需求描述中,我们是给定某一课程体系情况下,指定学生毕业所需修读的课程数,求在此约束下的最多学分选课方案。

【基本要求】

 从课程体系文本文件中读取课程信息,生成该课程体系森林,该森林采用二叉链表作为存储结构。

‚ 程序能够先序、中序、后序遍历该二叉树,以便验证所生成的二叉树表示的正确性。

ƒ 指定一个课程数目,生成对应于该课程数目的“最大学分和”及相应选课方案(* 选做内容)。

一、【概要设计】

(本部分应包括:抽象数据类型的功能规格说明、主程序模块、各子程序模块的伪码说明,主程序模块与各子程序模块间的调用关系)

本抽象数据类型CourseBinTree是继承于二叉树类的,并使用类型CourseInfo作为二叉树节点的类型。关于二叉树类型的描述请参看《二叉树类型设计说明》。对于本次实验中的“最优求解问题”,我们小组设计了三种方案,并实现了其中两种,下面以方案I进行描述(关于方案II方案III四、【实验总结】一项中给予简要说明)。

节点抽象数据类型的描述:

class CourseInfo

{

    public:

       // 缺省的构造器,初始化该节点数据

       CourseInfo(void);

       // 带参数的构造器,根据参数初始化节点数据

       CourseInfo(unsigned int courseSerial,  // 课程序号

                   unsigned int courseScore,  // 课程学分值

                   unsigned int precedenceCourse);   // 先修课序号

       unsigned int m_courseSerial;       // 课程序号

       unsigned int m_courseScore;        // 课程学分值

       unsigned int m_precedenceCourse;   // 该课程的先修课程序号

       unsigned int m_subTreeNodeNum;     // 子树节点个数

       unsigned int m_leftSubTreeScoreSum;    // 左子树学分总和

       unsigned int m_rightSubTreeScoreSum;   // 右子树学分总和

       unsigned int m_maxSum[20];     // 在该节点处可以取得的最大学分和域

};

②课程二叉树抽象数据类型的描述

class CourseBinTree : public BinaryTree<CourseInfo>

{

    public:    // 用户接口

       // 缺少的构造器,初始化相关数据

       CourseBinTree(void);

       // 从存储课程信息的文本文件中读取信息

       void LoadInfo(std::string fileName);  

       // 显示所有课程信息

       void DisplayAll(void);

       // 生成课程二叉树

       void GenerateCourseBinTree(void);

       // 为计算该选课数下可获得的最大学分和,输入一个确定的选课数

       void InputElectiveNumber(void);

       // 根据用户输入的选课数计算可获得的最大学分和

       void GetMaxCredit(void);

       // 根据用户输入的选课数显示最大学分选课方案

       void ShowMaxCreditSolution(void);

       // 先序遍历该课程树

       void PreOrderTraverse(void) const;

       // 中序遍历该课程树

       void InOrderTraverse(void)  const;

       // 后序遍历该课程树

       void PostOrderTraverse(void)    const;

       // 按层遍历该课程树

       void LevelOrderTraverse(void)   const;

protected: // 保护的函数

       // 从给定的节点开始先序遍历该节点的子树

       void PreOrder(BinaryTreeNode<CourseInfo> *root)     const;

       // 从给定的节点开始中序遍历该节点的子树

       void InOrder(BinaryTreeNode<CourseInfo> *root)      const;

       // 从给定的节点开始后序遍历该节点的子树

       void PostOrder(BinaryTreeNode<CourseInfo> *root)   const;

       // 从给定的节点开始按层遍历该节点的子树

       void LevelOrder(BinaryTreeNode<CourseInfo> *root)  const;

    private: // 私有的数据或函数

       // 生成课程树的根节点

       void GenerateRoot(void);

       // 生成课程树的非根节点

       void GenerateNode(BinaryTreeNode<CourseInfo> *p);

       // 显示一门课程的信息

       inline void DisplayOne(const list<CourseInfo>::iterator &i);

       // 求给定节点的最大学分

void MaxScoreSum(BinaryTreeNode<CourseInfo> *root,

                         unsigned int m);

       // 显示给定节点的最大学分选课方案

       void DisplayOptionalPlan(BinaryTreeNode<CourseInfo> *root,

                                 unsigned int m);

       unsigned int m_electiveNo;             // 保存用户输入的选课数

       unsigned int m_maxCredit;              // 保存最大学分和

       std::list<CourseInfo> m_courseSList;   // 保存课程信息

       // 课程信息的备份,m_courseSList的一个复本

       std::list<CourseInfo> m_bakCSList;

};

 

 

主程序模块与抽象类型接口的调用关系描述

int main(void)

{

    CourseBinTree myCourse;     // 创建一个课程二叉树对象

    // 读取当前目录下的CourseHerarchy.txt文件

    myCourse.LoadInfo(CourseHierarchy.txt);

    myCourse.DisplayAll();   // 显示所有课程信息

    // 根据课程信息生成课程二叉树

    myCourse.GenerateCourseBinTree();

    // 对该二叉树分别进行先序、中序、后序、层序遍历

    myCourse.PreOrderTraverse();

    myCourse.InOrderTraverse();

    myCourse.PostOrderTraverse();

    myCourse.LevelOrderTraverse();

    // 提示用户输入一个选课门数

    myCourse.InputElectiveNumber();

    // 计算用户输入选课数下可取得的最大学分和

    myCourse.GetMaxCredit();

    // 显示产生最大学分和的选课方案(某一种)

    myCourse.ShowMaxCreditSolution();

 

   return 0;

}

 

 

 

 

二、【详细设计】

(本部分应包括:抽象数据类型具体实现的函数原型说明、 关键操作实现的伪码算法、 函数的调用关系图)

节点抽象数据类型具体实现的函数原型说明

//=============================

// 函数名:CourseInfo

// 功能:缺省的构造函数,初始化该节点的相关数据

// 输入参数:void

// 输出参数:无

CourseInfo::CourseInfo(void)

{

    m_courseSerial = 0;

    m_courseScore = 0;

    m_precedenceCourse = 0;

    m_subTreeNodeNum = 1;    // 节点本身计数

    m_leftSubTreeScoreSum = 0;

    m_rightSubTreeScoreSum = 0;

    memset(m_maxSum, 0, sizeof(m_maxSum));

 

    return;

}

//=============================

// 函数名:CourseInfo

// 功能:带参数的构造函数,根据参数初始化相关数据

// 输入参数:unsigned int courseSerial:课程序号,缺省为

//           unsigned int courseScore:课程学分,缺省为

//           unsigned int precedenceCourse:该课程的先修课序号,缺省为

// 输出参数:无

CourseInfo::CourseInfo(unsigned int courseSerial = 0,

              unsigned int courseScore = 0,

              unsigned int precedenceCourse = 0)

{

    m_courseSerial = courseSerial;

    m_courseScore = courseScore;    

    m_precedenceCourse = precedenceCourse;

    m_subTreeNodeNum = 0;

    memset(m_maxSum, 0, sizeof(m_maxSum));

    m_leftSubTreeScoreSum = 0;

    m_rightSubTreeScoreSum = 0;

  

return;

}

②课程二叉树抽象数据类型具体实现的函数原型说明及关键伪码算法描述

//=============================

// 函数名:CourseBinTree

// 功能:缺省的构造器,初始化成员数据

// 输入参数:void

// 输出参数:无

CourseBinTree::CourseBinTree(void)

{

    m_electiveNo = 0;

    m_maxCredit = 0;

 

    return;

}

//=============================

// 函数名:GenerateRoot

// 功能:生成课程树的根节点

// 输入参数:void

// 输出参数:void

void CourseBinTree::GenerateRoot(void)

{

    for (遍历课程链表)

    {

       if 发现课程先修序号为0 then

       {

           生成根节点;

           在课程链表中删除该课程信息;

           return;

       }

    }

}

//==================================

// 函数名:GenerateNode

// 功能:递归地生成课程树的非根节点

// 输入参数:BinaryTreeNode<CourseInfo> *p:在p指向的节点处生成该节点的子树

// 输出参数:void

void CourseBinTree::GenerateNode(BinaryTreeNode<CourseInfo> *p)

{

    for (遍历课程信息链表)

    {

       // 生成右子树

       if 发现一门课程和指针p所指向的课程有相同的先修课then

       {

           将该课程信息插入到指针p指向节点的右孩子;

           从课程信息链表中删除该课程信息;

           GenerateNode(p的右孩子);    // 递归调用

           for (遍历课程信息链表)

           {

              // 生成右子树

              if 发现一门课程和指针p所指向的课程有相同的先修课then

              {

                  将该课程信息插入到指针p指向节点的右孩子;

                  从课程信息链表中删除该课程信息;

                  GenerateNode(p的右孩子);    // 递归调用

                  return;

              }

              if 发现一门课程是指针p所指向的课程的后继课程then

              {

                  将该课程信息插入到指针p指向节点的左孩子;

                  从课程信息链表中删除该课程信息;

                  GenerateNode(p的左孩子);    // 递归调用

                  return;

               }

           }

       }

       // 生成左子树

       if 发现一门课程是指针p所指向的课程的后继课程then

       {

           将该课程信息插入到指针p指向节点的左孩子;

           从课程信息链表中删除该课程信息;

           GenerateNode(p的左孩子);    // 递归调用

           for (遍历课程信息链表)

           {

              // 生成右子树

              if 发现一门课程和指针p所指向的课程有相同的先修课then

              {

                  将该课程信息插入到指针p指向节点的右孩子;

                  从课程信息链表中删除该课程信息;

                  GenerateNode(p的右孩子);    // 递归调用

                  return;

              }

              if 发现一门课程是指针p所指向的课程的后继课程then

              {

                  将该课程信息插入到指针p指向节点的左孩子;

                  从课程信息链表中删除该课程信息;

                  GenerateNode(p的左孩子);    // 递归调用

                  return;

              }

           }

       }

    }

}

//==================================

// 函数名:InputElectiveNumber

// 功能:让用户输入一个确定的选课数,并保存这个选课数

// 输入参数:void

// 输出参数:void

void CourseBinTree::InputElectiveNumber(void)

{

    do

    {

       用户输入选课数;

       保存这个选课数;

    }while 输入不合法时

 

    return;

}

//==================================

// 函数名:GetMaxCredit

// 功能:根据用户输入的选课数,基于MaxScoreSum函数得出最大学分和

// 输入参数:void

// 输出参数:void

void CourseBinTree::GetMaxCredit(void)

{

    if 用户选定的课程数等于课程总数then

{// 排除一下特殊情况

      直接累加所有课程学分;

}

else

{

      调用MaxScoreSum函数进行最大学分的计算;

}

 

输出计算后的最大学分和;

 

    return;

}

//==================================

// 函数名:ShowMaxCreditSolution

// 功能:基于DisplayOptionalPlan函数显示最大学分的选课方案

// 输入参数:void

// 输出参数:void

void CourseBinTree::ShowMaxCreditSolution(void)

{  

    DisplayOptionalPlan(GetRoot(), m_electiveNo);

 

    return;

}

//==================================

// 函数名:DisplayOne

// 功能:显示一门课程的信息

// 输入参数:const list<CourseInfo>::iterator &i:指向课程链表里的一个元素

// 输出参数:void

inline void CourseBinTree::DisplayOne(const list<CourseInfo>::iterator &i)

{

控制格式地输出迭代器i所在的信息;

   

    return;

}

//==================================

// 函数名:DisplayAll

// 功能:基于DisplayOne函数显示保存在课程链表里的所有课程的信息

// 输入参数:void

// 输出参数:void

void CourseBinTree::DisplayAll(void)

{

    for (使用迭代器i遍历课程信息链表)

    {

       调用DisplayOne显示当前课程信息

    }

 

    return;

}

//==================================

// 函数名:LoadInfo

// 功能:从文本文件中读取课程信息到课程链表里

// 输入参数:string fileName:要打开的文件名及路径

// 输出参数:void

void CourseBinTree::LoadInfo(std::string fileName)

{

    设置文件指针并打开路径为fileName的文本文件;

    if 打开文件正确 then

    {

       while 文件没有读到末尾

       {

           读取一行,生成相应的课程信息结构体并放入到课程链表里;

       }

       关闭文件;

    }

    else

    {

       提示文件打开失败并退出程序;

    }

 

    return;

}

//==================================

// 函数名:GenerateCourseBinTree

// 功能:生成课程二叉树

// 输入参数:void

// 输出参数:void

void CourseBinTree::GenerateCourseBinTree(void)

{

    调用函数GenerateRoot生成根节点;

    调用函数GenerateNode生成非根节点;

    return;

//==================================

// 函数名:PreOrderTraverse

// 功能:先序遍历课程树

// 输入参数:void

// 输出参数:void

void CourseBinTree::PreOrderTraverse(void)    const

{

    输出提示;

    以根为实参调用PreOrder函数;

 

    return;

//==================================

// 函数名:InOrderTraverse

// 功能:中序遍历课程树

// 输入参数:void

// 输出参数:void

void CourseBinTree::InOrderTraverse(void) const

{

    输出提示;

    以根为实参调用InOrder函数;

 

    return;

//==================================

// 函数名:PostOrderTraverse

// 功能:后序遍历课程树

// 输入参数:void

// 输出参数:void

void CourseBinTree::PostOrderTraverse(void)   const

{

    输出提示;

    以根为实参调用PostOrder函数;

 

    return;

//==================================

// 函数名:LevelOrderTraverse

// 功能:按层遍历课程树

// 输入参数:void

// 输出参数:void

void CourseBinTree::LeveOrderTraverse(void)   const

{

    输出提示;

    以根为实参调用LeveOrder函数;

 

    return;

//==================================

// 函数名:PreOrder

// 功能:从给定的节点处先序遍历其子树

// 输入参数:BinaryTreeNode<CourseInfo> *root:指向给定的节点

// 输出参数:void

void CourseBinTree::PreOrder(BinaryTreeNode<CourseInfo> *root) const

{

    输出root指向节点的信息;

    PreOrder(root的左孩子指针);

    PreOrder(root的右孩子指针);

   

return;

}

//==================================

// 函数名:InOrder

// 功能:从给定的节点处中序遍历其子树

// 输入参数:BinaryTreeNode<CourseInfo> *root:指向给定的节点

// 输出参数:void

void CourseBinTree::InOrder(BinaryTreeNode<CourseInfo> *root)  const

{

    InOrder(root的左孩子指针);

    输出root指向节点的信息;

    InOrder(root的右孩子指针);

   

return;

}

//==================================

// 函数名:PostOrder

// 功能:从给定的节点处后序遍历其子树,并计算该节点的子树节点个数

// 输入参数:BinaryTreeNode<CourseInfo> *root:指向给定的节点

// 输出参数:void

void CourseBinTree::PostOrder(BinaryTreeNode<CourseInfo> *root)    const

{

    PostOrder(root的左孩子指针);

    PostOrder(root的右孩子指针);

    输出root指向节点的信息;

    if 该节点存在左子树和右子树then

    {

       该节点的子树节点个数+= 左子树中的节点个数+ 右子树中的节点个数;

    }

    else if 该节点只存在左子树then

    {

       该节点的子树的节点个数+= 左子树中的节点个数;

    }

    else if 该节点只存在右子树then

    {

       该节点的子树的节点个数+= 右子树中的节点个数;

    }

 

    return;

}

//==================================

// 函数名:LevelOrder

// 功能:从给定的节点处后序遍历其子树

// 输入参数:BinaryTreeNode<CourseInfo> *root:指向给定的节点

// 输出参数:void

void CourseBinTree::LevelOrder(BinaryTreeNode<CourseInfo> *root) const

{

    if root不为空then

    {

       root进队q;

    }

 

    while (q不空)

    {

       root = q出队;

       输出root指向节点的信息;

 

       if  root有左孩子then

       {

           root的左孩子进队q;

       }

       if  root有右孩子then

       {

           root的右孩子进队q;

       }

    }

    return;

}

//==================================

// 函数名:MaxScoreSum

// 功能:计算在给定节点处的可取到的最大学分和

// 输入参数:BinaryTreeNode<CourseInfo> *root:指向给定的节点

//           unsigned int m:在该节点处应该选的课程门数

// 输出参数:void

void CourseBinTree::MaxScoreSum(BinaryTreeNode<CourseInfo> *root,

                                 unsigned int m)

{

    if ((NULL != root) && (m > 0) && (root->GetData().m_maxSum[m] == 0)

      && (root->GetData().m_subTreeNodeNum >= m))

   {

      左子树根指针rlstp = root->GetLeftChild();

右子树根指针rrstp = root->GetRightChild();

     

      // 进行m+1 种方案的学分和比较

      for (k = 0; k <= m; k++)

      { 

         if ((NULL == rlstp) || (0 == k))

         {// k,说明不选当前根

            左子树可取最大学分和清零;

         }

         else // (NULL != rlstp) && (0 != k)

         {// k不为,说明选中当前根,且当左子树存在时

          // 求解左子树选课数为k-1 的最大学分和

            MaxScoreSum(rlstp, k - 1);

            左子树可取到的最大学分= rlstp->m_maxSum[k-1];

         }

         if (NULL == rrstp)

         {

            清除右子树可取到的最大学分;

         }

         else

         {// 右子树不空的话,从右根出发,

          // 求解选课数为m-k 的最大学分和

            MaxScoreSum(rrstp, m - k);

            右子树可取到的最大学分 = rrstp->m_maxSum[m-k];

         }  

        

         if (0 == k)

         {

当前根可取到的最大

            = max{ 当前根可取到的最大, 右子树可取到的最大 };

         }

         else

         {

当前根可取到的最大

             = max{ 当前根可取到的最大,

左子树可取到的最大

+右子树可取到的最大

+当前根学分}

}

      }

   }

 

    return;

}

//==================================

// 函数名:DisplayOptionalPlan

// 功能:输出最大学分和选课方案(某一种)

// 输入参数:BinaryTreeNode<CourseInfo> *root:指向给定的节点

//           unsigned int m:在该节点处应该选的课程门数

// 输出参数:void

void CourseBinTree::DisplayOptionalPlan(

BinaryTreeNode<CourseInfo> *root,

                      unsigned int m)

{

if((NULL != root) && (m > 0)

&& (root->GetData().m_subTreeNodeNum >= m))

   {

      左子树根指针rlstp = root->GetLeftChild();

右子树根指针rrstp = root->GetRightChild();

     

      // 进行m+1 种方案的学分和比较

      for (k = 0; k <= m; k++)

      { 

         if ((NULL == rlstp) || (0 == k))

         {// k,说明不选当前根

            左子树可取最大学分和清零;

         }

         else // (NULL != rlstp) && (0 != k)

         {// k不为,说明选中当前根,且当左子树存在时

          // 求解左子树选课数为k-1 的最大学分和

            MaxScoreSum(rlstp, k - 1);

            左子树可取到的最大学分= rlstp->m_maxSum[k-1];

         }

         if (NULL == rrstp)

         {

            清除右子树可取到的最大学分;

         }

         else

         {// 右子树不空的话,从右根出发,

          // 求解选课数为m-k 的最大学分和

            MaxScoreSum(rrstp, m - k);

            右子树可取到的最大学分 = rrstp->m_maxSum[m-k];

         }

         if (当前根可取到的最大= 右子树可取到的最大)

             || = (左子树可取到的最大

                   +右子树可取到的最大

                  +当前根学分) then

         {

                if k > 0 then

{

                   输出当前课程;

}

DisplayOptionalPlan(rlstp, k - 1);

DisplayOptionalPlan(rrstp, m - k);

}

}

}

【实验总结】

(本部分应包括:自己在实验中完成的任务,注意组内的任意一位同学都必须独立完成至少一项接口的实现;对所完成实验的经验总结、心得)

      本小组共有两人,李丽嫒(20051120241)和我。李丽嫒完成的是二叉树类的设计与实现。基于李丽嫒提供的二叉树类,我完成了课程二叉树类的设计与实现,并解决了“最大学分和与相应选课方案的输出”问题。在整个实验的过程中,我们小组体会到了分工合作的重要性,初步体会到了软件工程思想在实际项目中的意义。

    关于“最大学分和”的问题,我们组设计了三个方案,并成功实现了其中两个方案——方案I和方案II。方案III实现并测试了核心模块。下面分别对这三个方案的主要思想进行简述:

方案I(即本次报告里实现的方案)

本方案采用动态规划思想求解最大学分和。设 N = { 1,2,, i ,, n }是节点集,wii的学分 rlsti i的左子树根,rrsti i的右子树根。 S[i,m](1in,0mn)是二叉树中,在节点i为根的树中选取m门课时所能获得的最大学分和。显然,我们的目标就是要求出S[1,m]

用动态规划思想解决问题,关键是要构造出该问题最优解的结构。对于上述问题,我们可定义下面描述S[i,m]的递归函数,此函数就描述了最优解的结构。 

 

方案II

   本方案采用穷举的思想求解最大学分和。在课程二叉树结构中,每个节点都有两种状态——被标记和不被标记。当一个节点被标记时,说明该节点被选入了候选方案。此时,选课剩余数-1,并且可进入该节点的左子树和右子树;当一个节点没有被选中时,选课不变,不可进入该节点的左子树,但可以进入到它的右子树。

    以上所说的“进入”分为两种情况:标记进入与非标记进入。标记进入指的就是移动到该节点并且标记该节点;非标记进入只是移动到该节点,并不标记该节点。

    对于一个给定的选课数m,当从课程树的根开始进行以上进程的穷举时,m递减到0说明找到了一个候选方案。当每次找到一个方案的时候都将这个方案存放到候选方案集合里,最后在集合中过滤掉非优方案。下面给出的是穷举进程的伪码描述:

 

// 输入参数说明:

// root:指向课程树中的一个节点指针

// m:在root指向的节点处选定的m门课程数

// flag:标记root指向的节点是否被选中

void EnumSolution(BinTreeNodePtr *root, int m, bool flag)

{

    if (NULL != root && m >= 0)

    {

        BinTreeNodePtr *rst = root的左孩子指针;

        BinTreeNodePtr *lst = root的右孩子指针;

        // 记录标记状态

        root->m_usedFlag = flag;

        if (0 == m)// 说明找到了一个可选方案

        {

           遍历课程树,将标记的课程记录到候选方案集合;

           return;

         }

         if (flag)// 说明当前节点被选中

         {

             if (NULL != lst)

             {

                // 左孩子标记进入

                  EnumSolution(lst, m - 1, true);

                // 左孩子非标记进入

                EnumSolution(lst, m - 1, false);

             }

            if (NULL != rst)

            {

             // 右孩子标记进入

                EnumSolution(rst, m - 1, true);

                // 右孩子非标记进入

                EnumSolution(rst, m - 1, false);

            }

         }

        else    // 当前节点没有被选中

        {

            if (NULL != rst)

            {

                 // 右孩子标记进入

                 EnumSolution(rst, m, true);

                 // 右孩子非标记进入

                EnumSolution(rst, m, false);

            }

        }

    }

    return;

}

方案III

    本方案采用分治的思想进行最大学分和的求解。当1a1 a2 .. am TreeHigh,其中ai为所选的课程数目。设 Ni = { sa1, sa2, , sam }是节点im门课时可以获得的学分和的集合。显然,我们的首要任务就是求解N1

    下面给出的是求解N1的伪码描述:

// 功能:采用后序遍历方式产生基本选课组合,以及在该组合下可获得的学分值

// 输入参数:BinTreeNodePtr *root:指向课程树中的一个节点

// 输出参数:void

void BasicElectiveStatus(BinTreeNodePtr *root)

{

    if (NULL != root)

    {

       BasicElectiveStatus(root的左孩子);

       BasicElectiveStatus(root的右孩子);

 

       if (root是整棵树的根) then

       {// 合并左树和右树的情况

           MergeLTreeAndRTree(root);

 

           return;

       }

 

       if (IsLeftChild(root))

       {

           MergeLTreeAndRTree(root);

            // 计算左子树学分情况

           CalcLTreeCredit(root);

       }

 

       if (IsRightChild(root))

       {

           MergeLTreeAndRTree(root);

           // 计算右子树学分情况

           CalcRTreeCredit(root);

       }

    }

 

    return;

}

// 功能:统计root指向节点的左右子树学分与选课情况

// 输入参数:BinTreeNodePtr:指向课程树中的一个节点

// 输出参数:void

void MergeLTreeAndRTree(BinTreeNodePtr *root)

{

    将节点本身放入root->RightElections;

    root->LeftElectionsroot->RightElections放入到

        root->AllElections

 

    return;

}

// 功能:计算右子树学分组合情况

// 输入参数:BinTreeNodePtr:指向课程树中的一个节点

// 输出参数:void

void CalcRTreeCredit(BinTreeNodePtr *root)

{

    for 遍历root->AllElections里的元素

    {

        将每个元素的选课数和在该选课数的可获得的学分

        放入到root父亲->RightElections;

    }

 

   return;

}

// 功能:计算左子树学分组合情况

// 输入参数:BinTreeNodePtr:指向课程树中的一个节点

// 输出参数:void

void CalcLTreeCredit(BinTreeNodePtr *root)

{

    for 遍历root->AllElections里的元素

    {

       将每个元素的选课数加和在该选课数的可获得的学分+父亲节点的学分

        放入到root父亲->LeftElections;

    }

    在求解出N1后对用户给出的m进行拆分,产生一系列的拆分部分,使得

m = x1 + x2 + + xn = y1 + y2 + + ym = = m。将产生的这一系列拆分部分别与N1中的a1a2 进行匹配。每一次匹配都可以产生一个学分和Sa,其中最大的Smax就是我们要求解的最大学分和。

三种方案的分析:

方案I:采用动态规划的思想,对于大规模的课程体系能够有效进行求解,效率高,对于存储空间需要较少。但是当存在两种课程方案或两种以上课程方案的最大学分和相同时很难进行课程方案的输出。不过,无疑,本方案是求解大规模时最大学分和的最优可选方案之一。

方案II:采用穷举的思想,对于小规模的课程体系能够求解,并且能够输出相同最大学分时的选课方案。但是当规模稍大的时候相当容易造成栈区的溢出,导致求解失败。不过,本方案适合对于规模较小,且容易产生相同最大学分和的课程体系。

方案III:采用分治的思想,对于任何情况都能进行求解,但是比较耗费存储空间。该算法主要将时间耗费在对N1的求解和m的拆分上。不过它可以输出相同最大学分和时的课程体系。而且,对于N1的求解只需要进行一次,以后都使用N1里的基本选课方案进行m拆分的匹配。所以第二次或以后用户输入的m进只要进行m的拆分与相应的匹配就可以了。所以本方案适合于重复求不同m下最大学分和的情况。

针对各方案的缺点,提出改进思路如下:

方案I:对于遇到相同最大学分子方案的时候,分别标记结点,以区分各相同选课方案,这样便可以进行相同最大学分选课方案的输出。

方案II在穷举的进程中先排除掉一些已经不可能的选课方案,使穷举的规模缩小。

在链接程序的时候适当设置栈区的大小,降低栈区溢出的可能性。

方案III采用多线程技术,一个线程在课程二叉树建立好便进行N1的求解,其它线程提供给用户接口。

          优化对m的拆分子程序。

 

 

下面是对方案I的测试文件和工程文件:
http://download1.csdn.net/down3/20070615/15125430592.rar
原创粉丝点击