机试指南 cha 5 图论

机试指南 cha 5 图论

并查集

畅通工程

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

using namespace std;
/*
并查集
畅通工程:所有顶点之间均可连通
time : 20+20+20
*/

int findroot(int v[],int a)
{
    // 寻找根节点的过程中,如果不是根节点的直接孩子,则修改为其直接孩子
    if (v[a] == -1)
        return a;
    else
    {
        int tmp = findroot(v,v[a]);
        v[a] = tmp;
        return tmp;
    }
}
int main()
{
    int m,n,x1,x2;
    int i,l,r;
    int v[1001] = {0}; // v[i] 代表第i个顶点对应的双亲节点
    while (cin >> n >> m)
    {
        if (n == 0)
            break;
        for (int i = 1;i<=n;i++)
            v[i] = -1;
        for (i = 0 ;i < m;i++)
        {
            cin >> x1 >> x2;
            // 根节点不同,即不在一个集合,则连通
            l = findroot(v,x1);
            r = findroot(v,x2);
            if (l!=r)
                v[r] = l;


        }
        // 输入完了以后,相连的结点会在同一个集合中,计算有几个并查集,并查集-1即为新增路径条数
        int count = 0 ;
        for (i = 1;i<=n;i++)
        {
            if (v[i] == -1)
                count ++;
        }
        cout << count -1 << endl;

    }
    return 0;
}

最小生成树

还是畅通工程

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>

using namespace std;
/*
最小生成树
time :
*/
int v[1010];
int findroot(int a)
{
    if (v[a] == -1)
        return a;
    else
    {
        int tmp = findroot(v[a]);
        // 非根节点a直接和根相连
        v[a] = tmp;
        return tmp;
    }
}
struct node
{
  int a;
  int b;
  int w;
};
bool cmp(const node &a,const node &b)
{
    return a.w < b.w; // 从小到大
}
int main()
{
    int n,a,b,w;
    int i,l,r;
    node node[5000],tmp;
    int ans = 0;
    while (cin >>n && n!=0)
    {
        ans = 0;
        for (int i = 1;i<=n;i++)
            v[i] = -1;
        // 初始化node数组
        for (i = 0;i< n*(n-1)/2 ;i++)
        {
            cin >> a >>b >> w;
            node[i].a =a;
            node[i].b =b;
            node[i].w =w;
        }
        sort(node,node+n*(n-1)/2,cmp); // 不是n条边!
        for (i = 0;i<n*(n-1)/2;i++)
        {
            tmp = node[i];
            l = findroot(tmp.a);
            r = findroot(tmp.b);
            if (l!=r)
            {
                v[r] = l; // 两个根节点集合合并
                ans += tmp.w;
            }
        }
        cout << ans << endl;

    }

    return 0;
}

最短路径

最短路

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>

#define INFINITY 65515
using namespace std;
/*
最短路径
time :
*/

int main()
{
    int i,j,n,m;
    int p[101][101];
    while (cin >> n >> m && (n!=0 && m != 0))
    {
        for (i = 1;i<=n;i++)
            for (j=1;j<=n;j++)
        {
            if (i == j)
                p[i][i] = 0;
            else
                p[i][j] = INFINITY;
        }
        for (i = 0 ;i < m;i++)
        {
            int a,b,c;
            cin >>  a>> b >> c;
            p[a][b] = c;
            p[b][a] = c;
        }
        for (int k = 1 ; k <= n; k++)
        {
            for ( i = 1;i<=n ; i++)
                for (j = 1 ;j<=n ; j++)
                    if ( i!=k && j!= k && (p[i][k] + p[k][j] < p[i][j]))
                    {
                       p[i][j] = p[i][k] + p[k][j];
                    }
        }
        cout << p[1][n] <<endl;
    }

    return 0;
}

more is better

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>

using namespace std;
// 求并查集的过程中在每个集合根节点中记录该集合的人数

const int num = 100001;
int v[num]; // v[i] 为第i个节点的双亲节点
int sum[num];// sum[i]当且仅当v[i] == -1 时有效,数字代表该集合的人数

int findroot(int a)
{
    if (v[a] == -1)
        return a;
    else
    {
        int tmp = findroot(v[a]); // 递归得到根节点
        v[a] = tmp;
        return tmp;
    }
}
int main()
{

    int n;
    int a,b,i,r,l;
    while ( cin>>n )
    {
        for (i = 1;i<= num ; i++) // 这个地方不能设为2*n,因为如果n很大时,2*n可能会超过num
        {
            v[i] = -1; // 只知道N对好朋友,但不知道男孩的数目,最多为一对儿两个不同的男孩
            sum[i] = 1;
        }
        for (i = 1 ;i <= n ;i++)
        {
            cin >> a >> b;
            l = findroot(a);
            r = findroot(b);
            if (l!=r)
            {
              v[l] = r;
              sum[r] += sum[l] ;// 注意!合并集合时要把两个集合的sum值合并
            }

        }
        int max = 1;
        for (i = 1;i<=num;i++)
        {
            if (v[i] == -1 && sum[i] > max)
            {
                max = sum[i];
            }
        }
        cout << max <<endl;
    }


    return 0;
}

Freckles

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <algorithm>

using namespace std;
const int num = 101;
int v[num]; // 点集合
struct edge
{
    int v1,v2; // 每条边的两个端点均在v[num]中
    float w;
}e[num*(num-1)/2]; // 边集合

int findroot(int a)
{
    if (v[a] == -1)
        return a;
    else
    {
        int tmp = findroot(v[a]); // 递归得到根节点
        v[a] = tmp;
        return tmp;
    }
}
bool cmp(const edge &a,const edge &b)
{
    return a.w < b.w;
}
int main()
{
    int n,i,j,l,r;
    float dot[num][2],a,b;
    float ans;
    while (cin>>n)
    {
        for (i=1;i<=n;i++)
        {
            v[i] = -1;
            cin >> dot[i][0] >> dot[i][1] ;
        }
        // 初始化边edge数组,n个点有n(n-1)/2条边,进行二层循环
        float ans;
        int size = 1;
        for (i = 1 ;i <= n ;i++)
        {
            for (j = i+1 ; j <= n ; j++)
            {
                a = dot[i][0] - dot[j][0];
                b = dot[i][1] - dot[j][1];
                ans = sqrt(a*a + b*b);
                e[size].v1 = i;
                e[size].v2 = j;
                e[size++].w = ans;

            }
        }
        // sort(edge),合并并查集
        sort(e+1,e+size,cmp);
//        for (i = 1 ;i<4;i++)
//            cout << e[i].w << endl;
        ans = 0;
        // 从小权值边开始选择,每次要判断这条边的两个端点是否在同一个集合中
        for (i = 1;i<size;i++)
        {
            l = findroot(e[i].v1);
            r = findroot(e[i].v2);
            if (l!=r)
            {
                v[l] = r;
               ans += e[i].w;
            }
        }
        printf("%.2f",ans);

    }
    return 0;
}

拓扑排序

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
using namespace std;
struct edge
{
    int nextnode;
};
queue<int> q;
vector<edge> vex[100];
int visited[100];

void findrudu(int n)
{
    // 找到入度为0的点(未在邻接表右侧链中出现的点),放到队列中
    int i,a;
    bool rudu[100];
    for (i = 0;i<100;i++)
        rudu[i] = true;
        // 一个再细微不过的知识点,都有可能成为决定成败的事情!
    for (i = 0;i<n;i++)
    {
        for (vector<edge>::iterator it = vex[i].begin() ; it != vex[i].end() ; it++)
        {
            a = (*it).nextnode;
            rudu[a] = false;
        }
    }

    for (i = 0;i<n;i++)
    {
      //  cout << rudu[i] <<' ' << visited[i] << endl;
        if (rudu[i]&&visited[i]){
            q.push(i);
        }
    }
}
int main()
{
    int n,m,i,a,b;
    edge e;
    vector<edge>::iterator it;
    // i 为顶点 , vex[i][0]为与该顶点相邻接的第一条弧的弧头顶点,vex[i] 为一个向量,记录与该顶点邻接的所有弧的弧头顶点
    while (cin >> n >> m)
    {
        if (m==0&&n==0)
            break;
        // 初始化vector
        for (i = 0;i<n;i++)
        {
            vex[i].clear();
            visited[i] = true;
        }
        // 初始化队列
        while (!q.empty())
            q.pop();

        for (i = 0;i<m;i++)
        {
            cin >> a >> b; // a->b为一条弧
            e.nextnode = b;
            vex[a].push_back(e);
        }
        findrudu(n);
        while (!q.empty())
        {
            a = q.front();// 从队列中取出一个入度为0的结点
            q.pop();
            vex[a].erase(vex[a].begin(),vex[a].end());// 删除所有的弧
            visited[a] = false;
            findrudu(n);
        }
        // 如果队列为空时仍旧有顶点为true,则存在环
        bool flag = true;
        for (i = 0;i<n;i++ )
            if (visited[i])
            {
                flag = false;
            }
        if (flag)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;



    }
    return 0 ;
}

太蠢的做法!结果超时了,完全可以第一次统计每个结点的入度,然后每次删除节点时对该入度数组进行– 操作,用bool真的太蠢了!而且这道题墨迹了一整个下午! 以后半小时内做不出来就研究答案!一道题目不要超过1个半小时!

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
using namespace std;

queue<int> q;
vector<int> vex[100];


int main()
{
    int n,m,i,a,b;
    vector<int>::iterator it;
    int degree[100];
    // i 为顶点 , vex[i][0]为与该顶点相邻接的第一条弧的弧头顶点,vex[i] 为一个向量,记录与该顶点邻接的所有弧的弧头顶点
    while (cin >> n >> m)
    {
        if (m==0&&n==0)
            break;
        // 初始化vector和degree数组
        for (i = 0;i<n;i++)
        {
            vex[i].clear();
            degree[i] = 0;
        }
        // 初始化队列
        while (!q.empty())
            q.pop();
        for (i = 0;i<m;i++)
        {
            cin >> a >> b; // a->b为一条弧
            vex[a].push_back(b);
            // 增加b的入度
            degree[b] ++;
        }
        for (i = 0;i<n;i++)
        {
            if (degree[i] == 0)
                q.push(i);
        }
        int c = 0; // 计数器,用来确定已经被删除的结点的个数,或者用bool数组,但bool数组还得遍历
        while (!q.empty())
        {
            a = q.front();// 从队列中取出队首
            q.pop(); // 删除队首
            c ++;
            for (it = vex[a].begin();it != vex[a].end() ; it++)
            {
                // 将待删除节点的邻接点的入度--,如果该邻接点成为入度为0的点,入队
                degree[*it] -- ;
                if (degree[*it] == 0)
                    q.push(*it);
            }
            vex[a].erase(vex[a].begin(),vex[a].end());// 删除所有的邻接顶点

        }

        if (c == n)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;



    }
    return 0 ;
}
  • 另一种遍历vector的方法
  • for (int i = 0;i

确定比赛名次 30min

和上面那道题比起来,只需要每次pop()之前或之后输出结点数据。而且并不需要erase掉被选中结点的链表,因为我们看的是被push进queue的结点。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
using namespace std;

vector<int> vex[501];
struct cmp{
    bool operator() (const int &a, const int& b ){
        return a > b;
    }
};
priority_queue<int, vector<int>, cmp > q;
int degree[501];
// 如何保证号小的先被选中?优先队列!
int main()
{
  int n,m,i,a,b;
  while (cin>>n>>m)
  {
      // 初始化
      for (i=1;i<=n;i++)
      {
          vex[i].clear();
          degree[i] = 0;
      }
      // queue 没有 clear()方法,故需要循环pop()数据
      while (!q.empty())
        q.pop();

      for (i = 0;i<m;i++)
      {
          cin >> a >> b;
          vex[a].push_back(b);
          degree[b]++;
      }
      for (i=1;i<=n;i++)
        if (degree[i] == 0)
          q.push(i);
      bool flag = true;
      while (!q.empty())
      {
          a = q.top();// 选取队尾元素
          q.pop();
          if (flag)
          {
              flag = false;
              cout << a;
          }else
          cout << ' ' << a;
          for (i=0;i<vex[a].size();i++)
          {
              b = vex[a][i];
              if ((--degree[b])==0)
                q.push(b);
          }
      }
      cout << endl;
  }
    return 0 ;
}

产生冠军

没调过,已经调了一个小时了,附带上正确答案

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <map>
#include <string>
using namespace std;

vector<int> vex[10000];
int degree[10000];
queue<int> q;
int main()
{
    int i,n,count,a;
    string s1,s2;
    map<string,int> m; // 映射
    map<string,int>::iterator it;
    while (cin>>n && n!=0)
    {
        count =0;
        for (i=0;i<1000;i++)
        {
            vex[i].clear();
            degree[i] = 0;
        }
        for (i=0;i<n;i++)
        {
            // 难点:把名字和数组的i进行映射,不知道具体的队员个数
            cin >>s1>>s2;
            if (m.find(s1) == m.end())
                m.insert(pair<string,int>(s1,count++));
            if (m.count(s2)==0)
                m.insert(pair<string,int>(s2,count++));
            vex[m[s1]].push_back(m[s2]);
            degree[m[s2]]++;
            // 如果插入的字符串已经出现过了则不会执行该条语句的啦!名字和索引的映射成功!
//            for (it = m.begin() ; it!=m.end();it++)
//                cout << (*it).first << " : " << (*it).second << endl;
        }
        // !!!!!!
        m.clear();
        while (!q.empty())
            q.pop();

        for (i=0;i<m.size();i++)
            if (degree[i] == 0)
                q.push(i);
        int cnt = 0;
        while (!q.empty())
        {
            a = q.front();
            q.pop();
            cnt ++;
            for (i=0;i<vex[a].size();i++)
            {
                if ((--degree[vex[a][i]])==0)
                    q.push(vex[a][i]);
            }
        }
        if (cnt == 1)
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
    return 0 ;
}

正确答案:

#include <stdio.h>
#include <string.h>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#include <iostream>
#include <map>
using namespace std;
map<string,int> name;
vector<int> edge[10000];
queue<int> Q;
int indegree[10000];
int main(){
    int n,i,j;
    while(~scanf("%d",&n)&&n){
        for(i=0;i<10000;i++) { edge[i].clear();indegree[i]=0;}
        while(!Q.empty()) Q.pop();
        i=0;
        name.clear();
        for(j=0;j<n;j++){
            string a,b;
            cin>>a>>b;
            if(name.find(a)==name.end()) { name[a]=i++;}
            if(name.find(b)==name.end())  { name[b]=i++;}
            edge[name[a]].push_back(name[b]);
            indegree[name[b]]++;
        }
        int num=i;
        int cnt=0;
        for(i=0;i<num;i++)
            if(indegree[i]==0)    cnt++;
        /*
        int num=i;
        for(i=0;i<n;i++){
            if(indegree[i]==0) Q.push(i);
        }
        int cnt=0;
        while(!Q.empty()){
            int nowp=Q.front();
            Q.pop();
            cnt++;
            for(i=0;i<edge[nowp].size();i++){
                indegree[edge[nowp][i]]--;
                if(indegree[edge[nowp][i]]==0) Q.push(edge[nowp][i]);
            }
        }
        */
        if(cnt==1) puts("Yes");
        else puts("No");
    }
    return 0;
}

最短路径问题

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <map>
#include <string>
#define INFINITY 65535
using namespace std;

struct edge
{
    int nextnode;
    int d;//距离
    int p;//花费
};
vector<edge> vex[1001]; // 邻接链表,vex[i]为一个向量,记录与vex[i]邻接的所有边的信息(邻接点以及权值)
bool S[1001]; // s集合为已经找到最短路径的顶点集合
int dis[1001]; // 距离向量
int cost[1001];//花费向量
int main()
{
    int n,m,d,p,s,t,i,j,k,min,a,b;
    edge e;
    while (cin >> n >> m && n!=0 && m!=0)
    {
        // 1. 初始化邻接链表、距离向量和集合S,现在未知初始顶点和结束顶点是什么
        for (i=1;i<=n;i++)
        {
            vex[i].clear();
            S[i] = false;
            dis[i] = INFINITY; // -1代表不可达
            cost[i] = 0;
        }
        // 2.输入数据
        for (i=0;i<m;i++)
        {
            cin >> a>>b>>d>>p;
            e.d = d;e.p = p;
            e.nextnode = b;
            vex[a].push_back(e);
            e.nextnode = a;
            vex[b].push_back(e);
            // 无向图,加入两条边
        }
        cin >> s >>t ;
        // 确定初始顶点后,初始化集合S和dis
        S[s] = true;dis[s] = 0; // 初始顶点自己到自己为0
        for (i=0;i<vex[s].size();i++)
        {
            int num = vex[s][i].nextnode;
            dis[num] = vex[s][i].d;
            cost[num] = vex[s][i].p;
        }
        // 循环找到最短路径
        for (i=2;i<=n;i++)
        {
            // 找到当前最短路径和顶点
            min = INFINITY;
            for (j=1;j<=n;j++)
            {
                if (!S[j] && min > dis[j] )
                {
                    k = j;
                    min = dis[j];
                }
            }
            S[k] = true;

            for (j = 0;j<vex[k].size();j++)
            {
                int node = vex[k][j].nextnode;
                if (S[node])
                    continue;

                if (min+vex[k][j].d<dis[node] || min+vex[k][j].d == dis[node] && cost[node] > cost[k]+vex[k][j].p)
                {
                    dis[node] = min + vex[k][j].d;
                    cost[node] = cost[k]+vex[k][j].p;
                }
            }

        }
        cout << dis[t]<<" "<< cost[t]<< endl;

    }

    return 0 ;
}

猜你喜欢

转载自blog.csdn.net/qq_33846054/article/details/80736820