贪心算法

贪心算法

  • 当具有最优子结构性质的时候,可以使用动态规划算法也可以使用贪心算法
  • 最优子结构性质、贪心选择性质
  • 虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况之下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。

活动安排问题

选出最大的相容活动子集Q

1. 动态规划方法

步骤一 : 分析最优子解的结构性质

  • 最有最有子结构性质:某活动X包含在Q中,则X之前、之后的活动都是最优的
  • 子问题的最优解可以构造出原问题的最优解

步骤二 : 递归的定义最优解

  • 动态规划的灵魂(我的理解)在于二维数组,定义I,j进行遍历
  • 设c[i, j]为Sij中最大兼容子集中的活动数
  • Xk在最有优解中被使用,k的取值有j-i-1种可能

2. 贪心算法

贪心策略:

  • 对活动的完成时间进行非减序排列
  • 每次选择最早完成时间的活动加入最优解子集
  • 贪心的意义在于:使得剩余安排时间最大化

3. 注解

  • 贪心算法依赖于以往所做的选择,不依赖于未来的选择,也不依赖于子问题的解。动态规划采用的是自底向上,而贪心算法则是自顶向下,以迭代的方式相继求解。
  • 贪心策略认为:最早结束的活动一定在最优解中。这是解决问题的核心。

4. 证明贪心算法可以构成最优解:

设E = {1,2,3,...,n}为所给的活动集合。由于E中活动按结束时间的非递减序列排列,故活动1具有最早完成时间。
首先证明,活动安排问题有一个最优解以贪心选择开始,即该最优解中包含活动1.设A∈E是所给的活动安排问题的一个最优解,且A活动也按结束时间非减序排列,A中的第一个活动是活动k。
若k=1,则A就是一个以贪心选择开始的最优解。
若k>1,则设B=(A-{k})U{1}。由于f1<=fk,且A中活动是相容的,故B中活动也是相容的。
又B中的活动个数与A中的活动个数相等,且A是最优的,所以,B也是最优的。
也就是说,B是一个贪心选择的最优解。
所以,总存在贪心选择的最优解。(贪心选择的证明)

5. 代码

template<class Type>
/*
Input: 
    n:活动的总数目
    s[]: 每个活动的开始时间
    f[]: 每个活动的结束时间
Output:
    A[]: 最终活动选择结果,要则为1,不要则为0
*/
void GreedySelector(int n, Type s[], Type f[], bool A[])
{
    A[1] = true;
    int j = 1;
    for(int i=2; i<=n; i++)
    {
        if(s[i] > = f[i]) 
        {
            A[i] = true;
            j = i;
        }
        else
            A[i] = false;
    }
}

最优装载

1. 简短说明

排序,每次选择重量较轻的先装入

2. 代码

template<class Type>
/*
Input:
    w[]: 第i集装箱的重量
    c: 轮船的载货量
    n: 集装箱整体的数量
Output:
    x[]: 对于第i个集装箱是否装入的表示,1/0 
*/
void Loading(int x[], Type w[], Type c, int n)
{
    int * t = new int[n+1];
    Sort(w, t, n);
    for(int i=1; i<=n; i++) x[i]=0;
    for(int i=1; i<=n && w[t[i]]<=c; i++) {x[t[i]]=1; c-=w[t[i]]}
}

哈夫曼编码

1. 相关解释

  • 前缀码:0/1为串,每个字符的代码都不是其他字符的前缀,前缀码长度不均
  • 哈夫曼树:又称最优二叉树,是一种带权路径长度最短的二叉树
  • 表示最优前缀码的二叉树一定总是一棵完全二叉树,也就说每个结点都有2个儿子。一般情况下,若C是编码字符集,表示其最优前缀码的二叉树中有 |C| 个叶子,每个叶子对应于字符集中一个字符,|C|-1 个内部结点

2. 霍夫曼编码的具体步骤

  • 将信源符号的概率按减小的顺序排队。
  • 把两个最小的概率相加,并继续这一步骤,始终将较高的概率分支放在右边,直到最后变成概率1。
  • 画出由概率1处到每个信源符号的路径,顺序记下沿路径的0和1,所得就是该符号的霍夫曼码字。
  • 将每对组合的左边一个指定为0,右边一个指定为1(或相反)。

3. 代码

/*
Huffman类的定义
*/
template<class Type>
class Huffman
{
  friend BinaryTree<int> HuffmanTree(Tree[], int);
  //如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数
  public:
    operator Type () const {return weight;}
  private:
    BinaryTree<int> tree;
    Type weight;
}
/*
算法HuffmanTree
*/
template<class Type>
BinaryTree<int> HuffmanTree(Type f[], int n)
{
  //生成单结点树
    Huffman<Type> * w = new Huffman<Tree> [n+1];
    BinaryTree<int> z,zero;
    for(int i=1; i<=n; i++)
    {
        z.MakeTree(i,zero,zero);
        w[i].weight=f[i];
        w[i].tree=z;
    }
    //建立优先队列
    MinHeap<Huffman<Type>> Q(1);
    Q.Initialize(w,n,n);
    //反复合并最小频率树
    Huffman<Type> x,y;
    for(int i=1;i<n;i++)
    {
        Q.DeleteMin(x);
        Q.DeleteMin(y);
        z.MakeTree(0,x.tree,y,tree);
        x.weight+=y.weight; x.tree=z;
        Q.Insert(x);
    }
    Q.DeleteMin(x);
    Q.Deactivate();
    delete [] w;
    return x.tree;
}

单源最短路径

1. Dijkstra算法

其基本思想是,设置 顶点集合S 并不断地作贪心选择来扩充这个集合。
一个顶点属于集合S当且仅当 从源到该顶点的最短路径长度 已知。
初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并 用数组dist记录当前每个顶点所对应的最短特殊路径长度
Dijkstra算法每次 从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到所有其它顶点之间的最短路径长度。
一旦S包含了所有V中定点, dist就记录了从源到所有其他顶点之间的最短路径长度

2. 代码

template<class Type>
/*
Input:
    n:结点个数
    v: 源点
    dist[i]: 尚未被扩充的顶点i 与 目标顶点集合的最短距离
    pre[i]: 记录的是从源到顶点i的最短路径上的前一个顶点,初始时,对所有i!=1,置pre[i]=v
    Type **c: c[i][j]表示边(i,j)的权
*/
void Dijkstra(int n, int v, Type dist[], int pre[], Type **c)
{
    bool s[maxint]; //用于记录那些点已经记录,那些点还未记录
    for(int i=1; i<=n; i++)
    {
        dist[i] = c[v][i];
        s[i]=false;
        if(dist[i] == maxint) prev[i]=0;
        else prev[i]=v;
    }
    dist[v]=0; s[v]=true;   //将源处理下
    //如下的循环开始迭代
    for(int i=1; i<=n; i++)
    {
        int temp = maxint;
        int u = v;
        for(int j=1; j<=n; j++) //筛选出最小dist[j]
        {
            if((!s[j])&&(dist[j]<temp))     //!s[j]:结点j还未被记录
            {
                u=j;
                temp=dist[j];
            }
        }
        s[u]=true;
        for (int j = 0; j < n; j++)
        {
            if((!s[j])&&(c[u][j]<maxint)) 
            {
                Type newdit = dist[u]+c[u][j];
                if(newdist<dist[j])
                {
                    dist[j]=newdist;
                    prev[j]=u;
                }
            }
        }
    }
}

最小生成树

1. 最小生成树MST性质

设G=(V,E)是连通带权图,U是V的真子集。如果(u,v)∈E,且u∈U,v∈V-U,且在所有这样的边中,(u,v)的权c[u][v]最小,那么一定存在G的一棵最小生成树,它以(u,v)为其中一条边。这个性质有时也称为MST性质。

2. Prime算法

  • 设G=(V,E)是连通带权图,V={1,2,…,n}
  • 首先置S={1}
  • 选取满足条件i∈S,j∈V-S,且c[i][j]最小的边,将顶点j添加到S中
  • 不断执行上一步,扩充S,直至S=V
  • 最后,在这个过程中选取到的所有边恰好构成G的一棵最小生成树

3. Prime算法实现

/*
@http://blog.csdn.net/jnu_simba/article/details/8869876
*/
/* Prim算法生成最小生成树  */
void MiniSpanTree_Prim(MGraph MG)
{
    int min, i, j, k;
    int adjvex[MAXVEX];/* 保存相关顶点下标 */
    int lowcost[MAXVEX];/* 保存相关顶点间边的权值 */
    lowcost[0] = 0;/* 初始化第一个权值为0,即v0加入生成树 */
    /* lowcost的值为0,在这里就是此下标的顶点已经加入生成树 */
    adjvex[0] = 0;/* 初始化第一个顶点下标为0 */
    cout << "最小生成树的边为:" << endl;
    for (i = 1; i < MG.numVertexes; i++)
    {
        lowcost[i] = MG.arc[0][i];/* 将v0顶点与之有边的权值存入数组 */
        adjvex[i] = 0;/* 初始化都为v0的下标 */
    }

    for (i = 1; i < MG.numVertexes; i++)
    {
        min = INFINITY; /* 初始化最小权值为∞, */

        j = 1;
        k = 0;

        while (j < MG.numVertexes)/* 循环全部顶点 */
        {
            if (lowcost[j] != 0 && lowcost[j] < min)/* 如果权值不为0且权值小于min */
            {
                min = lowcost[j];/* 则让当前权值成为最小值 */
                k = j;/* 将当前最小值的下标存入k */
            }

            j++;
        }

        cout << "(" << adjvex[k] << ", " << k << ")" << "  "; /* 打印当前顶点边中权值最小的边 */
        lowcost[k] = 0;/* 将当前顶点的权值设置为0,表示此顶点已经完成任务 */

        for (j = 1; j < MG.numVertexes; j++)/* 循环所有顶点 */
        {
            /* 如果下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 */
            if (lowcost[j] != 0 && MG.arc[k][j] < lowcost[j])
            {
                lowcost[j] = MG.arc[k][j];/* 将较小的权值存入lowcost相应位置 */
                adjvex[j] = k;/* 将下标为k的顶点存入adjvex */
            }
        }
    }
    cout << endl;
}

4. Kruskal算法

  • 首先将G的n个顶点看成n个孤立的连通分支。将所有的边按权从小到大排序。
  • 然后从第一条边开始,依边权递增的顺序查看每一条边,并按下述方法连接2个不同的连通分支:
  • 当查看到第k条边(v,w)时,如果端点v和w分别是当前2个不同的连通分支T1和T2中的顶点时,就用边(v,w)将T1和T2连接成一个连通分支,然后继续查看第k+1条边;如果端点v和w在当前的同一个连通分支中,就直接再查看第k+1条边。
  • 这个过程一直进行到只剩下一个连通分支时为止。

5. Kruskal算法实现

/*
@http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html
*/
typedef struct          
{        
    char vertex[VertexNum];                                //顶点表         
    int edges[VertexNum][VertexNum];                       //邻接矩阵,可看做边表         
    int n,e;                                               //图中当前的顶点数和边数         
}MGraph; 
 
typedef struct node  
{  
    int u;                                                 //边的起始顶点   
    int v;                                                 //边的终止顶点   
    int w;                                                 //边的权值   
}Edge; 

void kruskal(MGraph G)  
{  
    int i,j,u1,v1,sn1,sn2,k;  
    int vset[VertexNum];                                    //辅助数组,判定两个顶点是否连通   
    int E[EdgeNum];                                         //存放所有的边   
    k=0;                                                    //E数组的下标从0开始   
    for (i=0;i<G.n;i++)  
    {  
        for (j=0;j<G.n;j++)  
        {  
            if (G.edges[i][j]!=0 && G.edges[i][j]!=INF)  
            {  
                E[k].u=i;  
                E[k].v=j;  
                E[k].w=G.edges[i][j];  
                k++;  
            }  
        }  
    }     
    heapsort(E,k,sizeof(E[0]));                            //堆排序,按权值从小到大排列       
    for (i=0;i<G.n;i++)                                    //初始化辅助数组   
    {  
        vset[i]=i;  
    }  
    k=1;                                                   //生成的边数,最后要刚好为总边数   
    j=0;                                                   //E中的下标   
    while (k<G.n)  
    {   
        sn1=vset[E[j].u];  
        sn2=vset[E[j].v];                                  //得到两顶点属于的集合编号   
        if (sn1!=sn2)                                      //不在同一集合编号内的话,把边加入最小生成树   
        {
            printf("%d ---> %d, %d",E[j].u,E[j].v,E[j].w);       
            k++;  
            for (i=0;i<G.n;i++)  
            {  
                if (vset[i]==sn2)  
                {  
                    vset[i]=sn1;  
                }  
            }             
        }  
        j++;  
    }  
}  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 目录 1.贪心算法步骤 2.两个关键要素 3.两种背包问题3.1 0-1背包问题(适用于动态规划,不满足贪心选择性...
    王侦阅读 4,898评论 2 3
  • 引言:前两天在复习贪心算法时,看到单源最短路径的Dijkstra算法和最小生成树的Prim算法,由于自己不认真,竟...
    cp_insist阅读 7,201评论 1 2
  • 可用贪心算法解决的几个基本问题 关键:看问题有没有贪心选择性质和最优子结构性质。有些问题看似是可以用贪心算法,但是...
    碧影江白阅读 6,181评论 1 2
  • 时间:11月20日 学员:宋思铭 任教老师:小美老师 教学目标: 一 : 探讨在生活中轮子的作用。 二: 走 ...
    Letnaturetakeit阅读 166评论 0 0
  • 黄堡文化研究 第199期作者:史济民编辑:秦陇华 编者:请珍惜生活中每一件小事情,因为有一天你反思从前,会发现这...
    primates阅读 1,326评论 1 2