贪心算法解决

贪心算法解决哈夫曼编码


`


贪心算法

要素

最优子结构、贪心选择

基本思想

贪心算法总是做出在当前看来是最好的选择。也就是说,贪心算法并不从整体最优上加以考虑,所做的选择只是在某种意义上的局部最优选择


0/1背包不能贪心,背包问题可以贪心
动态规划与贪心都是最优子结构,但是动态规划没有贪心
最优装载问题有贪心选择性质

一、哈夫曼树编码

1.

采用了最优前缀码,平均码长最小
前缀码:对每一个字符规定一个 0、1 串作为其代码,并要求任一字符的代码都不是其他字符代码的
前缀

2.

二、单源最短路径(Dijkstra算法)

带权有向图

算法思想

按各个顶点与源点之间的路径长度的递增次序,生成源点到各个顶点的最短路径。即先求出长度最短的一条路径,再参照它求出长度次短的一条,以此类推,直到从源点到其他各个顶点的最短路径全部求出为止,俗称迪杰斯特拉算法。

算法设计

设源点为u,顶点集合V被分为两部分,S和V-S,其中S中的顶点到源点的最短路径已经确定,V-S中未确定。该算法选用的贪心策略是:每次从V-S集合中选择一个当前离源点的路径最短的顶点,并将其加入集合S中。
①设置带权邻接矩阵C[][],一维数组dis[]用来存储最短路径长度,一维数组p[]用来存储最短路径中i顶点的前驱顶点,即p[i]的值为i顶点的前驱顶点。记录顶点是否在S集合中的bool类型的数组res[]。若顶点i在集合S中,res[i]=true,反之为false。

②初始化。res[u]=true; 对V-S中的所有顶点x,设置dist[x]= C[u][x]。如果顶点x与源点u相邻,初始化p[x]为u,如果不相邻,也就是C[u][x]= ∞,初始化p[x]为-1。

③在V-S中依照贪心策略来寻找使得dist[x]具有最小值的顶点tv,满足该条件的顶点就是V-S中距离源点u最近的顶点。

④将tv加入S中,即设置res[tv]=true。

⑤如果V-S集合为空,算法结束。否则转步骤⑥。

⑥对集合V-S中的所有与顶点tv相邻的顶点x,更新其到源点的最短路径长度,即如果dist[x] > dist[tv] + C[tv][x],设置dist[x] = dist[tv] + C[tv][x],并设置p[x]=tv。转步骤③。

由此可求得源点u到该有向带权图的其余各个顶点的最短路径及其长度。
在这里插入图片描述

代码如下:


利用邻接矩阵建立二维数组
#include <iostream>
 
using namespace std;
void DA (int n,int u,int *dist,int *p,int C[][5])
{
    bool res[n];
    for(int i=0;i<n;i++)
    {
        dist[i]=C[u][i]; //首先将每个顶点到源点的最短距离初始化为从源点出发,到每个顶点的直接距离(没有边的为无穷,可用INT_MAX代替)
        res[i]=false; //全部顶点放到集合V-S中
        if(dist[i]==INT_MAX) //若源点无边到该顶点
        p[i]=-1; //将路径设为-1
        else
        p[i]=u; //否则设为源点
    }
    res[u]=true;
    for(int i=0;i<n;i++)
    {
        int tv =u;
        int temp = INT_MAX;
        for(int j=0;j<n;j++) 
        {
            if((!res[j])&&(dist[j]<temp)) //每次从剩余顶点中找出一个当前距离源点最近的顶点
            {
                temp=dist[j];
                tv=j;
            }
        }
        if(tv==u)break;
        res[tv]=true;//将顶点tv放入S集合中
        for(int j=0;j<n;j++) //每放入S一个顶点,就要将与tv顶点相连的剩余顶点的到源点最短路径更新一下
        {
            if((!res[j])&&(C[tv][j]!=INT_MAX)) 
                if(dist[j]>dist[tv]+C[tv][j])
            {
                dist[j]=dist[tv]+C[tv][j];
                p[j]=tv; //将剩余顶点的路径设为tv
            }
        }
    }
}
int main()
{
 
    int A[5][5]={ 0,8,32,INT_MAX,INT_MAX,
                  12,0,16,15,INT_MAX,
                  INT_MAX ,29 ,0,INT_MAX,13,
                  INT_MAX,21,INT_MAX,0,7,
                  INT_MAX,INT_MAX,27,19,0 };
                  int dist[5];
                  int p[5];
                  DA(5,0,dist,p,A);
                  for(int i=0;i<5;i++)
                  cout << dist[i] << " "<<p[i]<<endl;
    return 0;
}

三、连通图画最小生成树

无向连通带权图

1.问题

一个连通图 的生成树是一个极小连通子图,它含有图中全部的顶点,但是只有足有构成一棵树的n-1条边。
它有如下性质:
1)一棵有n 个顶点的生成树有且只有n − 1条边;
2)如果一个图有n个顶点和小于n − 1条边,则是非连通图;如果它多于n − 1 条边,则一定有环;
3)但是有n − 1 条边的n个顶点的图不一定是生成树。(它只是必要条件)
一棵生成树的代价就是树上各边的代价之和。
最小生成树就是构造连通网 的最小代价生成树(Minimum Cost Spanning Tree)(简称为最小生成树)的问题。

2.普里姆算法

1、就是从任意点u0出发,然后不断在所有U中顶点的邻接边中找一个加入不会构成环的顶点并入U直到U=V
2、Prim算法的思想是贪心算法,其正确性可以通过贪心准则的最优性来证明,常用的有贪心交换和数学归纳法
3、我们的贪心选择是最优的。但是,最小生成树不是唯一的,因为在某个步骤会出现多个权值相同的最小边,此时,最小边选择不同,可能导致最小生成树不同。
4、假设网中有n个顶点,第一个进行初始化的频度为n,第二个循环语句的频度为n-1,其中有两个内循环:其一、在closedge.lowcost中查找最小元素,其频度为n-1;其二是更新最小代价的边,其频度为n。
因此,普里姆算法的时间复杂度为O ( n 2 ) ,与网中的边无关,适合求边稠密的网的最小生成树。
lowcost存储该最小边上的权值,即
closedge[i-1].lowcost = Min{cost(u,v_{i})|u∈U}
vex域存储该边依附的在U中的顶点

代码如下(示例):

#include <iostream>
#include <string>

using namespace std;

#define MAX_VERTEX_NUM 20   // 最大的顶点数
// 弧的基本结构的定义
typedef struct ArcCell {
	int adj;  // 表示该弧上的权值
}ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
// 带权图的基本结构
typedef struct {
	int vexnum, arcnum;         //当前的顶点数和边数
	AdjMatrix arcs;             // 邻接矩阵
	string vexs[MAX_VERTEX_NUM];  // 顶点矩阵
}Graph;
// 无向带权图的普里姆算法
void MST_Prim(Graph G, string v);
int LocateVex(Graph G, string v) { // 在图G中找顶点v的位置
	int i;
	for (i = 0; i < G.vexnum && G.vexs[i] != v; i++);
	if (i == G.vexnum) return -1;
	else return i;
}
void createGraph(Graph& G) {
	printf("输入图的顶点数和边数:\n");
	cin >> G.vexnum >> G.arcnum;
	printf("输入图的顶点信息:\n");
	for (int i = 0; i < G.vexnum; i++) cin >> G.vexs[i];
	for (int i = 0; i < G.vexnum; i++) { // 初始化邻接矩阵
		for (int j = 0; j < G.vexnum; j++) G.arcs[i][j].adj = INT_MAX;
	}
	printf("输入图的边信息vi-vj-weight:\n");
	string v1, v2;
	int cost;
	for (int i = 0; i < G.arcnum; i++) {
		cin >> v1 >> v2 >> cost;
		int l1 = LocateVex(G, v1);
		int l2 = LocateVex(G, v2);
		G.arcs[l1][l2].adj = G.arcs[l2][l1].adj = cost;
	}
}

输入
6 10
V1 V2 V3 V4 V5 V6
V1 V2 6
V1 V3 1
V1 V4 5
V2 V3 5
V2 V5 3
V3 V4 5
V3 V5 6
V3 V6 4
V4 V6 2
V5 V6 6


输出(V1,V3),(V3,V6),(V6,V4),(V3,V2),(V2,V5)


2.克鲁斯卡尔算法(kruskal)

1、克鲁斯卡尔算法从边出发,不断选择一条代价最小的边且加入不会构成环的,将其两端点并入生成树顶点集T。
2、当图的边数为 e 时,Kruskal 算法所需的时间是是 O(eloge)
3/注意两点:
顶点下标从0到n-1;
每次合并不同连通分量的时候,一定是合并根结点。

(V1,V3),(V3,V6),(V6,V4),(V3,V2),(V2,V5)

该处使用的url网络请求的数据。


总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值