算法篇-7-贪心算法-Huffman编码&Dijkstra单源最短路径&Kruskal最小生成树

本系列所有代码https://github.com/YIWANFENG/Algorithm-github

huffman编码

题目:

依据给定字符以及相应频率构造该字符的哈夫曼编码。

 

算法思路分析与相关公式:

Haffman即前缀码,用一棵二叉树即可标示,树叶表示给定的字符,每个字符的前缀码就是从树根到该字符所在的树叶的一条道路。二叉树的每一路分支的路径我们在向右时即为1 ,向左时即为0。构造一棵haffman树即可得出我们的编码。

 

要使空间节约,那么使用频率低的字符编码长点,使用频率高的编码短点,对应的为相应的叶子节点的深度值大或者小。我们每次选择总频率小的子树的合并为新子树(频率为子树总频率之和),然后如此重复来得到最终编码树。(叶子结点视为频率为该节点的频率)。

 

 

程序源代码:

class cmp {
public:
       booloperator() (const int &a,const int &b) {
              if(a>b) return true;
              return false;
       }
};
 
class CHuffmanChar {
public:
       charc_;                   //保存字符
       CHuffmanChar*parent_; //指向父节点
       intchild_type_ ;            //1==left子节点  0 == right
};
 
class CHuffmanChar_Heap {
public:
       intindex_;                    //原始编号
       floatweight_;        //权重
       booloperator < (const CHuffmanChar_Heap &c2) const {
              return weight_ > c2.weight_;
       }
};
 
void HuffmanEncoding(int n, const chardata[], float fre[])
{
       //Huffman编码
       //n字符数量
       //data[]原始字符
       //fre[]字符出现的频率
       CHuffmanCharhc[n + n-1]; //中间出现节点数n-1
       inti, j;
       for(i=0;i<n; ++i)
              hc[i].c_ = data[i];
       for(i=0;i<n+n-1; ++i)
              hc[i].parent_ = NULL;
      
       priority_queue<CHuffmanChar_Heap>q;
       CHuffmanChar_Heaph, hr;
       for(i=0;i<n; ++i) {
              h.index_= i;
              h.weight_ = fre[i];
              q.push(h);
       }
      
       for(i=0;i<n-1; ++i) {
              h = q.top();
              q.pop();
              hr = q.top();
              q.pop();
              hc[h.index_].parent_ = &hc[n+i];
              hc[h.index_].child_type_ = 1;
              hc[hr.index_].parent_ = &hc[n+i];
              hc[hr.index_].child_type_ = 0;
              h.weight_ += hr.weight_;
              h.index_ = n+i;
              q.push(h);
       }
      
       charcode[n];
       for(i=0;i<n; ++i) {
              //从子节点寻根而上
              j = 0;
              CHuffmanChar *pc = &hc[i];
              while(pc->parent_!=NULL) {
                     code[j++]='1'-pc->child_type_;
                     pc= pc->parent_;
              }
              cout<<hc[i].c_<<':';
              for(j--; j>=0; j--) {
                     cout<<code[j];
              }
              cout<<'\n';
       }
      
}


Dijkstra单源最短路径

题目:

给定一带权有向图G=(V,E),求指定顶点X顶点的最短路径长度。

 

算法思路分析以及相关公式:

基本思想:设置顶点集合S并不断贪心扩充此集合。

首先确认两点,1:设从X到最短的顶点为x1(肯定是直达的),那么比这次短的顶点一定是直达的或者是从x1间接到达。 2:由1可知,设从X 到其他顶点第k短的是xk,(xk加入到S中),那么第k+1短的顶点一定是直达或者经过S中某点间接到达的。

 

做法是设置集合S,来收集已经知道X到该点的最短路径的点。E保存不知最短路径的点

1.  首先选择一个属于E的点u,使X到u路径最短。

2.  将u加入到S,在E中取出u,并计算在u加入S后对在E中的点与X的最短距离的影响。

即判断所有在E中的点c,是否存在从X到u,再从u到c的距离比原本X到c的距离小,若小则更新,否则不处理。

1.若S包含所有点,则结束否则重复上述过程。

 

程序源代码:

template <class T_>
void Dijkstra_ShortestPath(int n,int v,T_dist[],int prev[],
                                          const T_ *c,const T_ INF)
{
       //n:(in)顶点数量
       //v:(in)源顶点索引(0-...)
       //dist[]"(out)从源定点 到每个定点的距离
       //prev[]:(out) 在最短路径上每个定点的前面的一个顶点
       //c(in):两个顶点间的距离
       bools[n]; // s[i]表示该点是否在最短路径已知的顶点集合内。
       for(inti=0;i<n;i++) s[i] = false;
      
       for(inti=0; i<n; ++i) {
              dist[i]=c[v*n+i];
              if(dist[i]==INF) prev[i] = -1;
              else prev[i]=v;
       }
       //addv to s
       dist[v]= 0;
       s[v]= true;
      
       for(inti=1; i<n; ++i) {
              T_ dist_tmp = INF;
              int u = v;
              //选出该次到v最短的点,该点不属于s
              for(int j=0; j<n; ++j) {
                     if(s[j])continue;
                     if(dist_tmp==INF|| dist[j]<dist_tmp) {
                            dist_tmp = dist[j];
                            u = j;
                     }
              }
              s[u] = true;
              //加入该点后对其他点的影响
              for(int j=0; j<n; ++j) {
                     if(s[j]|| c[u*n+j]==INF) continue;
                     T_dist_new = dist[u]+c[u*n+j];
                     if(dist_new<dist[j]){
                            dist[j]=dist_new;
                            prev[j] = u;
                     }
              }
       }
} 
 
 
template<class  T_>
void Out(int n,int v,T_ dist[],int prev[])
{
       //n:(in)顶点数量
       //v:(in)源顶点索引(0-...)
       //dist[]"(out)从源定点 到每个定点的距离
       //prev[]:(out) 在最短路径上每个定点的前面的一个顶点
       intpath_points[n*(n-1)/2+1];
       inti,j,k;
       for(i=0;i<n; ++i) {
              if(i==v) continue;
              cout<<"顶点"<<i<<",最短路径长度="<<dist[i]<<endl;
             
              j=1;
              path_points[0] =i;
              k = i;
              while(prev[k]!=v && prev[k]!=-1){
                     path_points[j++]= prev[k];
                     k=prev[k];
              }
              for(--j; j>=0; j--) {
                     cout<<path_points[j]<<" ";
              }
              cout<<endl;
       }
      
}
 
int main() {
      
       intn=5;
       constfloat  INF = 65535;
       floatdist[n];
       intprev[n];
       floatc[n*n];
       constfloat graph_edges[] = {
              0,10,INF,30,100,
              10,0,50,INF,INF,
              INF,50,0,20,10,
              30,INF,20,0,60,
              100,INF,10,60,0
       };
       for(inti=0;i<n*n;i++) {
              c[i]=graph_edges[i];
       }
       Dijkstra_ShortestPath<float>(n,0,dist,prev,c,INF);
       Out<float>(n,0,dist,prev);
      
      
       cin.get();
       return0;
}


 

Kruskal最小生成树

题目:

已知一幅图的顶点数n、边e以及边的权重w[i],求一权重最小的生成树。

 

算法思路分析以及相关数学公式:

将G的n个顶点看成n个孤立的联通分支,将所有边按权从小到大排序,然后依次增加权重最小的边来连接两个独立的联通分支,在加边时,注意不可以让加入的边在图中构成回路。

直至加入了n-1条边或者加入的边数为e。


另一种求最小生成树的算法是prim算法,他是从顶点的角度考虑。

 

 

 

//并差集实现//
class CUnionFind {
public:
       CUnionFind();
       ~CUnionFind();
       voidInit(int num_elem) ;
      
       intFindSubSet(int elem_id) ;
      
       voidSubSetUnion(int set1,int ste2);
protected:
       intn_;                   //元素数量
       int*parent_id_;      //每个元素的父节点索引,-1表示该点为根节点
       int*depth_;           //每个元素所属子树的深度
       voidRelease();
};
 
void CUnionFind::Release() {
       n_= 0;
       if(parent_id_){
              delete [] parent_id_;
              parent_id_ = NULL;
       }
             
       if(depth_){
              delete [] depth_;
              depth_ = NULL;
       }
}
 
CUnionFind::CUnionFind() {
       n_= 0;
       parent_id_= NULL;
       depth_= NULL;
}
 
CUnionFind::~CUnionFind() {
       Release();
}
void CUnionFind::Init(int num_elem) {
       //元素编号从1开始
       Release();
       n_= num_elem;
       parent_id_= new int[n_];
       depth_= new int[n_];
       for(inti=0;i<n_;++i) {
              parent_id_[i]=-1;
              depth_[i]=0;
       }
}
      
int CUnionFind::FindSubSet(int elem_id) {
       //返回元素elem_id所属自己的编号
       inti = elem_id-1;
       while(parent_id_[i]!=-1)
              i = parent_id_[i];
       returni;
}
void CUnionFind::SubSetUnion(int set1,intset2) {
       //合并set1,set2
       if(set1== set2) return ;
       if(depth_[set1]==depth_[set2]){
              parent_id_[set2] = set1;
              depth_[set1]++;
       }else if(depth_[set1]<depth_[set2]){
              parent_id_[set1] = set2;
       }else {
              parent_id_[set2] = set1;
       }
}

 
 
class Edge{
public:
       intweight;
       intu,v;
};
class cmp {
public:
       booloperator() (Edge &a,Edge&b) {
              return a.weight > b.weight;
       }
};
 
 
bool Kruskal(int n,int e,Edge E[],Edge t[])
{
       //n顶点数
       //e边数
       //E[]具体边
       //t[](out)筛选出的边
      
       priority_queue<Edge,vector<Edge>,cmp> q;
       for(inti=0; i<e; ++i) {
              q.push(E[i]);
       }
       CUnionFindU;
       U.Init(n);
      
       intk =0;
       while(e&& k<n-1) {            //正常树应该会有n-1条边
              Edge x;
              x = q.top();    
              q.pop();
              e--;
              int a = U.FindSubSet(x.u);
              int b = U.FindSubSet(x.v);
             
              if(a!=b) {
                     t[k++]= x;
                     //cout<<"["<<x.u<<','<<x.v<<"]"<<endl;
                     U.SubSetUnion(a,b);
              }
       }
      
}
void Out(int n,Edge t[]) {
       //n顶点数
       //t[] 筛选出的边
       for(inti=0;i<n-1;++i) {
              cout<<"["<<t[i].u<<','<<t[i].v<<"]"<<endl;
       }
}
 
int main() {
       intn = 6;
       EdgeE[10];
       Edget[10];
       E[0].u= 1;        E[1].u = 1;          E[2].u = 1; 
       E[0].v= 2;        E[1].v = 3;                 E[2].v = 4;
       E[0].weight= 6;   E[1].weight = 1;     E[2].weight = 5;
      
       E[3].u= 3;        E[4].u = 5;          E[5].u = 3; 
       E[3].v= 2;        E[4].v = 2;                 E[5].v = 4;
       E[3].weight= 5;   E[4].weight = 3;     E[5].weight = 5;
      
       E[6].u= 3;        E[7].u = 3;          E[8].u = 4; 
       E[6].v= 5;        E[7].v = 6;                 E[8].v = 6;
       E[6].weight= 6;   E[7].weight = 4;     E[8].weight = 2;
      
       E[9].u= 5;
       E[9].v= 6;
       E[9].weight= 6;
      
       Kruskal(n,10,E,t);
       Out(n,t);
       cin.get();
       return0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值