算法实验报告

算法实验报告

贪心算法

贪心算法概述

  • 算法原理

    • 贪心算法总是做出当前最好的选择,它期望通过局部最优选择从而得到全局最优的解决
      方案。贪心算法的基本思路如下:
      1:建立数学模型来描述问题。
      2:把求解的问题分成若干个子问题。
      3:对每一子问题求解,得到子问题的局部最优解。
      4:把子问题的解局部最优解合成原来解问题的一个解。
  • 基本思想

    • 贪心法是一种解决最优问题的策略。它是从问题的初始解出发,按照当前最佳的选择,
      把问题归纳为更小的相似的子问题,并使子问题最优,再由子问题来推导出全局最优解。使
      用贪心方法需要注意局部最优与全局最优的关系,选择当前状态的局部最优并不一定能推导
      出问题的全局最优。
  • 特点

    • 1.贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择来达到,这样的
      选择称为贪心选择。这些选择只依赖于以往所做过的选择,决不依赖于将来的选择。
      2.最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子
      结构性质。

算法实例

  • 背包问题

    • 问题描述:现有很多物品(可分割),我们知道它们每个物品的单位重量的价值 v 和重量 w;如果给你一个背包它能容纳的重量为 m,如何把物品装到背包里,使得背包里的物品的价值总和
      最大。
      输入:第一行输入一个正整数 n(1<=n<=5),表示有 n 组测试数据;
      随后有 n 组测试数据,每组测试数据的第一行有两个正整数 s,m(1<=s<=10);s 表示有 s
      个物品。接下来的 s 行每行有两个正整数 v,w。
      输出:输出每组测试数据中背包内的物品的价值和,每次输出占一行。
    • 代码:
 #include <iostream>
 #include <algorithm>
 using namespace std;
 struct node
 {
 int v, w; //v 为价值、w 为物品质量
 }a[101];

 int cmp(node A, node B)
 { //根据价值降序排序
 return A.v > B.v;
 }
 int main()
 {
 int sum, num, cap, n; //n 为匹配次数
 cin >> n;
 while (n--)
 {
 sum = 0;
 cin >> num >> cap;//num 为物品个数,cap 为背包容量
 for (int i = 0; i < num; i++)
 cin >> a[i].v >> a[i].w;
 sort(a, a + num, cmp); //价值排序
 /*贪心算法核心*/
 for (int i = 0; i < num; i++)
 {
 if (cap < a[i].w)
 {
 sum = sum + a[i].v*cap;
 cap = 0;
 break;
 }
 else
 {
 sum = sum + a[i].v*a[i].w;
 cap = cap - a[i].w;
 }
 }
 cout << sum;
 }
 return 0;
 }
- 运行结果:

在这里插入图片描述

  • 活动安排问题

    • 问题描述:
      活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。该问题要求高效
      地安排一系列争用某一公共资源的活动。贪心算法提供了一个简单、漂亮的方法使得尽可能
      多的活动能兼容地使用公共资源。
      设有 n 个活动的集合 E={1,2,…,n},其中每个活动都要求使用同一资源,而在同一时间
      内只有一个活动能使用这一资源。每个活动 i 都有一个要求使用该资源的起始时间 si 和一个
      结束时间 fi,且 si <fi 。如果选择了活动 i,则它在半开时间区间[si, fi)内占用资源。若区间[si,
      fi)与区间[sj, fj)不相交,则称活动 i 与活动 j 是相容的。也就是说,当 si≥fj 或 sj≥fi 时,活动 i
      与活动 j 相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。
    • 代码:
 #include <iostream>
 #include <algorithm>
 #include <time.h>
 using namespace std;

 struct ActionTime
 {
 int index; //活动编号
 int start; //开始时间
 int end; //结束时间
 };
 bool cmp(const ActionTime &a, const ActionTime &b)
 {//按照活动结束时间实现升序
 if (a.end <= b.end)
 {
 return true;
 }
 return false;
 }
 int main()
 {
 int ActionsGroups; //所有的活动组数    
 cout << "请输入所有的活动组数:";
 cin >> ActionsGroups;
 ActionTime *act = new ActionTime[ActionsGroups];
 cout << "请输入所有活动详情:";
 for (int i = 0; i < ActionsGroups; i++)
 {//输入所有活动详情
 cin >> act[i].index >> act[i].start >> act[i].end;
 }
 sort(act, act + ActionsGroups, cmp);//排序
 int currentaction = 0, count = 0;
 int ArrangeAction[100];
 ArrangeAction[count] = act[0].index;//将第一个活动放入
 for (int j = 1; j < ActionsGroups; j++)
 {
 if (act[j].start >= act[currentaction].end)//不冲突
 {
 currentaction = j;
 ArrangeAction[++count] = act[j].index;
 }
 }
 cout << "活动安排如下:";
 for (int k = 0; k <= count; k++)
 {
 cout << ArrangeAction[k] << " ";
 }
 delete[] act;
 system("pause");
 return 0;
 }
- 运行结果:

在这里插入图片描述

  • 最优装载问题

    • 问题描述:
      某艘船的载重量为 C,每件物品的重量为 wi,要将尽量多的物品装入到船上。把 n 件物
      品 从小到大排序,然后根据贪心策略尽可能多的选出前 i 个物品,直到不能装为止。
    • 代码:
 #include <iostream>
 #include <algorithm>

 using namespace std;
 int w[1000]; //每件货物的重量

 int main()
 {
 int cap , n;//cap 为载重量,n 为货物数
 int num = 0;//装入货物的数量
 int sum = 0;//装入货物的总重量
 cout << "请依次输入总载重量以及货物总数量:" << endl;                                               
 cin >> cap >> n;
 cout << "请依次输入每件货物的重量:" << endl;
 for(int i=0;i<n;i++)
 {//输入每件货物的重量
 cin>>w[i];
 }
 sort(w,w+n);
 for(int i=0;i<n;i++)
 {
 sum+=w[i];
 if(sum>=cap)
 {
 if(sum==cap)//最后一个能装进去
 {
 num=i+1;
 }
 else
 {
 num=i;//最后一个不能装进去
 }
 break;
 }
 cout<<i+1<<" "<<w[i]<<endl;
 }
 system("pause");                               
 return 0;
 }
- 运行结果:

在这里插入图片描述

哈夫曼编码

哈夫曼树概述

  • 哈夫曼树简介
    哈夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度最短的树。假设有
    n 个权值{w1,w2,…,wn},如果构造一棵有 n 个叶子节点的二叉树,而这 n 个叶子节点的权
    值是{w1,w2,…,wn},则所构造出的带权路径长度最小的二叉树就被称为赫夫曼树。
    根据节点的个数以及权值的不同,赫夫曼树的形状也各不相同,赫夫曼树具有如下特
    性:
    • 对于同一组权值,所能得到的赫夫曼树不一定是唯一的。
    • 赫夫曼树的左右子树可以互换,因为这并不影响树的带权路径长度。
    • 带权值的节点都是叶子节点,不带权值的节点都是某棵子二叉树的根节点。
    • 权值越大的节点越靠近赫夫曼树的根节点,权值越小的节点越远离赫夫曼树的根节
    点。
    • 赫夫曼树中只有叶子节点和度为 2 的节点,没有度为 1 的节点。
    • 一棵有 n 个叶子节点的赫夫曼树共有 2n-1 个节点。
  • 哈夫曼树的构建
    • 将给定的 n 个权值看做 n 棵只有根节点(无左右孩子)的二叉树,组成一个集合
    HT,每棵树的权值为该节点的权值。
    • 从集合 HT 中选出 2 棵权值最小的二叉树,组成一棵新的二叉树,其权值为这 2 棵二
    叉树的权值之和。
    • 将步骤 2 中选出的 2 棵二叉树从集合 HT 中删去,同时将步骤 2 中新得到的二叉树加
    入到集合 HT 中。
    • 重复步骤 2 和步骤 3,直到集合 HT 中只含一棵树,这棵树便是赫夫曼树。
  • 运行结果:

算法实现

  • 问题描述:
    输入:第一行输入一个正整数 n,表示有哈夫曼树结点数;
    第二行依次输入 n 个数字,分别代表每一个结点的权值。 输出:从根结点到叶子结点编码结果以及最小带权路径
  • 代码:
	#include <iostream>
	#include <cstring>
	#define MaxSize 1000
	using namespace std;
	
	/*结点存储结构*/
	typedef struct node {
	    int weight;
	    int parent;
	    int lchild, rchild;
	}HTnode,*HuffmanTree;
	
	typedef char **HuffmanCode;//用于存储每个叶子结点的哈夫曼编码

	void select_minnum(HuffmanTree HT,int k,int &min1, int &min2)
	{
	    int count=0;
	    do{
	        int i=0;
	        int min;
	        int min_weight;
	
	        while(HT[i].parent!=-1){i++;}
	        min_weight=HT[i].weight;
           min=i;//找到第一个parent为-1的结点并进行赋值
	
	        for(;i<k;i++)
	        {//找出weight最小点并进行赋值
	            if(HT[i].weight<min_weight&&HT[i].parent==-1)
	            {
	                min_weight=HT[i].weight;
                   min=i;
	            }
	        }
	        HT[min].parent=1;//下一次比较时将其排除在外
	        if(count==0){min1=min;}
	        if (count==1)
	        {
	            min2=min;
	        }
	    }while (count<=1);
	}
	
	HuffmanTree create_HuffmanTree(int *wet, int n) {
	    /*根据给定的n个权值构造一棵哈夫曼树,wet中存放n个权值*/
	    int totalnode = 2 * n - 1; /*总结点数*/
      HuffmanTree HT = (HuffmanTree)malloc(totalnode * sizeof(HTnode));
	    if (!HT) {
	        cout << "内存分配失败!";
	        exit(-1);
	    }
	    int i;
	    for (i = 0; i < n; i++)
	    {/*n个叶子结点赋值*/
	        HT[i].parent = -1;
	        HT[i].lchild = -1;
	        HT[i].rchild = -1;
          HT[i].weight = *wet;
	        wet++;
	    }
	    for (i = n; i < totalnode; i++) {/*中间结点*/
	        HT[i].parent = -1;
	        HT[i].lchild = -1;
	        HT[i].rchild = -1;
          HT[i].weight = 0;
	    }
	
	    int chooseleftnode,chooserightnode;
	    for (i = n; i < totalnode; i++)
	    {
	        select_minnum(HT,i,chooseleftnode,chooserightnode);
	        HT[chooseleftnode].parent=i;
	        HT[chooserightnode].parent=i;
	        HT[i].lchild=chooseleftnode;
	        HT[i].rchild=chooserightnode;
	        HT[i].weight=HT[chooseleftnode].weight+HT[chooserightnode].weight;
	    }
	    return HT;
	}
	
	void HuffmanCoding(HuffmanTree HT,HuffmanCode &HC,int n)
	{
	    HC=(HuffmanCode)malloc(n*sizeof(char *));
	    if (!HC)
	    {
	        cout<<"HuffmanCode malloc faild!"<<endl;
	        exit(-1);
	    }
	
	    char *code = (char *)malloc(n * sizeof(char));
      if (!code)
	    {
	        cout<<"code malloc faild!"<<endl;
	        exit(-1);
	    }
	
	    code[n-1]='\0';//编码结束符
	
	    int i;
	    for (i = 0; i < n; i++)
	    {
	        int current = i;           //定义当前访问的节点
	        int father = HT[i].parent; //当前节点的父节点
	        int start = n - 1;           //每次编码的位置,初始为编码结束符的位置
	                                     //从叶子节点遍历赫夫曼树直到根节点
	        while (father != -1)
	        {
	            if (HT[father].lchild == current)   //如果是左孩子,则编码为0
	                code[--start] = '0';
	            else                              //如果是右孩子,则编码为1       
	                code[--start] = '1';
	            current = father;
	            father = HT[father].parent;
	        }
	
	        //为第i个字符的编码串分配存储空间
	        HC[i] = (char *)malloc((n - start) * sizeof(char));
	        if (!HC[i])
        {
	            cout<<"HC[i] malloc faild!"<<endl;
	            exit(-1);
	        }
	        //将编码串从code复制到HC
	        strcpy(HC[i], code + start);
	    }
	    for (int i = 0; i < n; ++i) {
	        cout<<HC[i]<<endl;
	    }
	    free(code); //释放保存编码串的临时空间
	}
	
	int countWPL(HuffmanTree HT, int n)
	{	    int i,countRoads,WPL=0;
	    /*
	    由creat_huffmanTree()函数可知,HT[0]、HT[1]...HT[n-1]存放的就是各个叶子结点,
	    所以挨个求叶子结点的带权路径长度即可
	    */
	    for (i = 0; i < n; i++)
	    {
	        int father = HT[i].parent; //当前节点的父节点
	        countRoads = 0;//置当前路径长度为0
	        //从叶子节点遍历赫夫曼树直到根节点
	        while (father != -1)
	        {
	            countRoads++;
	            father = HT[father].parent;
	        }
	        WPL += countRoads * HT[i].weight;
	    }
	    return WPL;
	}
	
	int main()
	{
        int w[MaxSize];
	    int n;
	    cout << "请输入结点个数:";
	    cin>>n;
	    cout << "请依次输入各个结点的权值:";
	    for(int i=0;i<n;i++)
	    {
	        cin>>w[i];
	    }
	    HuffmanCode HC=NULL;
	    HuffmanTree htree=create_HuffmanTree(w,n);
	
	    int wpl=countWPL(htree,n);
	    cout<<"从叶子结点开始遍历二叉树求最小带权路径长度WPL=" <<wpl;
	
	    cout<<"从叶子到根结点编码结果为:"<<endl;
       HuffmanCoding(htree,HC,n);
	
	    system("pause");
	    return 0;
	}

  • 运行结果:
    在这里插入图片描述

证明最优

  • 证明三元哈夫曼算法中的最优前缀码具有贪心选择性质
    令C是编码字符集,C中字符c出现的频率为f©。设x,y是C中具有最小频率的两个字符,存在C 的最优前缀码使x,y具有相同码长且只有最后一位编码不同。
    证明:设二叉树T表示C的任意一个最优前缀码。下面证明可以对T作适当修改后得到一棵新树T’’,使得在新树中x,y是最深叶子且为兄弟。同时新树T’‘表示的前缀码也是C的最优前缀码。如果能做到这一点,则x,y在T’‘表示的最优前缀码中就具有相同的码长且仅最后一位编码不同。
    设b,c是树T的最深叶子且为兄弟。不失一般性可设f(b)≤f©, f(x)≤f(y)。由于x,y,是C中具有最小频率的两个字符,故f(x)≤f(b),f(y)≤f©。
    首先在树T中交换叶子b和x的位置得到树T’,然后在树T’中再交换叶子c和y的位置得到树T’’,如下图所示。
    在这里插入图片描述

由此可知,树T和T’‘表示的最优前缀码的平均码长之差为:
B(T)-B(T’)=∑c∈C f©dT©- ∑c∈C f©dT’©
= f(x)dT(x)+ f(b)dT(b)- f(x)dT’(x)- f(b)dT’(b)
= f(x)dT(x)+ f(b)dT(b)- f(x)dT(b)- f(b)dT(x)
=(f(b)-f(x))( dT(b)- dT(x)) ≥0
类似地,可以证明在T’中交换y与c的位置也不增加平均码长,即B(T’)-B(T’’)也是非负的,在T’‘中交换z与d的位置与此同理。由此可知B(T’’)≤B(T’) ≤ B(T)。另一方面,由于T所表示的前缀码是最优的,故B(T)≤B(T’’)。因此,B(T)=B(T’’),即T’'表示的前缀码也是最优前缀码,且x,y,具有最长的码长,同时仅最后一位编码不同。

  • 证明三元哈夫曼算法中的最优前缀码具有优子结构性质
    设T表示字符集C的一个最优前缀码的完全二叉树。C中字符c的出现频率为f©。设x,y是树T中的两个叶子且为兄弟,z是它们的父亲。若将z看作是具有频率f(z)=f(x)+f(y)的字符,则树T’=T-{x,y}表示字符集C’=C-{x,y}∪{z}的一个最优前缀码。
    证明:首先证明T的平均码长B(T)可用T’的平均码长B(T’)表示。
    对任意c∈C-{x,y}有dT©=dT’ ©,故f©dT©=f©dT’ ©。
    另一方面,dT(x)=dT(y)=dT’ (z)+1
    故f(x)dT(x)+f(y)dT(y) =(f(x)+f(y))( dT’ (z)+1)
    =f(x)+f(y)+f(w) dT’ (z)
    由此即知,B(T)=B(T’)+f(x)+f(y)
    若T’所表示的字符集C’的前缀码不是最优前缀码,则有T’‘表示的C’的前缀码使得B(T’’)<B(T’)。由于z被看作是C’中的一个字符,故z在T’‘中是一树叶。若将x,y加入树T’‘作为z的儿子,则得到表示字符集C的最优前缀码的二叉树T’’’,且有
    B(T’’’)=B(T’’)+ f(x)+f(y)<B(T’)+ f(x)+f(y) =B(T)
      这与T的最优性矛盾,故T’所表示的C’的前缀码是最优的。
  • 由贪心选择性质和最优子结构性质可以推出哈夫曼算法是正确的,即哈夫曼树产生C的一颗最有前缀编码树。

回溯法

回溯法概述

  • 算法原理
    回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
  • 基本思想
    回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含(剪枝过程),则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
    为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数来处死(剪枝)那些实际上不可能产生所需解的活结点,以减少问题的计算量。具有限界函数的深度优先生成法称为回溯法。
  • 两个常用的剪枝函数
    约束函数:在扩展结点处减去不满足约束的子数。
    限界函数:减去得不到最优解的子树。

算法实例

  • 0-1背包问题

    • 问题描述:
      给定N中物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为C。问应该如何选择装入背包的物品,使得转入背包的物品的总价值为最大?
    • 代码:
	#include <iostream>
	#include <algorithm>
	#define maxSize 1000
	using namespace std;
	
	int n;//物品数量
	int c;//背包容量
	double w[maxSize];//存放物品重量的数组 
	double v[maxSize];//存放物品价值的数组
	int order[maxSize];//物品编号1~n
	
	int best_x[maxSize];//用于记录回溯过程的最优情况 
	double vw[maxSize];//物品单位重量价值 
	int x[maxSize];//记录当前物品是否装入背包 
	int bestv;//最优价值 best value
	int cw;//当前重量 current weight 
	int cv;//当前价值 current value
	
	/*根据单位重量价值进行冒泡排序*/
	void Order()
	{
	    for(int i=1;i<=n;++i)
	        vw[i]=v[i]/w[i];//单位重量价值
	    for(int i=1;i<=n-1;i++)
	    {
	        for(int j=i+1;j<=n;j++)
	            if(vw[i]<vw[j])//冒泡排序vw[],order[],v[],w[]
	        {   double temp;
	            temp = vw[i];  
	            vw[i]=vw[i];
	            vw[j]=temp;
	 
	            temp=order[i];
	            order[i]=order[j];
	            order[j]=temp;
 
	            temp = v[i];
	            v[i]=v[j];
	            v[j]=temp;
	 
	            temp=w[i];
	            w[i]=w[j];
	            w[j]=temp;
	        }
	    }
	}
	
	int Bound(int i)//限界函数:该函数返回装入所有剩余物品后(不能超过c的前提下)的价值 
	{
	    int cleft=c-cw;//剩余容量
	    int value=cv;
	    while(i<=n&&w[i]<=cleft)
	    {
	        cleft-=w[i];
	        value+=v[i];
	        ++i; 
	     } 
	    if(i<=n) value+=v[i]*cleft/w[i];
	    return value; 
	}
	
	void Backtrack(int i)
	{
	    if(i>n)//到达根节点且根节点处理完毕
	    {   for(int i = 1; i <= n; i++)
	    	best_x[i] = x[i];   //记录回溯的最优情况
	        bestv=cv;//更新最优的价值 
	        return;
	    }
	    else//处理中间过程的节点 
	    {
	        if(cw+w[i]<=c)//如果满足约束条件,进入左子树(约束条件:不超过容量c) 
	        {
	            x[i]=1;//用来main函数构造最优解,将物品放入时x[i]=1 
	            cw+=w[i];
	            cv+=v[i];
	            Backtrack(i+1);
               cw-=w[i];
	            cv-=v[i];
	        }
	        if(Bound(i+1)>bestv)//满足限界函数进入右子树 
	        {
	            x[i]=0;//右子树意味着物品不装入,x[i]=0 
	            Backtrack(i+1); 
	        }
	    } 
	}
	
	int main()
	{
	    bestv=0;cv=0;cw=0;
	    cout<<"请输入物品数量:";
       cin>>n;//物品数量
	    cout<<"请输入背包容量:";
	    cin>>c;//背包容量
	    cout<<"请输入所有物品重量:"<<endl;
	    for(int i=1;i<=n;++i)
	        cin>>w[i];
	    cout<<"请输入所有物品价值:"<<endl;   
	    for(int i=1;i<=n;++i)
        cin>>v[i];
	        
	    for(int i=1;i<=n;++i)//物品编号:1~n 
	        order[i]=i;
	        
	    Order();//将物品按照单位重量价值排序(w,v,order,vw数组都要按照这个排序) 
	    
	    Backtrack(1);//从根节点开始回溯 
	    cout<<"可装入的最大价值:"<<bestv<<endl;
	    cout<<"放入背包的物品编号:" ; 
	    for(int i=1;i<=n;++i)
	    if(best_x[i]==1) cout<<order[i]<<" ";
	
      system("pause");
	    return 0;
	}

- 运行结果:

在这里插入图片描述

  • N皇后问题

    • 问题描述:
      在nxn格的棋盘上放置彼此不受攻击的n格皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在nxn格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
    • 代码:
	#include <iostream>
	#include <cmath>
	using namespace std;
	
	int sum=0;
	int n;
	
	int output(int  x[]){
	    int i;
	    for(i=1;i<=n;i++){
	      cout << "(" << i << "," << x[i] << ")" << " ";                                   
	    }
	    cout << endl;
	    return 0;
	}
	
	bool Place(int k,int  x[]){
	     int i;
	     for(i=1;i<k;i++){
	     if(abs(i-k)==abs(x[i]-x[k]) || x[i]==x[k])
	         return false;                     
	     } 
	     return true;
	}
	
	void Backtrack(int k,int  x[]){
	    x[k]=0;
	    while(k>0){
	        x[k]+=1;
	        while(x[k]<=n && !Place(k,x)) x[k]++;//找到第k行满足约束条件的那一列,以便对子结点继续深度搜索 
	        if(x[k]<=n){//找到了满足条件的子结点 
	             if(k==n){//是叶结点 
	                   output(x);
	                   sum++;  
	             }else{
                      k++;
	                  x[k]=0; 
	             }
	                    
	        }else{//对该行各列已经检查完 
	             k--; 
	        }              
	    }	} 
	
	int main()
	{
	    int *x,i;    
	    cout << "输入皇后个数:" << endl;
        cin >> n;
	    cout << endl;
	    x=new int[n+1];
	    for(i=0;i<=n;i++){
	      x[i]=0;                
	    }    
	    Backtrack(1,x);
	    cout << endl;
	    cout << "解的个数:" << sum << endl;
	    system("pause"); 
	    return 0;
}

- 运行结果:

在这里插入图片描述

  • 图的m着色问题

    • 问题描述:
      给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法使G中每条边的2个顶点着不同颜色。这个问题是图的m可着色判定问题。若一个图最少需要m种颜色才能使图中每条边连接的2个顶点着不同颜色,则称这个数m为该图的色数。求一个图的色数m的问题称为图的m可着色优化问题。
      输入:第1行有3个正整数n,k 和m,表示给定的图G有n个顶点和k条边,m种颜色。顶点编号为1,2,…,n。接下来的k行中,每行有2个正整数u,v,表示图G 的一条边(u,v)。
      输出:程序运行结束时,将计算出的不同的着色方案数输出。
    • 代码:
	#include <iostream>
	#include <math.h>
	#include <cstring>
  using namespace std;
	
	int n;//图一共有n个顶点
	int m;//m种颜色被选择
	int k;//图中有k条边
	int sum=0;//图的m着色种类
	int x[101];//存放当前的着色序列
	int a[101][101];//图的邻接矩阵
	
	//判断给点t着色为x[t]是否可行
	bool OK(int t)
	{
	    //可行的条件是当前点相邻的点不能与之同色
	    for(int i=1;i<t;i++)
	        if(a[i][t]==1&&x[i]==x[t])
	            return false;
	    //如果所有与之相邻的点都不与之同色
	    return true;
	}
	
	void getsum(int i)
	{
	    if(i>n){//i>n说明找到了一个可行的涂色方案
          sum++;//涂色方案数++
	        //输出解向量
	        for(int k=1;k<=n;k++)
	            cout<<x[k]<<" ";
	        cout<<endl;
	        return ;
	    }
	    //还没有到叶子节点,需要给当前节点可行的涂色
	    else{
	        for(int k=1;k<=m;k++){  //子树是一个m叉树
	            x[i]=k;//给第i个顶点着第k种颜色
              if(OK(i))
	                getsum(i+1);
	            x[i]=0;//如果给第i个顶点着第k种颜色不可行
	            //不涂色,便于后续涂色
	        }
	    }	    
	 return ;//如果当前节点所有颜色都不可行,结束对子树的遍历,返回
	}
	
	int main()
	{
	    cin>>n>>k>>m;//输入定点数,边数,着色种类数
	    int x,y;
	    memset(a,0,sizeof(a));//给邻接矩阵赋初值
	    for(int i=1;i<=k;i++){
	        cin>>x>>y;
	        a[x][y]=a[y][x]=1;//生成邻接矩阵
	    }
	    getsum(1);
	    cout<<sum;//输出最大可行的着色方案数,sum初始值为0
	    system("pause"); 
	    return 0;
	}

- 运行结果:

在这里插入图片描述
在这里插入图片描述

  • 旅行者售货员问题

    • 问题描述:
      某售货员要到若干城市去推销商品,已知各城市之间的路程,他要选定一条从驻地出发,经过每个城市一遍,最后回到住地的路线,使总的路程最短。
      输入:第一行输入两个正整数,分别代表城市数和城市间的道路总数。紧接着分别输入每条道路对应的具体城市和道路距离。
      输出:总路线最短的路线
    • 代码:
	#include <iostream>
	using namespace std;
	const int max_ = 0x3f3f3f;   //定义一个最大值
	const int NoEdge = -1;      //两个点之间没有边
	int citynum;                //城市数
	int edgenum;                //边数
	int currentcost;            //记录当前的路程
	int bestcost;               //记录最小的路程(最优)
	int Graph[100][100];        //图的边距记录
	int x[100];                 //记录行走顺序
	int bestx[100];             //记录最优行走顺序 
	
	//初始化
	void Initilize()
	{
	    currentcost = 0;
	    bestcost = max_;
	    for(int i = 1; i <= citynum; ++i)
	    {
	        x[i] = i;
	    }
	}

	void Swap(int &a, int &b)
	{
	    int temp;
	    temp = a;
	    a = b;
	    b = temp;
	}
	
	void BackTrack(int i) //这里的i代表第i步去的城市而不是代号为i的城市
	{	    if(i == citynum)
	    {
	        //进行一系列判断,注意的是进入此步骤的层数应是叶子节点的父节点,而不是叶子节点
	        if(Graph[x[i - 1]][x[i]] != NoEdge && Graph[x[i]][x[1]] != NoEdge && (currentcost + Graph[x[i - 1]][x[i]] + Graph[x[i]][x[1]] < bestcost || bestcost == max_))
	        {
	            //最小(优)距离=当前的距离+当前城市到叶子城市的距离+叶子城市到初始城市的距离
	            bestcost = currentcost + Graph[x[i - 1]][x[i]] + Graph[x[i]][x[1]];
	            for(int j = 1; j <= citynum; ++j)
	                bestx[j] = x[j];
	        }
	    }
	    else
	    {
	        for(int j =  i; j <= citynum; ++j)	        {
            if(Graph[x[i - 1]][x[j]] != NoEdge && (currentcost + Graph[x[i - 1]][x[j]] < bestcost || bestcost == max_))
	            {
	                Swap(x[i], x[j]);  //这里i 和 j的位置交换了, 所以下面的是currentcost += Graph[x[i - 1]][x[i]];
	                currentcost += Graph[x[i - 1]][x[i]];
	                BackTrack(i + 1);   //递归进入下一个城市
	                currentcost -= Graph[x[i - 1]][x[i]];
	                Swap(x[i], x[j]);
	            }
	        }
	    }
	}
	
	int main()
	{
	    int pos1, pos2, len;     //点1 点2 距离
	
        cout<<"请输入城市数和边数(c e):";	
        cin>>citynum>>edgenum;
	    memset(Graph, NoEdge, sizeof(Graph));
	
	    cout<<"请输入两座城市之间的距离(p1 p2 l):"<<endl;
	    for(int i = 1; i <= edgenum; ++i)
	    {
	        cin>>pos1>>pos2>>len;
           Graph[pos1][pos2] = Graph[pos2][pos1] = len;
	    }
	    Initilize();
	    BackTrack(2);
	    cout<<"最短路程为:"<<bestcost<<endl;
	    cout << "路线为:" << endl;
	    for(int i = 1; i <= citynum; ++i)
	        cout << bestx[i] << " ";
	    cout << "1" << endl;
	}
- 运行结果:

在这里插入图片描述

XMind - Trial Version

  • 2
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分治算法是一种把问题分成多个子问题,然后将子问题的解合并起来得到原问题解的算法。通常情况下,分治算法可以分为三个步骤:分解、解决和合并。在本次实验中,我们将使用分治算法来解决一个经典的问题:求解一个数组中的最大值。 1.问题描述 给定一个数组,我们需要找到其中的最大值。 2.算法设计 我们使用分治算法来解决这个问题。具体来说,我们将数组分成两个子数组,然后递归地对这两个子数组求解最大值,最后将两个子数组的最大值合并起来得到原数组的最大值。 具体的算法步骤如下: (1)将数组分成两个子数组,分别求解其中的最大值。 (2)将两个子数组的最大值进行比较,得到原数组的最大值。 3.实验过程 为了实现这个算法,我们需要编写一个递归函数。该函数将在不断地对子数组进行分解,直到子数组的长度为1时,返回子数组中的唯一元素。 在进行合并时,我们需要比较两个子数组的最大值,然后将较大的值作为结果返回。 下面是具体的代码实现: ``` // 分治算法求解最大值 int max(int arr[], int start, int end) { // 如果子数组长度为1,返回该元素 if (start == end) { return arr[start]; } // 将数组分成两个子数组 int mid = (start + end) / 2; int left = max(arr, start, mid); int right = max(arr, mid + 1, end); // 合并两个子数组的结果 return left > right ? left : right; } ``` 4.实验结果 我们使用一个长度为10的随机数组进行测试,如下所示: ``` int arr[] = {9, 4, 7, 8, 6, 2, 10, 3, 5, 1}; int result = max(arr, 0, 9); cout << "The max value in the array is " << result << endl; ``` 运行结果如下: ``` The max value in the array is 10 ``` 可以看到,我们成功地使用分治算法求解了给定数组的最大值。 5.实验总结 本次实验我们学习了分治算法的基本思想,并且使用分治算法成功地求解了一个经典问题。分治算法可以解决很多复杂的问题,例如排序、查找等,它的时间复杂度通常可以达到O(nlogn)。因此,在算法设计中,我们可以考虑使用分治算法来解决一些复杂的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值