贪心算法的几个应用

具体代码: 

dijktra:

设邻接矩阵:a[][],有n个节点。

1.初始化:dist[i] = a[v][i],原始集合中只有v

2.dist的最小值,加入原始集合中,更新其他dist,更新n-1

具体代码如下:

/*

5

0 10 10000 30 100

10000 0 50 10000 10000

10000 10000 0 10000 10

10000 10000 20 0 60

10000 10000 10000 10000 0

 

1

*/

 

#include<stdio.h>

#include<stdlib.h>

 

#define NUM 10

#define MAX_VALUE 10000

 

int dist[NUM];

int s[NUM];

int pre[NUM];

 

void  dijkstra(int v,int a[NUM][NUM], int n){

      //初始化

      int i=1,j=1;

      for(i=1; i<=n; i++){

           dist[i] = a[v][i];

           s[i] = false;

           if(a[v][i] == MAX_VALUE){

                 pre[i] = 0;

           }else{

                 pre[i] = v;

           }

      }    

     

      s[v] = true;

      dist[v] = 0;

     

      //更新n-1次

      int min = 0,tmp=v;

      for(i=1; i<n; i++){

            //找最小的dist

            min = MAX_VALUE;

            for(j=1; j<=n; j++){

                     if(!s[j] && min>dist[j]){

                              min = dist[j];

                              tmp = j;

                     }

            }

            s[tmp] = true;   //tmp的初始值是v,不能为0或者随意

     

            //用最小的dist更新已有的dist

            for(j=1; j<=n; j++){

                     if(!s[j] && a[tmp][j] < MAX_VALUE && (dist[tmp]+a[tmp][j] < dist[j])){

                              dist[j] = dist[tmp]+a[tmp][j];

                              pre[j] = tmp;

                     }

            }

      }

}

 

int main(){

    int i=1,j=1,v,n,a[NUM][NUM];

   

    //输入邻接矩阵

    scanf("%d",&n);

    for(i=1; i<=n; i++){

       for(j=1; j<=n; j++){

           scanf("%d",&a[i][j]);

       }

    }

   

    //输入起始节点

    scanf("%d",&v);

   

    //计算dijkstra

    dijkstra(v,a,n);

   

    //打印源点到各点的最短距离

    int tmp = 0;

    for(i=1; i<=n; i++){

           printf("从%d到%d的距离是%d\n",v,i,dist[i]);

           tmp = i;

           printf("从%d到%d的最短路经过:");

           while(pre[tmp]>0 && pre[tmp] != v){

               printf("%d ",pre[tmp]);

               tmp = pre[tmp];

           }

           printf("%d\n",v);

    }

 

    system("pause");

    return 0;

}

 

时间复杂度分析:使用邻接矩阵,o(n*n)

 

最小生成树:prim

/*
6
0 6 1 5 10000 10000
6 0 5 10000 3 10000
1 5 0 5 6 1
5 10000 5 0 10000 2
10000 3 6 10000 0 6
10000 10000 1 2 6 0

*/

#include<stdio.h>
#include<stdlib.h>

#define NUM 10
#define MAX_VALUE 10000
 
int closest[NUM];  //closest[j]记录j和S中的邻接节点中距离最近的节点
int lowcost[NUM];  //lowcost[j]记录j和S中最近邻接点的距离
int s[NUM];        //s[j] = true,标记j在S中

int prim(int a[NUM][NUM],int n){
    //初始化
    int i,j,sum=0;
    for(i=1; i<=n; i++){
             closest[i] = 1;
             lowcost[i] = a[i][1];
             s[i] = false;
    }
    s[1] = true;

    for(j=1; j<n; j++){
        //寻找离S中节点最近的节点及距离
        int min = MAX_VALUE,v=1;
        for(i=1; i<=n; i++){
             if(!s[i] && min > lowcost[i]){
                      min = lowcost[i];
                      v = i;
             }
        }
        s[v] = true;
        sum += lowcost[v];
   
        //每添加一个新的节点到S,比较节点i和S中以前节点的最短距离 和 i和新增节点的最短距离,更新closest和c
        for(i=1; i<=n; i++){
             if(!s[i] && a[i][v] < lowcost[i]){
                      lowcost[i] = a[i][v];
             }
        }           
    }    
   
    return sum;                  
   
}

int main(){
    int i=1,j=1,n,a[NUM][NUM];
   
    //输入邻接矩阵
    scanf("%d",&n);
    for(i=1; i<=n; i++){
       for(j=1; j<=n; j++){
           scanf("%d",&a[i][j]);
       }
    }
   
    printf("%d",prim(a,n));
   
    system("pause");
    return 0;
}

时间复杂度是:o(n*n)

说明:最小生成树primdijkstra相似,dijkstra保存了各点和源点的最近距离(dist)prim保存了各点和S中节点的最近邻接点及和最近邻接点的距离(closest, lowcost)。程序的书写过程相似,先初始化,再找最小的距离,再更新数组。

 

 

 

 

代码的注释我写得很详细,方便理解,有几点需要说明一下。

1、2个for循环都是从2开始的,因为一般我们默认开始就把第一个节点加入生成树,因此之后不需要再次寻找它。

2、lowcost[i]记录的是以节点i为终点的最小边权值。初始化时因为默认把第一个节点加入生成树,因此lowcost[i] = graph[1][i],即最小边权值就是各节点到1号节点的边权值。

3、mst[i]记录的是lowcost[i]对应的起点,这样有起点,有终点,即可唯一确定一条边了。初始化时mst[i] = 1,即每条边都是从1号节点出发。

编写程序:对于如下一个带权无向图,给出节点个数以及所有边权值,用Prim算法求最小生成树。

输入数据:

7 11
A B 7
A D 5
B C 8
B D 9
B E 7
C E 5
D E 15
D F 6
E F 8
E G 9
F G 11

输出:

A - D : 5
D - F : 6
A - B : 7
B - E : 7
E - C : 5
E - G : 9
Total:39

 

#include <stdio.h>
#include <stdlib.h>
 
#define MAX 100
#define MAXCOST 0x7fffffff
 
int graph[MAX][MAX];
 
int Prim(int graph[][MAX], int n)
{
	/* lowcost[i]记录以i为终点的边的最小权值,当lowcost[i]=0时表示终点i加入生成树 */
	int lowcost[MAX];
 
	/* mst[i]记录对应lowcost[i]的起点,当mst[i]=0时表示起点i加入生成树 */
	int mst[MAX];
 
	int i, j, min, minid, sum = 0;
 
	/* 默认选择1号节点加入生成树,从2号节点开始初始化 */
	for (i = 2; i <= n; i++)
	{
		/* 最短距离初始化为其他节点到1号节点的距离 */
		lowcost[i] = graph[1][i];
 
		/* 标记所有节点的起点皆为默认的1号节点 */
		mst[i] = 1;
	}
 
	/* 标记1号节点加入生成树 */
	mst[1] = 0;
 
	/* n个节点至少需要n-1条边构成最小生成树 */
	for (i = 2; i <= n; i++)
	{
		min = MAXCOST;
		minid = 0;
 
		/* 找满足条件的最小权值边的节点minid */
		for (j = 2; j <= n; j++)
		{
			/* 边权值较小且不在生成树中 */
			if (lowcost[j] < min && lowcost[j] != 0)
			{
				min = lowcost[j];
				minid = j;
			}
		}
		/* 输出生成树边的信息:起点,终点,权值 */
		printf("%c - %c : %d\n", mst[minid] + 'A' - 1, minid + 'A' - 1, min);
 
		/* 累加权值 */
		sum += min;
 
		/* 标记节点minid加入生成树 */
		lowcost[minid] = 0;
 
		/* 更新当前节点minid到其他节点的权值 */
		for (j = 2; j <= n; j++)
		{
			/* 发现更小的权值 */
			if (graph[minid][j] < lowcost[j])
			{
				/* 更新权值信息 */
				lowcost[j] = graph[minid][j];
 
				/* 更新最小权值边的起点 */
				mst[j] = minid;
			}
		}
	}
	/* 返回最小权值和 */
	return sum;
}
 
int main()
{
	int i, j, k, m, n;
	int x, y, cost;
	char chx, chy;
 
	/* 读取节点和边的数目 */
	scanf("%d%d", &m, &n);
	getchar();
 
	/* 初始化图,所有节点间距离为无穷大 */
	for (i = 1; i <= m; i++)
	{
		for (j = 1; j <= m; j++)
		{
			graph[i][j] = MAXCOST;
		}
	}
 
	/* 读取边信息 */
	for (k = 0; k < n; k++)
	{
		scanf("%c %c %d", &chx, &chy, &cost);
		getchar();
		i = chx - 'A' + 1;
		j = chy - 'A' + 1;
		graph[i][j] = cost;
		graph[j][i] = cost;
	}
 
	/* 求解最小生成树 */
	cost = Prim(graph, m);
 
	/* 输出最小权值和 */
	printf("Total:%d\n", cost);
 
	//system("pause");
	return 0;	
}


 

 

 

 

 

今天从志权师兄那里学会了最小生成树。所谓生成树,就是n个点之间连成n-1条边的图形。而最小生成树,就是权值(两点间直线的值)之和的最小值。

  

         首先,要用二维数组记录点和权值。如上图所示无向图:

int map[7][7];
       map[1][2]=map[2][1]=4;
       map[1][3]=map[3][1]=2;
       ......

      然后再求最小生成树。具体方法是:

1.先选取一个点作起始点,然后选择它邻近的权值最小的点(如果有多个与其相连的相同最小权值的点,随便选取一个)。如1作为起点。

visited[1]=1;

pos=1;

//用low[]数组不断刷新最小权值,low[i](0<i<=点数)的值为:i点到邻近点(未被标记)的最小距离。

low[1]=0;  //起始点i到邻近点的最小距离为0

low[2]=map[pos][2]=4;

low[3]=map[pos][3]=2;

low[4]==map[pos][4]=3;

low[5]=map[pos][5]=MaxInt;  //无法直达

low[6]=map[pos][6]=MaxInt;

 

  2.再在伸延的点找与它邻近的两者权值最小的点。

//low[]以3作当前位置进行更新

visited[3]=1;

pos=3;

low[1]=0;   //已标记,不更新

low[2]=map[1][2]=4;  //比5小,不更新

low[3]=2;  //已标记,不更新

low[4]=map[1][4]=3;   //比1大,更新后为:low[4]=map[3][4]=1;

low[5]=map[1][5]=MaxInt;//无法直达,不更新

low[6]=map[1][6]=MaxInt;//比2大,更新后为:low[6]=map[3][6]=2;

 

    3.如此类推...

  当所有点都连同后,结果最生成树如上图所示。

     所有权值相加就是最小生成树,其值为2+1+2+4+3=12。

     至于具体代码如何实现,现在结合POJ1258例题解释。代码如下:

 
  
#include <stdio.h>

#include <string.h>

#define MaxInt 0x3f3f3f3f

#define N 110

//创建map二维数组储存图表,low数组记录每2个点间最小权值,visited数组标记某点是否已访问

int map[N][N],low[N],visited[N];

int n;

 

int prim()

{

    int i,j,pos,min,result=0;

    memset(visited,0,sizeof(visited));

//从某点开始,分别标记和记录该点

    visited[1]=1;pos=1;

//第一次给low数组赋值

    for(i=1;i<=n;i++)

        if(i!=pos) low[i]=map[pos][i];

//再运行n-1次

    for(i=1;i<n;i++)

    {

//找出最小权值并记录位置

     min=MaxInt;

     for(j=1;j<=n;j++)

         if(visited[j]==0&&min>low[j])

         {

             min=low[j];pos=j;

         }

//最小权值累加

    result+=min;

//标记该点

    visited[pos]=1;

//更新权值

    for(j=1;j<=n;j++)

        if(visited[j]==0&&low[j]>map[pos][j])

            low[j]=map[pos][j];

    }

    return result;

}

 

int main()

{

    int i,v,j,ans;

    while(scanf("%d",&n)!=EOF)

    {

//所有权值初始化为最大

        memset(map,MaxInt,sizeof(map));

        for(i=1;i<=n;i++)

            for(j=1;j<=n;j++)

            {

                scanf("%d",&v);

                map[i][j]=map[i][j]=v;

            }

            ans=prim();

            printf("%d\n",ans);

    }

    return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值