算法设计与分析 实验三 贪心算法

一、 实验目的和要求

1、掌握贪心算法的基本思想。
2、学习利用贪心算法设计和实现算法的方法。
3、了解利用替换法证明贪心策略是否能获得全局最优解的过程。
4、熟练掌握贪心算法在两个典型图搜索中的应用,即单源最短路径和最小生成树算法中,利用合理的数据结构优化算法复杂度的技巧。

二、实验任务

1、问题描述:利用贪心法来设计并实现最优装载问题
2、问题描述:利用贪心法来设计并实现单源最短路径。
3、问题描述:字符a~h出现的频率恰好是前n个Fibonacci数,它们的哈夫曼编码是什么?扩展到前n个字符的频率恰好是前n个Fibonacci数的情形。写程序实现。

三、实验内容及步骤

1.贪心法来设计并实现最优装载问题

问题描述:有一批集装箱要装上一艘载重量为C的轮船。其中集装箱i的重量为Wi。最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。

#include<stdio.h>
int main(){
	int n = 0; 
	int max_weight;
	scanf("%d",&max_weight);	//输入轮船的载重量 
	scanf("%d",&n);				//输入集装箱的数量 
	int a[n] = {0};
	for(int i = 0;i < n; i++){	//将每个集装箱的重量存入数组a[n]中 
		scanf("%d",&a[i]);
	}
	
	for(int i= 0;i < n; i++){   //用冒泡排序将货物从小到大排列
        for(int j = 0;j < i;j++){
            if(a[j]>a[j+1]){
                int temp = 0;
                temp = a[j+1];
                a[j+1] = a[j];
                a[j] = temp;
            }
        }
    }

	int b[n] = {0};				//用数组b[n]记录能装入轮船的集装箱 
	for(int i = 0;i < n;i++){
		max_weight = max_weight - a[i];	
		if(max_weight > 0){	//还大于零就代表轮船还能继续装集装箱 
			b[i] = a[i];		//装进一个,数组b就记录一个 
		} 
	}

	for(int i = 0;i < n; i++){
        if(b[i] != 0){
            printf("%d ",b[i]);
        }
    }
	
}

2.贪心法来设计并实现单源最短路径

//【贪心算法】单源最短路径问题
#include <iostream>
using namespace std;

#define N 5		// 5个顶点,1、2、3、4、5
#define M 9999	// maxint,大整数

void Dijkstra(int n, int v, int dist[], int prev[], int c[][N + 1]);
void Traceback(int v, int i, int prev[]);

int main()
{
	int v = 1;			// 源点为1
	int dist[N + 1];	// 从源到顶点i的最短特殊路径长度
	int	prev[N + 1];	// 从源到顶点i的最短路径上前一个顶点
	// 带权有向图的邻接矩阵,行和列下标从1开始
	int c[N + 1][N + 1] = {
		{M,	M,	M,	M,	M,	M	},
		{M,	M,	10,	M,	30,	100	},
		{M,	M,	M,	50,	M,	M	},
		{M,	M,	M,	M,	M,	10	},
		{M,	M,	M,	20,	M,	60	},
		{M,	M,	M,	M,	M,	M	},
	};

	// “输入”:带权有向图
	cout << "带权有向图的邻接矩阵为:\n";
	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= N; j++)
			cout << c[i][j] << "\t";
		cout << endl;
	}

	// Dijkstra算法
	Dijkstra(N, v, dist, prev, c);

	// 输出
	cout << "源点1到顶点5的最短路径长度为:" << dist[5];
	cout << ",路径为:";
	Traceback(1, 5, prev);
	cout << endl;

	return 0;
}


void Dijkstra(int n, int v, int dist[], int prev[], int c[][N + 1])
{
	bool s[N + 1];	// 顶点集合s
	for (int i = 1; i <= n; i++)
	{
		dist[i] = c[v][i];	// 从源到顶点i的最短特殊路径长度
		s[i] = false;

		if (dist[i] == M)
			prev[i] = 0;	// 从源到顶点i的最短路径上前一个顶点
		else
			prev[i] = v;
	}

	dist[v] = 0;
	s[v] = true;

	for (int i = 1; i < n; i++)
	{
		int temp = M;		// 
		int u = v;			// 上一顶点

		// 找到具有最短特殊路长度的顶点u
		for (int j = 1; j <= n; j++)
		{
			if ((!s[j]) && (dist[j] < temp))
			{
				u = j;
				temp = dist[j];
			}
		}
		s[u] = true;

		// 更新dist值
		for (int j = 1; j <= n; j++)
		{
			if ((!s[j]) && (c[u][j] < M))
			{
				int newdist = dist[u] + c[u][j];
				if (newdist < dist[j])
				{
					dist[j] = newdist;
					prev[j] = u;
				}
			}
		}
	}
}

//输出最短路径 v源点,i终点,
void Traceback(int v, int i, int prev[])
{
	// 源点等于终点时,即找出全部路径
	if (v == i)
	{
		cout << i;
		return;
	}
	Traceback(v, prev[i], prev);
	cout << "->" << i;
}

3.哈夫曼编码与Fibonacci结合

问题描述:字符a~h出现的频率恰好是前n个Fibonacci数,它们的哈夫曼编码是什么?扩展到前n个字符的频率恰好是前n个Fibonacci数的情形。写程序实现。

/*字符a~h出现的频率恰好是前n个Fibonacci数,它们的哈夫曼编码是什么?
扩展到前n个字符的频率恰好是前n个Fibonacci数的情形。写程序实现。*/ 
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
int fib(int i){		/*斐波那契函数改进,此函数能得到第n个数的斐波那契函数的值*/
	int j,a,b,c;
	int shuzu[i]={0};
	a=1;
    b=1;  /* 前两项已知为1,直接输出  */
	if(i<3){
		return 1;
	}
	else{
		for(j=2;j<i;j++)      
    	{
    	c=a+b;   /* 前两项相加   */
    	shuzu[j]=c;
    	a=b;
    	b=c;	
    	}
    	return shuzu[i-1];
	}
}
struct HTreeNode{
    int parent;
    int lchild;
    int rchild;
    int weight;
};
struct HTree{
    struct HTreeNode *body;		/*定义一个指向结构体 HTreeNode 的指针 body */
    int length;
};
struct HTree *iniHTree(int num){/*对哈夫曼树初始化*/
    struct HTree *ht;			/*定义一个指向结构体 HTree 的指针 ht */ 
    ht=(struct HTree *)malloc(sizeof(struct HTree));  /*创建一个 struct HTree 结构体大小的空间,然后强制转换为struct HTree * 类型的指针,
	最后赋值给ht,此时ht指向一个 struct HTree 结构体大小的新空间 */ 
    int size=2*num-1;
    ht->body=(struct HTreeNode *)malloc(sizeof(struct HTreeNode)*size);
    ht->length=0;
    int i;
    for(i=0;i<num;i++){
        ht->body[i].parent=-1;
        ht->body[i].lchild=-1;
        ht->body[i].rchild=-1;
        int temp;
        temp=fib(i+1);
        printf("The %dth weight= %d \n",i,fib(i+1));
        ht->body[i].weight=temp;
        ht->length++;
    }
    for(;i<size;i++){
        ht->body[i].parent=-1;
        ht->body[i].lchild=-1;
        ht->body[i].rchild=-1;
        ht->body[i].weight=pow(2,31)-1;
    }
    return ht;
}
void show(struct HTree *ht){/*打印哈夫曼树*/
    if(ht->body!=NULL){
        for(int i=0;i<ht->length;i++){
            printf("%4d",ht->body[i].lchild);
            printf("%4d",ht->body[i].weight);
            printf("%4d",ht->body[i].rchild);
            printf("%4d",ht->body[i].parent);
            printf("\n");
        }
    }
}
void findTwoMinHTNode(struct HTree *ht,int *min1,int *min2){
    int m1,m2;
    int minWeight;
    int j=0;
    while(ht->body[j].parent!=-1){
        j++;
    }
    m1=j;
    minWeight=ht->body[m1].weight;
    for(int i=m1+1;i<ht->length;i++){
        if(ht->body[i].parent==-1&&ht->body[i].weight<minWeight){
            m1=i;
            minWeight=ht->body[i].weight;
        }
    }
    ht->body[m1].parent=1;
    *min1=m1;
    j=0;
    while(ht->body[j].parent!=-1){
        j++;
    }
    m2=j;
    minWeight=ht->body[m2].weight;
    for(int i=m2+1;i<ht->length;i++){
        if(ht->body[i].parent==-1&&ht->body[i].weight<minWeight){
            m2=i;
            minWeight=ht->body[m2].weight;
        }
    }
    *min2=m2;
    ht->body[m2].parent=1;
}
void consummateHT(struct HTree *ht,int num){/*完善哈夫曼树*/
    if(ht==NULL){
        printf("HT is not exit\n");
        return;
    }
    int min1,min2;
    for(int i=num;i<2*num-1;i++){
        findTwoMinHTNode(ht,&min1,&min2);
        ht->body[min1].parent=i;
        ht->body[min2].parent=i;
        ht->body[i].lchild=min1;
        ht->body[i].rchild=min2;
        ht->body[i].weight=ht->body[min1].weight+ht->body[min2].weight;
        ht->length++;
    }
}
void codeHT(struct HTree *ht){/*哈夫曼编码核心代码*/
    int count=(ht->length+1)/2;
    int weightdata[count];
    for(int i=0;i<count;i++){
        weightdata[i]=ht->body[i].weight;
    }
    char *ch[count];
    char a[count];
    int index=0;
    for(int i=0;i<ht->length;i++){
        ht->body[i].weight=0;
    }
    int cur=ht->length-1;
    while(cur!=-1){
        if(ht->body[cur].weight==0){/*weight为0表示没有被访问过*/
            ht->body[cur].weight=1;/*weight为1表示左子树被访问过*/
            if(ht->body[cur].lchild!=-1){/*左孩子不等于-1说明还在中间节点,cur继续往左走,编码左0右1,所以编为0*/
                a[index++]='0';
                cur=ht->body[cur].lchild;
            }else{/*当前节点的左孩子为-1说明遍历到头了,当前节点的完整编码就存在数组a中*/
                a[index]='\0';
                ch[cur]=(char *)malloc(sizeof(char)*count);
                strcpy(ch[cur],a);/*对编码拷贝*/
            }
        }else if(ht->body[cur].weight==1){/*weight为1表示右子树没被访问过*/
            ht->body[cur].weight=2;/*weight为2表示左右子树都被访问过*/
            if(ht->body[cur].rchild!=-1){/*右孩子孩子不等于-1说明还在中间节点,cur继续往右走,编码左0右1,所以编为1*/
                a[index++]='1';
                cur=ht->body[cur].rchild;
            }
            /*右孩子为-1时说明遍历到头了,由于之前编码已经拷贝过了,所以直接跳过,不做处理*/
        }else{/*weight为2说明左右都访问过,回退到父节点,并将索引下标减一,相当于删掉数组中最后一个字符*/
            ht->body[cur].weight=0;
            cur=ht->body[cur].parent;
            index--;
        }
    }
    printf("最优前缀码:\n");
    for(int i=0;i<count;i++){
        printf("权重为%d的编码:%s\n",weightdata[i],ch[i]);
    }
}

int main(){
    struct HTree *ht;
    int num;
    printf("leaf=");
    scanf("%d",&num);
    
    ht=iniHTree(num);
    consummateHT(ht,num);
    
    printf("哈夫曼树存储表如下:\n");
    
    show(ht);
    
    printf("\n");
    
    codeHT(ht);
    
    return 0;
}

注:其中的关于哈夫曼编码的代码参考了此博客 哈夫曼编码代码实现(C语言)

四.实验心得

自己写!!!

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值