算法分析与设计复习3(贪心与回溯算法)

贪心算法
哈夫曼编码
单源点最短路径
最小生成树

活动安排问题
最优装载问题
单机调度问题

哈夫曼编码先统计频率,将频率最小两点combine然后重复以上步骤,直到形成一棵树,一般的编码习惯为左子树取0,右子树取1

单源点最短路径(Dijkstra算法*)

不使用ADT版

#include<iostream>
#define N 1000
#define INF 0x7fffffff
using namespace std;


bool visited[N+1]={0};//存储各结点是否访问过,索引从1开始
int map[N+1][N+1];//存储图的邻接表,从(1,1)开始
int Dist[N+1];//存放最短路径,索引从1开始

//dijkstra算法中的N与宏定义中的N含义不同,此处不执行代码,方便起见,用同一个字母
void dijkstra (int s) {//传入源点
    int pos = -1;
    for (int i = 1; i <= N;i++) {//该数组从1开始
        Dist[i] = map[s][i];
    }
    visited[s] = 1;
    for (int i = 1; i <= N;i++) {
        int min = INF;
        for (int j = 1; j <= N;j++) {//找未被访问的最小距离点
            if(Dist[j]<min&&!visited[j]) {
                min = Dist[j];
                pos = j;
            }
        }
        if(min==INF)//不联通点
            break;
        visited[pos] = 1;//设为已访问
        for (int j = 1; j <= N;j++) {//更新未被访问点的最小距离
            if(!visited[j]&&Dist[j]>Dist[pos]+map[pos][j])
                Dist[j] = Dist[pos] + map[pos][j];
        }
    }
}

使用HNU的ADT版

void Dijkstra1(int* D, int s)
    {
    	int v,w;
    	//注意,通常情况下D数据是需要对source进行初始化的,但这里不需要,D的初始化在main函数里做过了,要是再做的话,0会覆盖掉本该出现的INF
    	for(int i=0;i<G->n();i++) {//这个意思是说要遍历n个结点
			v=minVertex(D);//找到D数组中为访问的最小数的下标,别的不用管,minVertex帮你做过了
			if(v==INFINITY) return;
			G->setMark(v,VISITED);
			for(w=G->first(v);w<G->n();w=G->next(v,w)) //我这里还有疑问,为什么不需要判断w结点是否已访问过呢?我还试了下,如果验证是否访问,反而会出现错误
				if(D[w]>D[v]+G->weight(v,w)) D[w]=D[v]+G->weight(v,w);
		}
    }
最小生成树(Prim算法*)
int Prim() {
    int pos,min,sum=0;
    memset(visited, 0, sizeof(visited));
    for (int i = 1; i <= N; i++)
        Dist[i] = map[1][i]; // Prim算法从哪个点出发都是一样的
    visited[1] = 1;
    for (int i = 1; i <= N;i++) {
        min = INF;
        for (int j = 1; j <= N;j++) {
            if(!visited[j]&&Dist[j]<min) {
                min = Dist[j];
                pos = j;
            }
        }
        if(min==INF)
            break;
        visited[pos]=1;
        for (int j = 1; j <= N;j++) {
            if(!visited[j]&&Dist[j]>map[pos][j])
                Dist[j] = map[pos][j];
        }
    }
    for (int i = 1;i<=N;i++) {//这里这一步是算最小生成树的各边权重和,可以没有
        sum += Dist[i];
        if(Dist[i]==INF)
            return -1;
    }
    return sum;
}

HNU的ADT版本

int V[G->n()];
int v,w,i;
for(int i=0;i<G->n();i++) {
	v=minVertex(D);
	if(v==INFINITY) return;
	G->setMark(v,VISITED);
	if(v!=s) ADDEdgetoMST(V[v],v);
	for(w=G->first(v);w<G->n();v=G->next(v,w)) 
		if(D[w]>G->weight(v,w)) {
		D[w]=G->weight(v,w);
		V[w]=v;
		}
}
}

结构差不太多,一通百通

最小生成树(kruskal算法)

涉及归并
来源leetcode官方题解

int Find(vector<int>& parent,int index) {
    if(parent[index]!=index) {//当前结点有父节点
        parent[index] = Find(parent, parent[index]);//找爷爷结点
    }
    return parent[index];//当前结点没有父节点,返回
}
void union(vector<int>& parent,int index1,int index2) {//归并两个连通分量
    parent[Find(parent, index1)] = Find(parent, index2);
}
vector<int> findRedundantConnection(vector<vector<int> >&edges) {
    int n=edges.size();
    vector<int> parent(n+1);
    for(int i=1;i<=n;++i) {
        parent[i] = i;//先初始化为每个结点都未连通
    }
    for(auto& edge:edges) {
        int node1=edge[0],node2=edge[1];
        if(Find(parent,node1)!=Find(parent,node2)) {
            Union(parent, node1, node2);
        }
        else {
            return edge;
        }
    }
    return vector<int>{};
}
符号三角形*(回溯算法)

下图是由14个“+”和14个“-”组成的符号三角形。2个同号下面都是“+”,2个异号下面都是“-”。
在这里插入图片描述在一般情况下,符号三角形的第一行有n个符号。符号三角形问题要求对于给定的n,计算有多少个不同的符号三角形,使其所含的“+”和“-”的个数相同。

符号三角形斜着算的,具体看代码
这里贴两种代码,一个是老师给的标准模板(虽然跑不出来正确答案)

int sum = 0;//放结果,三角形个数
int p[1000][1000];//放符号三角形本身,开大数组
int count=0;//统计符号个数
int n;//三角形层数
int half = (n+1) * n / 4;//符号三角形的一半应该有这个数

void Backtrack(int t)
{
  if ((count>half)||(t*(t-1)/2-count>half)) return;//不符合条件剪枝剪掉
  if (t>n) sum++;//能走到这里必然是满足条件的
    else
      for (int i=0;i<2;i++) {
        p[1][t]=i;
        count+=i;
        for (int j=2;j<=t;j++) {
          p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
          count+=p[j][t-j+1];
        }
      Backtrack(t+1);
      for (int j=2;j<=t;j++)
        count-=p[j][t-j+1];
      count-=i;
     }
  }

int main() {
    cin >> n;
    Backtrack(0);
    cout << sum;
    return 0;
}

另一个版本,可以跑通的代码,借鉴CSDN上大佬的,漂亮且简洁,最重要的是,可以跑通的!不同在于递归终止条件和剪枝的匹配。

#include<cstdio>
using namespace std;
int n;
int half;
int sum; 
int count; //1代表+ ; 0代表- ; count就是+的计数 
int p[100][100];

void Dfs(int t);

int main()
{
	scanf("%d",&n);
	if((n+1) * n % 4)
	{
		printf("ERROR");
		return 0 ;
	} 
	half = (n+1) * n / 4; // + 和 - 都不能超过half
	sum = 0; //答案数初始化;
	count = 0; 
	
	Dfs(0);
	
	printf("%d",sum); 
	
	return 0;
}

void Dfs(int t)//第t步,也就是第一行的第t位 
{
		if(t == n && count == half)
		{
			sum ++;
			return ; //得到一个答案,向上return 回溯 
		} 
		
		//如果还有位子,一个位子要尝试 + - 两种情况 
		for(int i = 0; i < 2; i++) 
		{
			p[0][t] = i;
			count += i; 
			
			for(int j = 1; j <= t; j++)
			{
				p[j][t-j] = p[j-1][t-j]^p[j-1][t-j+1];
				count += p[j][t-j];
			}
			
			if(count <= half && (t+2)*(t+1)/2 - count <= half)//当前图形中+ -都没超过一半
			{
				Dfs(t + 1); 
			} 
			
			//还原本次,考虑下一个符号
			for( int j = 1; j <= t; j++)
			{
				count -= p[j][t-j];
			} 
			count -= i;
		}
}	
八皇后问题(回溯算法*)

还是上两个版本,一个是课上写过的标准版本,一个是CSDN上大神的版本
大神版

bool place(int k)
{
    for(int j = 1;j<k;j++)
        if(abs(x[k] - x[j]) == abs(k-j)||x[j] == x[k])//皇后在对角线上或者竖线上 
            return false;//返回不能放 
        return true;
 
}
void backtrack(int t)
{
    if(t>num) //num为皇后的数目
    {
        sum++;//sum为所有的可行的解
        for(int m = 1;m<=num;m++)
        {
            cout<<"<"<<m<<","<<x[m]<<">";//这一行用输出当递归到叶节点的时候,一个可行解
        }
        cout<<endl;
    }
    else
        for(int i = 1;i<=num;i++)
        {
            x[t] = i;
            if(place(t)) 
				backtrack(t+1);//此处的place函数用来进行我们上面所说的条件的判断,如果成立,进入下一级递归
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值