ADT图及图的实现及图的应用

图:

图中涉及的定义:
有向图:

顶点之间的相关连接具有方向性;

无向图:

顶点之间相关连接没有方向性;

完全图:

若G是无向图,则顶点数n和边数e满足:0<=e<=n(n-1)/2,当e=n(n-1)/2时,称之为完全无向图;若G是有向图,则0<=e<=n(n-1);当e=n(n-1)时,称之为完全有向图;即此时图中边数最多;

顶点的度:

无向图中定义为关联顶点的边数,有向图中分入度和出度;度D(G)和边数e满足关系:2*e=D(G),即边数的两倍等于图中度数和;

图的实现:
邻接矩阵实现图:
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream> 
#define MAX_SIZE 10005
using namespace std;

int tmp[MAX_SIZE][MAX_SIZE];                         //由于数组可能会比较大,定义在main里面可能会爆,所以先定义在全局; 

typedef struct agraph* Graph;
typedef struct agraph
{
    int n;                                          //顶点数 
    int e;                                          //边数 
    int a[MAX_SIZE][MAX_SIZE];
}Agraph;

Graph GraphInit(int n,Graph G)                      // 对图G的初始化; 
{
    G->n=n;
    G->e=0;
    return G; 
}

void GraphAdd(int i,int j,int val,Graph G)          //插入顶点之间的连接关系; 
{
    G->a[i][j]=val;G->e++;
}
void GraphDelete(int i,int j,Graph G)               //删除顶点之间的连接关系; 
{
    G->a[i][j]=0;G->e--;
}
int OutDegree(int i,Graph G)
{
    int j,sum=0;
    for(j=1;j<G->n;j++)
    {
        if(G->a[i][j]!=0)sum++;
    }
    return sum;
}
int InDegree(int i,Graph G)
{
    int j,sum=0;
    for(j=1;j<=G->n;j++)
    {
        if(G->a[j][i]!=0)sum++; 
    }
    return sum;
}
void GraphOut(Graph G)
{
    int i,j;
    for(i=1;i<=G->n;i++)
    {
        for(j=1;j<=G->n;j++)
            printf("%3d",G->a[i][j]);
        printf("\n");
    }
}
int main()
{
    int i,j,n;
    scanf("%d",&n);
    Graph G=new Agraph;
    G=GraphInit(n,G);
    GraphAdd(1,3,6,G);
    GraphAdd(1,5,55,G);
    GraphAdd(2,3,12,G);
    GraphAdd(2,4,50,G);
    GraphAdd(3,4,3,G);
    GraphAdd(1,2,47,G);
    GraphAdd(3,6,21,G);
    GraphAdd(3,2,31,G);
    GraphAdd(4,1,19,G);
    printf("%d %d\n",InDegree(2,G),OutDegree(2,G));
    printf("\n\n\n");
    printf("邻接矩阵中图G的关系输出:\n");
    GraphOut(G);
    return 0;
}

输出截图:
1093650-20180110123508004-2127861617.png

邻接表实现图:
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;

typedef struct anode* Node;                                     //可以看成是建立一条边; 
typedef struct anode
{
    int v;                                                      //边的另一个顶点;
    int val;                                                    //边权; 
    Node next;                                                  //邻接表指针; 
}Anode;

typedef struct agraph* Graph;
typedef struct agraph
{
    int n;
    int edges;
    Node *nodes;
}Agraph;

Node NewNext(int v,int val,Node next)                           //新建一个节点建立边的连接关系; 
{
    Node x=new Anode;
    x->v=v;
    x->val=val;
    x->next=next;
    return x;
}

Graph GraphInit(int  n,Graph G)                                 //对图G的初始化; 
{
    int i;
    G->n=n;
    G->edges=0;
    G->nodes=(Node*)malloc((n+1)*sizeof(Node));
    for(i=0;i<=n;i++)
    {
        G->nodes[i]=0;
    } 
    return G;
}
void GraphAdd(int i,int j,int val,Graph G)
{
    G->nodes[i]=NewNext(j,val,G->nodes[i]);                     //相当于i的邻接顶点不断增加时,其邻接表从表头加入,先加入的关系不断后移; 
    G->edges++;
}
void GraphDelete(int i,int j,Graph G)
{
    Node p,q;
    p=G->nodes[i];
    if(p->v==j)
    {
        G->nodes[i]=p->next;
        free(p);
    }
    else
    {
        while(p&&p->next->v!=j)p=p->next;
        if(p)
        {
            q=p->next;
            p->next=q->next;
            free(q);
        }
    }
    G->edges--;
}
int InDegree(int i,Graph G)
{
    int j,sum=0;
    for(j=1;j<=G->n;j++)
    {
        /*判断当前有向图G中的边(i,j)是否存在*/
        Node p=G->nodes[i];
        while(p&&p->v!=j)p=p->next;
        if(p)sum++; 
    }
    return sum;
}
int OutDegree(int i,Graph G)
{
    Node p=G->nodes[i];
    int sum=0;
    while(p)
    {
        sum++;
        p=p->next;
    }
    return sum;
}
void GraphOut(Graph G)
{
    Node p;
    int i;
    for(i=1;i<=G->n;i++)
    {
        if(G->nodes[i])printf("%d的邻接表:",i);
        p=G->nodes[i];
        while(p)
        {
            printf("%3d",p->v);
            p=p->next;
        }
        printf("\n");
    }
}
int main()
{
    int i,j,n;
    scanf("%d",&n);
    Graph G=new Agraph;
    G=GraphInit(n,G);
    GraphAdd(1,3,6,G);
    GraphAdd(1,5,55,G);
    GraphAdd(2,3,12,G);
    GraphAdd(2,4,50,G);
    GraphAdd(3,4,3,G);
    GraphAdd(1,2,47,G);
    GraphAdd(3,6,21,G);
    GraphAdd(3,2,31,G);
    GraphAdd(4,1,19,G);
    printf("%d %d\n",InDegree(2,G),OutDegree(2,G));
    printf("\n\n\n");
    printf("邻接表中图G的关系图输出:\n");
    GraphOut(G);
    return 0;
}

输出截图:
1093650-20180110123946176-734867427.png

图的遍历:

图的遍历一般有两种方法:
1、广度优先搜索;2、深度优先搜索;

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream> 
#include<queue>
#define MAX_SIZE 10005
using namespace std;
int vis[10005]={0};
int cnt;

int tmp[MAX_SIZE][MAX_SIZE];                         //由于数组可能会比较大,定义在main里面可能会爆,所以先定义在全局; 

typedef struct agraph* Graph;
typedef struct agraph
{
    int n;                                          //顶点数 
    int e;                                          //边数 
    int a[MAX_SIZE][MAX_SIZE];
}Agraph;

Graph GraphInit(int n,Graph G)                      // 对图G的初始化; 
{
    G->n=n;
    G->e=0;
    return G; 
}

void GraphAdd(int i,int j,int val,Graph G)          //插入顶点之间的连接关系; 
{
    G->a[i][j]=val;G->e++;
}
void GraphDelete(int i,int j,Graph G)               //删除顶点之间的连接关系; 
{
    G->a[i][j]=0;G->e--;
}
int OutDegree(int i,Graph G)
{
    int j,sum=0;
    for(j=1;j<G->n;j++)
    {
        if(G->a[i][j]!=0)sum++;
    }
    return sum;
}
int InDegree(int i,Graph G)
{
    int j,sum=0;
    for(j=1;j<=G->n;j++)
    {
        if(G->a[j][i]!=0)sum++; 
    }
    return sum;
}
void GraphOut(Graph G)
{
    int i,j;
    for(i=1;i<=G->n;i++)
    {
        for(j=1;j<=G->n;j++)
            printf("%3d",G->a[i][j]);
        printf("\n");
    }
}

/*图的遍历:广度优先搜索*/
void bfs(Graph G,int i)
{
    int j;
    queue<int>q;
    q.push(i);                                                      //从i顶点开始当前的搜索,即用顶点i初始化队列q; 
    vis[i]=cnt++;                                                   //vis数组储存的是编号为j的这个顶点被访问时的序号;即第几个被访问到的顶点; 
    while(!q.empty())
    {
        i=q.front();
        q.pop();
        for(j=1;j<=G->n;j++)                                        //即对每次的i的相邻的未被访问过的顶点进行访问(符合广度优先搜索的由近到远搜索) 
        {
            if((G->a[i][j])&&(vis[j]==0))
            {
                q.push(j);
                vis[j]=cnt++;
            }
        }
    }
}
void GraphSearchByBfs(Graph G)
{
    int i;
    cnt=1;
    for(i=1;i<=G->n;i++)
    {
        if(vis[i]==0)
            bfs(G,i);                                       //n个顶点中可能存在多个联通块,对其做一次循环之后可以保证将图中的所有的顶点都访问到; 
    }
}
//上述邻接矩阵实现的图的广度优先搜索遍历因为在搜索时需要确保对所有的顶点都访问,因此所需要的时间为O(n*n); 

/*图的遍历:深度优先搜索*/
void dfs(Graph G,int i)                                     //邻接矩阵实现的图的深搜 
{
    int j;
    vis[i]=cnt++;
    for(j=1;j<=G->n;j++)
    {
        if(G->a[i][j])
        {
            if(vis[j]==0)dfs(G,j);                          //即递归走到尽头才停止这一次的搜索; 
        }
    }
}
/*这段邻接表深搜函数函数适用的是邻接表实现的图,在此无效*/
//void dfs2(Graph G,int i)
//{
//  Node p;
//  vis[i]=cnt++;
//  for(p=G->nodes[i];p;p=p->next)
//  {
//      if(vis[p->v]==0)
//          dfs(G,p->v);
//  }
//}
void GraphSearchByDfs(Graph G)
{
    int i;
    cnt=1;
    for(i=1;i<=G->n;i++)
    {
        if(vis[i]==0)
            dfs(G,i);                                       //n个顶点中可能存在多个联通分支,对其做一次循环之后可以保证将图中的所有的路径都访问到; 
    }
}

int main()
{
    int i,j,n;
    scanf("%d",&n);
    Graph G=new Agraph;
    G=GraphInit(n,G);
    GraphAdd(1,3,6,G);
    GraphAdd(1,5,55,G);
    GraphAdd(2,3,12,G);
    GraphAdd(2,4,50,G);
    GraphAdd(3,4,3,G);
    GraphAdd(1,2,47,G);
    GraphAdd(3,6,21,G);
    GraphAdd(3,2,31,G);
    GraphAdd(4,1,19,G);
    GraphAdd(7,3,12,G);
    GraphAdd(2,9,1,G);
    GraphAdd(3,8,47,G);
    GraphAdd(7,9,99,G);
    GraphAdd(7,10,11,G);
    GraphAdd(9,8,23,G);
    printf("%d %d\n",InDegree(2,G),OutDegree(2,G));
    printf("\n\n\n");
    printf("邻接表中图G的关系图输出:\n\n");
    GraphOut(G);
    
//  printf("\n\n");
//  printf("对图进行遍历,广度优先搜索输出:\n\n");
//  GraphSearchByBfs(G);
//  for(i=1;i<=n;i++)
//      printf("%d是第%d个遍历到的顶点\n\n",i,vis[i]);
        
    printf("\n\n");
    printf("对图进行遍历,深度优先搜索输出:\n\n");
    GraphSearchByDfs(G);
    for(i=1;i<=n;i++)
        printf("%d是第%d个遍历到的顶点\n\n",i,vis[i]);
        
    return 0;
}
1、广度优先搜索:

基本思想:从图中的某个顶点i出发,在访问了顶点i之后,接着就尽可能的先横向搜索i的邻接顶点,在一次访问过i的各个未被访问过的邻接顶点之后,分别从这些邻接顶点出发,递归以广度优先方式搜索图中的其他顶点,直至图中的所有顶点都被访问到为止;即以i为起始顶点,由近到远(即路径长度为1,2,3,.....)依次访问和顶点i有路相通的顶点;

输出结果:
1093650-20180110150207472-1019353714.png

2、深度优先搜索:

从i开始搜索,标记i为已访问再递归的用深度优先搜索依次搜索i的每一个未被访问的邻接顶点,如果从i出发有路可达的顶点都已被访问过,则从i开始的搜索过程结束;此时,如果图中海油未被访问的节点,则再任选一个并从该节点开始做新的搜索;直至图中所有的节点都被访问过为止;

输出结果:
1093650-20180110154533160-1973098595.png

图的实际问题应用:

最短路径问题:1、单源最短路径;2、所有顶点对之间的最短路径;

单源最短路径(Dijkstra算法/Bellman-Ford算法):
Dijktra(不能存在负权边):

V是赋权有向图,设置一个集合S并不断作贪心算法扩充该集合,其中的顶点当且仅当源到该顶点的最短路径已知;用dist[]数组记录当前每个顶点所对应的最短特殊路径长度(特殊路径:从源到顶点u且中间只经过S中顶点的路径称为从源到u的特殊路径)。该算法每次从V-S中取出具有最短路径长度的顶点u,将其添加到S中,同时对数组dist作必要的修改,当S中包含了V中所有顶点,dist就记录了从源到其他所有顶点的最短路径长度;

Bellman-Ford:

对图中除源顶点s外的任一顶点u,依次构造
从s到u的最短路径长度序列dist1[u],dist2[u],...,dist n-1[u].其中dist i[u]表示从s到u的最多经过i条边的最短路径长度;若G中没有从源可达的负权圈,则从s到u的最多有n-1条边;因此,dist n-1[u]就是从s到u的最短路径长度;

实现时:
用ub[]数组记录顶点的最短路径长度更细状态,count[]数组记录顶点的最短路径长度更新次数,队列que[]存储最短路径更新过的顶点,算法依次从que中取出待更新的顶点u,按照计算dist i[u]的递归式进行计算,一旦发现存在顶点k有count[k]>n,说明图G中有一个从k出发的负权圈,算法终止,否则,当队列que为空时,算法得到图G的各顶点的最短路径长度;

具体题目应用代码:后续详细复习补上;

所有顶点对之间的最短路径(floyd算法):
Dijsktra * n:

每次以一个顶点为源,重复执行Dijkstra算法n次;时间复杂度为O(n* n *n);

Floyd:

设置一个n*n的矩阵c,初始时c[i][j]=a[i][j];a是权值数组;在矩阵c上做n次迭代,经k次迭代后,c[i][j]值是从顶点i到顶点j且中间不经过编号大于k的顶点的最短路径长度。做迭代时,公式:c[i][j] = min{ a[i][j] , c[i][k]+a[k][j] };

第十一次作业:

1、(给定一个图,求图中所有顶点对之间的最短路径长度,用Floyd算法实现)

 
            #include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
#define MAX 405
using namespace std;
int pre[MAX][MAX],dist[MAX][MAX];
void Floyd(int n,int gra[][MAX])
{
    int i,j,k;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            dist[i][j]=gra[i][j];pre[i][j]=j;
        }
    }
    for(k=1;k<=n;k++)
    {
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                if(dist[i][k]!=INF&&dist[k][j]!=INF&&dist[i][k]+dist[k][j]<dist[i][j])
                {
                    dist[i][j]=dist[i][k]+dist[k][j];
                    pre[i][j]=pre[i][k];
                }
            }
        }
    }
}
int gra[MAX][MAX];

int main()
{
    int n,m,i,j,k;
    int u,v,val,tmp,que;
    scanf("%d%d",&n,&m);
    for(i=0;i<=n;i++)
    {
        for(j=0;j<=n;j++)
        {
            gra[i][j]=(i==j?0:INF);
        }
    }
    for(i=0;i<m;i++)
    {
        scanf("%d%d%d",&u,&v,&val);
        gra[u][v]=gra[v][u]=val;
    }
    Floyd(n,gra);
    scanf("%d",&que);
    for(i=0;i<que;i++)
    {
        scanf("%d%d",&u,&v);
        if(dist[u][v]!=INF)
            printf("%d\n",dist[u][v]); 
        else printf("-1\n");
    }
    return 0;
}

2、(判断图中是否存在负权圈--深搜做法:但是要会开类似三维数组vector<pair<int,int>>vv[2005];)

#include<stdio.h> 
#include<vector>
#include<memory.h>
using namespace std;
bool vis[2005];
vector<pair<int,int> >vv[2005];
int n,m,s;
bool flag=false;
int dis[2005];
void dfs(int pos)
{
    if(flag) return;
    vis[pos]=true;
    vector<pair<int,int> >::iterator it;
    for(it=vv[pos].begin();it!=vv[pos].end();it++)
    {   int to=it->first;
        if(dis[to]>dis[pos]+it->second)
        {
            dis[to]=dis[pos]+it->second;
        
        if(vis[to]) {
        flag=true;return;}
        else 
        {
            dfs(to);
        }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    int  i,j;
    memset(vis,false,sizeof(vis));
    memset(dis,0,sizeof(dis));
    for(i=1;i<=n;i++) vv[i].clear();
    for(i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        vv[u].push_back(make_pair(v,w));
    }
    scanf("%d",&s);
    dis[s]=0;
    dfs(s);
    if(flag) printf("EL PSY CONGROO");
    else printf("ttt");
}

转载于:https://www.cnblogs.com/heihuifei/p/8260164.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2. 系统设计 1.用到的抽象数据类型的定义 的抽象数据类型定义: ADT Graph{ 数据对象V:V是具有相同特性的数据元素的集合,称为顶点集 数据关系R: R={VR} VR={<v,w>|v,w∈V且P(v,w),<v,w>表示从v到w的弧, 谓词P(v,w)定义了弧<v,w>的意义或信息} 基本操作P: CreatGraph(&G,V,VR) 初始条件:V是的顶点集,VR是中弧的集合 操作结果:按V和VR的定义构造G DestroyGraph(&G) 初始条件:G存在 操作结果:销毁G InsertVex(&G,v) 初始条件:G存在,v和中顶点有相同特征 操作结果:在G中增添新顶点v …… InsertArc(&G,v,w) 初始条件:G存在,v和w是G中两个顶点 操作结果:在G中增添弧<v,w>,若G是无向的则还增添对称弧<w,v> …… DFSTraverse(G,Visit()) 初始条件:G存在,Visit是顶点的应用函数 操作结果:对进行深度优先遍历,在遍历过程中对每个顶点调用函数Visit一次且仅一次。一旦Visit()失败,则操作失败 BFSTraverse(G,Visit()) 初始条件:G存在,Visit是顶点的应用函数 操作结果:对进行广度优先遍历,在遍历过程中对每个顶点调用函数Visit一次且仅一次。一旦Visit()失败,则操作失败 }ADT Graph 栈的抽象数据类型定义: ADT Stack{ 数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 数据关系:R1={<ai-1,ai>|ai-1,ai∈D,i=2,…,n} 约定an端为栈顶,ai端为栈底 基本操作: InitStack(&S) 操作结果:构造一个空栈S DestroyStack(&S) 初始条件:栈S已存在 操作结果:将S清为空栈 StackEmpty(S) 初始条件:栈S已存在 操作结果:若栈S为空栈,则返回TRUE,否则FALSE …… Push(&S,e) 初始条件:栈S已存在 操作结果:插入元素e为新的栈顶元素 Pop(&S,&e) 初始条件:栈S已存在且非空 操作结果:删除S的栈顶元素,并用e返回其值 StackTraverse(S,visit()) 初始条件:栈S已存在且非空 操作结果:从栈底到栈顶依次对S的每个数据元素调用函数visit(),一旦visit()失败,则操作失效 }ADT Stack 队列的抽象数据类型定义: ADT Queue{ 数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 数据关系:Rl={<ai-1,ai>|ai-1,ai∈D,i=2,…,n} 约定其中ai端为队列头,an端为队列尾。 基本操作: InitQueue(&Q) 操作结果:构造一个空队列Q DestroyQueue(&Q) 初始条件:队列Q已存在 操作结果:队列Q被销毁,不再存在 QueueEmpty(Q) 初始条件:队列Q已存在 操作结果:若Q为空队列,则返回TRUE,否则FALSE …… EnQueue(&Q,e) 初始条件:队列Q已存在 操作结果:插入元素e为Q的新的队尾元素 DeQueue(&Q,&e) 初始条件:Q为非空队列 操作结果:删除Q的队头元素,并用e返回其值 }ADT Queue 2.主程序的流程: 调用CreateDN函数创建的邻接表G; 调用PrintDN函数输出邻接表G; 调用DFSTraverse函数深度优先遍历; 调用BFSTraverse函数广度优先遍历
循环队列是一种特殊的队列,它的队尾指针指向最后一个元素的下一个位置,队头指针指向队列中第一个元素。当队列满时,队尾指针和队头指针相等,但此时队列不是空的。 循环队列的实现可以使用数组来存储元素,定义一个结构体来表示队列,其中包含队列的头指针、尾指针和数组元素。下面是循环队列ADT实现: ``` typedef struct { int *base; // 队列的存储空间 int front; // 队头指针 int rear; // 队尾指针 int size; // 队列的最大长度 } Queue; void InitQueue(Queue *q, int size) { q->base = (int *)malloc(size * sizeof(int)); q->front = q->rear = 0; q->size = size; } void EnQueue(Queue *q, int x) { if ((q->rear + 1) % q->size == q->front) { printf("Queue is full.\n"); return; } q->base[q->rear] = x; q->rear = (q->rear + 1) % q->size; } int DeQueue(Queue *q) { if (q->front == q->rear) { printf("Queue is empty.\n"); return -1; } int x = q->base[q->front]; q->front = (q->front + 1) % q->size; return x; } int GetQueueLength(Queue *q) { return (q->rear - q->front + q->size) % q->size; } ``` 使用循环队列可以方便地实现一些算法,如广度优先搜索(BFS)和滑动窗口等。下面是一个使用循环队列实现滑动窗口的例子: ``` void SlidingWindow(int *a, int n, int k) { Queue q; InitQueue(&q, k); int i; for (i = 0; i < k; i++) { while (GetQueueLength(&q) > 0 && a[i] >= a[q.base[q.rear-1]]) { DeQueue(&q); } EnQueue(&q, i); } for (; i < n; i++) { printf("%d ", a[q.base[q.front]]); while (GetQueueLength(&q) > 0 && a[i] >= a[q.base[q.rear-1]]) { DeQueue(&q); } while (GetQueueLength(&q) > 0 && q.base[q.front] <= i - k) { DeQueue(&q); } EnQueue(&q, i); } printf("%d\n", a[q.base[q.front]]); } ``` 该函数可以实现在一个长度为n的数组a中,找出每个长度为k的子数组中的最大值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值