• 【算法】算法的应用(一)


    储油问题

       一辆重型卡车的油耗是1L/km,载油能力为500L,今欲穿过1000km的沙漠。由于卡车一次过不了沙漠,因此司机必须在沿途设几个储油点。问:如何建立这些储油点,每一个储油点储存多少油才能使卡车以最小油耗通过沙漠?
       实例解析:
       本例采用倒推法来解题。所谓倒推法,就是在不知初始值的情况下,通过某种递推关系,由最终值推算出初始值的方法。储油问题和猴子吃桃子问题等都是典型的倒退问题。
       显然,卡车要通过沙漠,必须在离起点500km处存储500L油,如图17-1所示。
     
     
     
     
                           图17-1 储油点及储油量示意图
       而要在500km处储油500L,需要卡车从某处(设离起点x1公里处)向500km处运送n趟(最后一趟不需要返回),卡车往返总耗油是:(2n-1)*(500-x1)*1,因此,x1处储油量y1应是:y1 = (2n -1)*(500 -x1) +500,而这个储油量也是卡车n趟的总载油量,即:y1=500n。
       可以证明,卡车往返的总耗油 (2n-1)*(500-x1) =500时,最为省油。故:y1=500+500 =1000,此时n = 2,即x1处要储油1000L,其中的500L送往中点500km处,另外500公里用来跑路(3个单程)。可以算出,x1的值为333km
       同样的道理,要在333km处储油1000L,需要在前面某点x2处储油500L*3=1500L,其中500L用来跑路(耗油500L最省油),另外1000L运送到333km处,即:y2 = (2*3 -1)*(x1-x2)+500*2 = 1500。
       继续前推,对下一个储油点有:y3 = (2*4 -1)*(x2-x3) +500*3 =2000
       再下一个储油点:y4 = (2*5 -1)*(x3-x4) +500*4 = 2500
       ......
       可以得出一个通用公式:
       某储油点的储油量应为:
       yk = (2*(k+1) -1)*(xk-1-xk) + 500*k = 500*(k+1)
       初始值:x0 = 500, k = 1。
       我们可以定义一个数组distance[N]用来存储各储油点离起点的距离,其中distance[0] = 500,定义一个数组oil[N]存储储油量,oil[0] = 500。其他储油点的坐标和储油量数据由上面的通用公式求得,即:
       xk = x k-1-500/(2k+1)
       yk = 500*(k+1)
       注意一点:推导的过程中,xk的值越来越小,当某次计算出的坐标xk<=0时,意味着已经计算到起点了。若xk = 0,意味着计算出的最经济的储油点正好位于起点,储油量按照上面的通用公式计算即可,即yk=500*(k+1)。但若xk<0,意味着计算出的储油点是不实际的,因此实际储油量不需要公式计算的那么多,所以要根据实际距离重新计算。
       下面是程序代码:
    #include<stdio.h>
       #define N 10
       int main()
       {int distance[N] = {500}, oil[N] = {500};
        int k;
       for(k = 1; distance[k-1] > 0; k++){//坐标大于0则继续循环
           distance[k] = distance[k-1] - 500/(2*k + 1);
         oil[k] = 500*(k+1);
        }
       k--;
       if(distance[k] < 0){
           distance[k] = 0;
           oil[k] = 500*k + (2*k + 1)*distance[k-1];
       }
       printf("
    distance   oil
    
    ");
       for(  ; k >= 0; k--)
          printf("%5d,%8d
    ", distance[k], oil[k]);
       getch();
        return 0;
       }
     
     

    背包问题
       给定n种物品和一个背包,其中物品i的重量为wi,对应的价值为vi,背包可装物品的最大重量为c,问题:怎样选择物品,才能使装入背包的物品的价值最大?
       实例解析:
       在选择物品时,每个物品的选择都只有两种:选或者不选,因此这个问题叫做0-1背包问题。
       我们采用动态规划的方法来解决这个问题。其基本思想是:
       将待解决的问题分成若干个子问题,先求子问题的解,然后从子问题的解中得出整个问题的解。
       适用于动态规划的问题经分解后形成的子问题往往不是相互独立的,在求解过程中如果能保存已解决的子问题的答案,以便在需要时加以利用,就可以避免大量重复计算。为了达到这个目的,可以用一个表来记录所有已解决的子问题的答案(不管有用无用,都保存),这就是动态规划的主要思想。
       本例中,我们定义三个数组来描述背包问题:
    int value[N];          //用来存储各物品的价值
    int weight[N];         //用来存储各物品的重量
    int maxvalue[N][CONTENT];   //存储最优解
     
     
       数组maxvalue用来存储动态规划过程中的最优解。例如:maxvalue[i][j]表示背包剩余容量为j,还有第i,i+1,i+2,......N-1物品可选择时的最优解。
       程序代码如下:
    #define  CONTENT 5
       #define  N  10
       #include <stdio.h>
       void knapsack(int v[N],int w[N], int c, int m[N][CONTENT+1])
       {int n = N-1;
        int i, j;
        int jMax;
        /*初始化数组,只有最后一件物品n可选时*/
        /**************************************************************/
    /*对下面4行代码的说明:当剩余容量c<w[n]时,m数组对应的元素应为0,意即不能选该物品,c>=w[n]时,则为v[n],意即应该选择。比如,设w[n]=4,v[n]为9,背包最大容量为6,则m数组第n行的值应为:
        剩余容量:      0  1  2  3  4  5  6
        m[n][j]的值:    0  0  0  0  9  9  9     */
      /**************************************************************/
        for(j = 0; j <= c; j++)       //先都清0
          m[n][j] = 0;
        for(j = w[n]; j <= c; j++)   //对于c >= w[n]的情况,改为价值
          m[n][j] = v[n];
        /*以下求解m[i][j]*/
        for(i = n-1; i >= 0; i--) {
           if(w[i]–1 < c)   //相当于w[i] <=c,物体重量小于等于背包容量
             jMax = w[i]-1;
           else
             jMax = c;
           for(j = 0; j <= jMax; j++) //对于剩余容量<物体重量的情况
             m[i][j] = m[i+1][j];
           for(j = jMax+1; j <= c; j++){ //剩余容量>=物体重量的情况
              if(m[i+1][j] >= m[i+1][j-w[i]] + v[i])
             m[i][j] = m[i+1][j];
              else
             m[i][j] = m[i+1][j-w[i]] + v[i];
           }
        }
       }
       void traceback(int flag[N], int w[N], int m[N][CONTENT+1])
       {int n = N-1;
        int i;
        int c = CONTENT;
        int j;
        for(i = 0; i <= n-1; i++)
          if(m[i][c] != m[i+1][c]){
              flag[i] = 1;    //如果两种最优解不同,说明选取了第i件物品
              c -= w[i];      //剩余容量减小
          } 
        if(m[n][c]>0)        //判断最后一种物品选择与否
          flag[n] = 1;
       }
      void printResult(int flag[], int w[], int v[], int m[][CONTENT+1])
       {int i;
        printf(" num  weight value
    ");
        for(i = 0; i <= N-1; i++)
          if(flag[i] == 1)
            printf("%3d%7d%5d
    ", i, w[i], v[i]);
        printf("content is %d,", CONTENT);
        printf("the max value is: %d
    ", m[0][CONTENT]);
       }
       int main()
       {int value[N] = { 5,2,3,9,3,6,5,7,8,2};
        int weight[N] = {2,1,3,5,7,3,6,2,6,3};
        int c = CONTENT;
        int maxvalue[N][CONTENT+1];
        int flag[N] = {0};
        clrscr();
        knapsack(value, weight, c, maxvalue);
        traceback(flag, weight, maxvalue);
        printResult(flag, weight, value, maxvalue);
        getch();
        return 0;
       }
     
     
     

    链表的逆置
       编程实现链表(无头结点)的逆置。
      实例解析:
       所谓链表逆置,就是将所有结点逆序排列。本例可用两种方法来做。
      方法一:从第一个结点开始直到链尾,依次处理每一个结点。
      (1)对于第一个结点,其指针域next赋值为NULL,使之成为链尾。
      (2)对于之后的结点,使之指向前一结点。
      (3)最后一个结点的指针,赋给head,使之成为第一个结点。
      下面是逆置部分的程序代码:
    struct student* reverse(struct Student *head)
       {struct student *p1,*p2,*t;
       p1 = p2 = head;
       p1 = p1->next;        //指向第二个结点(若有)
       while(p1 != NULL) {
         t = p1->next;     
        p1->next = p2;      //成为上一个结点的前驱
        if(p2 == head)      //上一个结点若是第一个结点,使之成为链尾
         p2->next = NULL;
        p2 = p1;             //p2跟上p1
        p1 = t;              //p1指向下一个结点
        }
       head = p2;            //最后一个结点成为第一个结点
       return head;
       }
     
     
     
     
      方法二:
      从链表第二个结点开始,依次将每个结点插入到链表的最前面。
      相应的函数代码是:
    struct student* reverse(struct student *head)
       {struct student  *p, *h, *t;
       if(head != NULL){
        h = head;            //记录原第一个结点的地址
        p = head->next;     //指向第二个结点
        while(p != NULL){  
          t = p->next;      // t记录下一个结点的地址
         p->next = head;  //*p成为第一个结点的前驱
         head = p;         //*p成为第一个结点
         p = t;             // p指向原来的下一个结点
           }
           h->next = NULL;    //最早的第一个结点成为链尾
         }
       return head;
      }
     
     
     

    约瑟夫环
       n个小孩围成一圈,从第一个人开始报数,报到k的人退出圈子,下面的人继续从1开始报数.....若最后圈子里只剩下m个人,他们分别是多少号?  n、k、m都从键盘输入。      
      实例解析:
      这是一个典型的单循环链表问题。先建立链表,然后从第一个结点开始计数,将第k个结点删除,然后再从下一结点开始计数,第k个结点删除......,直到链表中只剩m个结点。
      下面是程序代码:
    #include "stdio.h"
       #include "stdlib.h"
       struct Boy {
       int n;
        struct Boy *next;
       };
      void create(struct Boy**, int);
      struct Boy* proceed(struct Boy*, int, int, int);
      void print(struct Boy*);
       void del(struct Boy*);
       int main()
       {struct Boy *head;
       int n,k,m;
       clrscr();
       scanf("%d,%d,%d", &n, &k, &m);
       create(&head, n);              //建立链表
       if(head == NULL)
         exit(0);
       head = proceed(head, n, k, m);  //报数,处理
       print(head);                   //输出链表
       del(head);                     //删除整个链表,释放内存
       getch();
       return  0;
       }
       void create(struct Boy **p,int n)
       {struct Boy *p1,*p2;
       int i;
       for(i = 1; i <= n; i++) {
        p1 = (struct Boy*)malloc(sizeof(struct Boy));
        if(p1 == NULL){
           printf("no space.
    ");
           *p = NULL;
           return;
        }
        p1->n = i;
        if(i == 1)
         *p = p1;
        else
             p2->next = p1;
        p2 = p1;
        p1->next = *p;
       }
       }
       void print(struct Boy *head)
       {struct Boy *p = head;
       while(1) {
        printf("%5d", p->n);
        p = p->next;
        if(p == head)
         break;
       }
       }
       struct Boy* proceed(struct Boy *head,int n,int k,int m)
       {struct Boy *p1,*p2;
        int i,count,min;  //min表示圈中最小序号,count统计被删除的结点数
       p1 = p2 = head;
       for(count = 1; count <= n-m; count++){
        for(i = 1; i < k; i++){
         p2 = p1;
          p1 = p1->next;
         }
        p2->next = p1->next;
        p2 = p1;            //p2指向要删结点
        p1 = p1->next;
        free(p2);
       }
       head = p1;          
       //以下代码使head指向序号最小的结点,以便按从小到大顺序输出结果
       min = p1->n;
       for(i = 1; i <= m; i++) {
         p1 = p1->next;
         if(min > p1->n){
            head = p1;
          min = p1->n;
        }
        }
       return  head;
       }
       //删除整个链表
       void del(struct Boy *head)
       {struct Boy *p1,*p2;
       p1 = head;
       if(p1 != NULL)
        while(1) {
         p2 = p1;
         p1 = p1->next;
         free(p2);
         if(p1 == head)
           break;
        }
       }
     
     
     

    本文出自 “成鹏致远” 博客,请务必保留此出处http://infohacker.blog.51cto.com/6751239/1171353

  • 相关阅读:
    Unity WebGL打包发布报错
    Makefile:4: *** missing separator. Stop.
    Unity使用VSCode没有代码提示/代码无法折叠
    Unreal Engine is exiting due to D3D device being lost
    使用Doxygen生成UE4的chm格式API文档
    'UTextRenderComponent::SetText': Passing text as FString is deprecated, please use FText instead (likely via a LOCTEXT)
    f4v格式视频播放失败
    Unity自定义Button组件Transition
    mysql安装步骤
    ansible 安装
  • 原文地址:https://www.cnblogs.com/lcw/p/3159447.html
Copyright © 2020-2023  润新知