图-邻接矩阵转邻接表+DFS递归非递归+BFS非递归

#include<iostream>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
const int num = 10;
vector<int> visited(num); //DFS中保存已经被访问过的节点


//结点结构类型
struct ANode {
    int adjvex;     //
    ANode* nextarc; //指向表头结点下一条邻接的边
    int info;       //该边的相关信息,对于带权图可存放权值
};
//图的邻接表类型
struct AGraph {
    vector<ANode*> adjlist; //所有表头组成的数组
    int n, e; //结点个数,边条数
};


//------生成图的邻接表算法------
void createAdj(AGraph* &G, vector<vector<int> > A, int n) {//n个节点
    //由数组A生成邻接表
    ANode *p;
    G = new AGraph();
    G->n = n;
    G->e = 0;
    for (int i = 0; i < n; i++) {//初始化邻接表头结点
        ANode *anode = new ANode();
        anode->nextarc = NULL;
        G->adjlist.push_back(anode);
    }
    for (int j = 0; j < n; j++) {
        for (int k = n - 1; k >= 0; k--) {//从一行的后往前
            if (A[j][k] != 0) {
                p = new ANode();
                p->adjvex = k;
                p->nextarc = G->adjlist[j]->nextarc;//插入到链表头
                G->adjlist[j]->nextarc = p;
                G->e++;
            }
        }
    }
}

//------输出图的邻接表算法------
void dispAdj(AGraph *G) {
    ANode* p;
    for (int i = 0; i < G->n; i++) {//头结点,从0开始,n个节点
        cout << i << "->";//头结点
        p = G->adjlist[i]->nextarc;//边节点
        while (p) {
            cout << p->adjvex << "->";//边指向的值
            p = p->nextarc;
        }
        cout << "NULL" << endl;
    }
}

//深度优先遍历---递归
void DFS(AGraph *G, int v) {
    //vector<int> visited(G->n);//递归 所以这里错误
    cout << v << " ";
    visited[v] = 1;//标记该值被访问过
    ANode *p = G->adjlist[v]->nextarc;
    while (p) {
        if (!visited[p->adjvex])
            DFS(G, p->adjvex);
        p = p->nextarc;//同层
    }
}
//深度优先遍历---非递归
void DFStranverse(AGraph *G, int v)
{
    ANode *curr;
    stack<int> s;
    cout << v << " ";
    visited[v] = 1;
    s.push(v);//不需要压入节点指针。因为每次都需要从头结点找下一个而非在pre所在链表找同层节点
    while (!s.empty())
    {    
        curr = G->adjlist[s.top()]->nextarc;//找到头结点G->adjlist[pre->adjvex]继续访问,而非pre
        while (curr)
        {
            //找pre邻接节点中没有访问过的
            if (!visited[curr->adjvex])
            {
                cout << curr->adjvex << " ";
                visited[curr->adjvex] = 1;
                s.push(curr->adjvex);
                curr = G->adjlist[s.top()]->nextarc;//深度
            }
            else
            {
                curr = curr->nextarc;
            }
        }
        s.pop();//同层都被访问过
    }
}

//广度优先遍历---非递归
void BFS(AGraph* G, int v) {
    ANode *curr;
    queue<int> qu;
    vector<int> visited(G->n);//n个元素的flag用于标明该值是否被访问
    int w;
    cout << v << " ";
    visited[v] = 1;
    qu.push(v);
    while (!qu.empty()) {
        w = qu.front();
        qu.pop();
        curr = G->adjlist[w]->nextarc;//这里不可使用front换掉w,因为front已经不同
        while (curr) {
            if (!visited[curr->adjvex]) {//未被访问
                cout << curr->adjvex << " ";
                visited[curr->adjvex] = 1;
                qu.push(curr->adjvex);//访问后进队,此while访问完同层后,才pop该值,再访问该值邻接的元素
            }
            curr = curr->nextarc;//同层
        }
    }
}

//测试用例 
int main() {
    //有向图
    /*int a[] = { 0,1,0,1,0 };
    int b[] = { 0,0,1,1,0 };
    int c[] = { 0,0,0,1,1 };
    int d[] = { 0,0,0,0,0 };
    int e[] = { 1,0,0,1,0 };*/

    //无向图
    int a[] = { 0,1,0,1,1 };
    int b[] = { 1,0,1,1,0 };
    int c[] = { 0,1,0,1,1 };
    int d[] = { 1,1,1,0,1 };
    int e[] = { 1,0,1,1,0 };

    vector<vector<int> > graph;
    vector<int> A(a, a + 5);//初始化
    vector<int> B(b, b + 5);
    vector<int> C(c, c + 5);
    vector<int> D(d, d + 5);
    vector<int> E(e, e + 5);

    graph.push_back(A);
    graph.push_back(B);
    graph.push_back(C);
    graph.push_back(D);
    graph.push_back(E);

    AGraph* G;
    int n = 5;//节点数量
    createAdj(G, graph, n);//构造图的邻接表
    cout << "输出图的邻接表:" << endl;
    dispAdj(G);
    cout << endl;
    
    cout << "DFS递归 从0深度优先遍历:" << endl;
    //DFS(G, 0);
    cout << endl;

    cout << "DFS非递归 从0深度优先遍历:" << endl;
    DFStranverse(G, 0);
    cout << endl;

    cout << "BFS非递归 从0广度优先遍历" << endl;
    BFS(G, 0);
    return 0;
}

 

图-最短路径-dijkstra、bellmanford、spfa、floyd

/*
最短路径 
https://blog.csdn.net/YF_Li123/article/details/74090301 dijkstra算法
https://blog.csdn.net/YF_Li123/article/details/74090409 dijkstra笔试题只需要修改更新d[k]处
http://www.voidcn.com/article/p-pwheyixu-te.html 优化
*/
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;

/*一、dijkstra迪杰斯特拉算法
对比 最小生成树的prim算法
*/

const int INF = 1000000000;
/*
1、邻接矩阵 求s到图G中其他顶点的最短路径
d是起点s到图中每个顶点的 最短距离
pre是s到每一节点的前驱节点,用于后续输出s到某个节点的 最短路径
*/
void Dijkstra(int n, int s, vector<vector<int> > G, vector<bool>& vis, vector<int>& d, vector<int> &pre)
{
    for (int i = 0; i < n; i++)//pre用于后续输出s到其他顶点的最短路径
        pre[i] = i;

    //vector<bool> vis(n, false);
    //vector<int> d(n);
    fill(d.begin(), d.end(), INF);//s与其余节点距离无限大
    d[s] = 0;
    for (int i = 0; i < n; i++)//操作节点个数n次
    {
        int u = -1;//与当前节点最小距离的未访问节点
        int mindistance = INF;
        for (int j = 0; j < n; j++)
        {
            if (vis[j]==false && d[j] < mindistance)
            {
                mindistance = d[j];
                u = j;
            }
        }
        if (u == -1)//不连通
            return ;
        vis[u] = true;
        //以中介点u更新d。d为s到图中每个顶点的最短距离
        for (int k = 0; k < n; k++)//遍历u可到达的未访问节点 以u为中介可以让s到k的距离d[k]更优 则更新d[k]【对比最小生成树的prim算法
        {
            if (!vis[k] && G[u][k] + d[u] < d[k])
            {
                d[k] = G[u][k] + d[u];
                pre[k] = u;//pre记录u->k 用于后续输出最短路径
            }
        }
    }
}

/*
2、邻接表
*/
struct Node
{
    int v;//连接的节点值
    int dis;//权值
    Node(int x, int y) :v(x), dis(y) {}
};
void Dijkstra1(int n, int s, vector<vector<Node> > G, vector<bool>& vis, vector<int>& d, vector<int> &pre)
{
    for (int i = 0; i < n; i++)//pre用于后续输出s到其他顶点的最短路径
        pre[i] = i;

    //vector<bool> vis(n, false);
    //vector<int> d(n);
    fill(d.begin(), d.end(), INF);//s与其余节点距离无限大
    d[s] = 0;
    for (int i = 0; i < n; i++)//操作节点个数n次
    {
        int u = -1;//与当前节点最小距离的未访问节点
        int mindistance = INF;
        for (int j = 0; j < n; j++)
        {
            if (vis[j] == false && d[j] < mindistance)
            {
                mindistance = d[j];
                u = j;
            }
        }
        if (u == -1)//不连通
            return ;
        vis[u] = true;
        //上面和Dijkstra相同
        for (int k = 0; k < G[u].size(); k++)//更新u与其余可连接的未访问节点(共G[u].size()个)的距离。实际是用原距离更新INF
        {
            int tmp = G[u][k].v;//未访问节点的值
            if (!vis[tmp] && G[u][k].dis + d[u] < d[tmp])
            {
                d[tmp] = G[u][k].dis + d[u];
                pre[tmp] = u;
            }
        }
    }
}

/*
优化最短路径迪杰斯特拉:堆排序的priority_queue存储最短距离、目标节点编号,即不用遍历所有节点找最短路径(第二个for
原On^2 优化OElogn
(更新最短距离只需访问每条可到达的边OE,【查找下一个可使用最短目标节点Ologn(原方法需要枚举所有节点比较)】)

*/
void Dijkstra_good(int n, int s, vector<vector<int> > G, vector<bool>& vis, vector<int>& d, vector<int> &pre)
{
    fill(d.begin(), d.end(), INF);
    d[s] = 0;
    for (int i = 0; i < n; i++)
        pre[i] = i;
    priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;//最小堆
    pq.push(make_pair(0, s));
    while (!pq.empty())
    {
        pair<int, int> toppair = pq.top();
        pq.pop();
        int mindistance = toppair.first;//当前节点能到达的最短距离
        int tonode = toppair.second;//当前节点能到达的最短距离的目标节点编号
        if (vis[tonode])
            continue;//当前最小距离节点已经访问过
        vis[tonode] = true;
        
        for (int i = 0; i < G[tonode].size(); i++)//以目标节点为中介 更新s到其余节点的最短距离
        {
            if (!vis[i] && mindistance + G[tonode][i] < d[i])
            {
                d[i] = mindistance + G[tonode][i];
                pre[i] = tonode;
                pq.push(make_pair(d[i], i));//把s经过中介tonode到可到达节点i的最短距离、目标节点编号加入优先级队列(自己会排序
            }
        }
    }
}

/*
bellmanford有负权  外层1到n-1次循环,每次松弛所有边,最后再循环所有边判断是否有负边权
*/
struct Edge
{
    int u, v;
    int weight;
    Edge(int x, int y, int z) :u(x), v(y), weight(z) {}
};
/*返回是否有最短路径,即有负边权则false*/
bool bellmanford(int n, int m, int s, vector<Edge> &E, vector<int> &d, vector<int> &pre)
{
    fill(d.begin(), d.end(), INF);
    d[s] = 0;
    for (int i = 0; i < n; i++)
        pre[i] = i;
    for (int i = 1; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (d[E[j].v] > d[E[j].u] + E[j].weight)//s->v的距离 > s->u->v的距离,则更新
            {
                d[E[j].v] = d[E[j].u] + E[j].weight;
                pre[E[j].v] = E[j].u;
            }
        }
    }
    for (int j = 0; j < m; j++)//最后判定是否有负边权。没收敛
    {
        if (d[E[j].v] > d[E[j].u] + E[j].weight)
        {
            cout << "xxxxxxxxxxxxxx" << endl;
            return false;
        }
    }
    return true;
}

/*
3、SPFA 队列优化bellmanford 单源 负边权
*/
bool SPFA(int n, int s, vector<vector<int> > G, vector<int> &d, vector<bool> &vis, vector<int> &pre)
{
    fill(d.begin(), d.end(), INF);
    d[s] = 0;
    for (int i = 0; i < n; i++)
        pre[i] = i;
    vector<int> flag(n, 0);//记录每个节点入队次数,如果n次,说明有负环路
    queue<int> q;//队列优化
    q.push(s);
    while (!q.empty())
    {
        int curr = q.front();
        q.pop();//从队列删除
        vis[curr] = false;//curr不在当前队列
        for (int i = 0; i < n; i++)//循环n-1次
        {
            if (G[curr][i] != INF && d[i] > d[curr] + G[curr][i])
            {
                d[i] = d[curr] + G[curr][i];
                if (!vis[i])
                {                    
                    vis[i] = true;
                    flag[i]++;
                    if (flag[i] >= n)//进队n次 有负环路
                        return false;
                    q.push(i);
                }
                pre[i] = curr;
            }
        }
    }
    return true;
}


/*输出s到v的最短路径*/
void Printresult(int s, int v,vector<int> pre)
{
    if (s == v)
    {
        cout << s << " ";
        return;
    }
    Printresult(s, pre[v], pre);//s到v的前一节点路径
    cout << v << " ";
}

/*floyd多源最短路径*/
void floyd(int n, vector<vector<int> > &e, vector<vector<int> > &path)
{
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                if (e[i][j] > e[i][k] + e[k][j])
                {
                    e[i][j] = e[i][k] + e[k][j];
                    path[i][j] = path[i][k];
                }
}

int main()
{
    //1、dijkstra算法
    int n = 6;
    vector<vector<int>> G = { {0,1,INF,4,4,INF},
                              {INF,0,INF,2,INF,INF},
                              {INF,INF,0,INF,INF,1},
                              {INF,INF,2,0,3,INF},
                              {INF,INF,INF,INF,0,3},
                              {INF,INF,INF,INF,5,0} };//有向图
    
    vector<vector<Node>> Adj = {
    {Node(1,1),Node(3,4),Node(4,4)},
    {Node(3,2)},
    {Node(5,1)},
    {Node(2,2),Node(3,4)},
    {Node(3,5)},
    {Node(4,5)}};

    vector<bool> vis(n,false);
    vector<int> d(n);//保存起始节点s到图中每个节点的距离
    vector<int> pre(n);
    //Dijkstra(n, 0, G, vis, d, pre);//邻接矩阵
    //Dijkstra1(n, 0, Adj, vis, d, pre);//邻接表
    //Dijkstra_good(n, 0, G, vis, d, pre);//priority_queue堆排序优化原On^2到OElogn  E是边n是节点
    
    //2、bellmanford 可判断负边权
    vector<Edge> E = { Edge(0,1,1),Edge(0,3,4),Edge(0,4,4),Edge(1,3,2),Edge(2,5,1),
                 Edge(3,2,2),Edge(3,4,3),Edge(4,5,3),Edge(5,4,5)};//没有负环路
    int b_n = 6;
    int b_m = 9;
    //bool nonegative = bellmanford(b_n, b_m, 0, E, d, pre);
    //vector<Edge> test = { Edge(0,1,5),Edge(1,2,3),Edge(2,0,-10)};//有负环路
    //bool nonegative = bellmanford(3, 3, 0, test, d, pre);    
    
    //3、SPFA 队列优化bellmanford
    vector<vector<int>> G1 = { {0,5,INF},
                              {INF,0,3},
                              {-10,0,INF}};
    //bool nonegative=SPFA(3, 0, G1, d, vis, pre);//负环路
    //bool nonegative = SPFA(n, 0, G, d, vis, pre);//无负环路
    //cout << "有最短路径,无负环路:" << nonegative << endl;
    
    /*cout << "起始点0到其余节点的最短距离:" << endl;
    for (auto x : d)
        cout << x << " ";
    cout << endl;

    //输出从起点s到顶点v的最短路径
    cout << "从起点s到顶点v的最短路径:" << endl;*/
    //Printresult(0, 5, pre);
    
    //4、floyd多源 动态规划
    vector<vector<int> > path(G.size(),vector<int>(G[0].size()));//初始化后用于输出具体路径的二维数组path
    for (int i = 0; i < G.size(); i++)
        for (int j = 0; j < G[0].size(); j++)
            path[i][j] = j;

    floyd(n, G, path);//会修改G存储多源最短距离
    for (int i = 0; i < n; ++i)//每个顶点到其他顶点最短距离
    {
        for (int j = 0; j < n; ++j)
        {
            if (G[i][j] != INF)//如果存在路径
            {
                //判断负环是否存在只需检查是否存在d[i][i]是负数的顶点i
                cout << "dis[" << i << "][" << j << "] = " << G[i][j];//最短距离

                //最短路径
                cout << "  路径:";
                cout << i << " ";
                int temp = path[i][j];
                while (temp != j)
                {
                    cout << temp << " "; //经过节点
                    temp = path[temp][j];
                }
                cout << j << " " << endl;
            }
            else
                cout << i << " cant to " << j << endl;
        }
    }
    
    return 0;
}

 

图-最小生成树prim+kruskal

/*
https://blog.csdn.net/YF_Li123/article/details/75148998
https://blog.csdn.net/YF_Li123/article/details/75195549
*/
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

/*一、prim普里姆算法*/
const int INF = 1000000000;//除了d[s]=0,s和其余节点初始距离无限大

/*
1、邻接矩阵实现最小生成树
n节点个数,s初始节点,G图的邻接矩阵,vis已访问节点,d其余未访问节点与集合S的最短距离(其余未访问节点与当前节点u的最短距离
返回最小权重和
*/
int Prim(int n, int s, vector<vector<int> > G)
{
    vector<bool> vis(n, false);
    vector<int> d(n);
    fill(d.begin(), d.end(), INF);//s与其余节点距离无限大
    d[s] = 0;
    int result = 0;//最小权重和 返回
    for (int i = 0; i < n; i++)//操作节点个数n次
    {
        int u = -1;//与当前节点最小距离的未访问节点
        int mindistance = INF;
        for (int j = 0; j < n; j++)
        {
            if (vis[j]==false && d[j] < mindistance)
            {
                mindistance = d[j];
                u = j;
            }
        }
        if (u == -1)//不连通
            return -1;
        vis[u] = true;
        result += d[u];
        for (int k = 0; k < n; k++)//更新u与其余可连接的未访问节点的距离。实际是用原距离更新INF
        {
            if (!vis[k] && G[u][k] != INF && G[u][k] < d[k])
                d[k] = G[u][k];
        }
    }
    return result;
}

/*
2、邻接表实现最小生成树
n节点个数,s初始节点,G图的邻接表,vis已访问节点,d其余未访问节点与集合S的最短距离(其余未访问节点与当前节点u的最短距离
返回最小权重和
*/
struct Node
{
    int v;//连接的节点值
    int dis;//权值
    Node(int x, int y) :v(x), dis(y) {}
};
int Prim1(int n, int s, vector<vector<Node> > G)
{
    vector<bool> vis(n, false);
    vector<int> d(n);
    fill(d.begin(), d.end(), INF);//s与其余节点距离无限大
    d[s] = 0;
    int result = 0;//最小权重和 返回
    for (int i = 0; i < n; i++)//操作节点个数n次
    {
        int u = -1;//与当前节点最小距离的未访问节点
        int mindistance = INF;
        for (int j = 0; j < n; j++)
        {
            if (vis[j] == false && d[j] < mindistance)
            {
                mindistance = d[j];
                u = j;
            }
        }
        if (u == -1)//不连通
            return -1;
        vis[u] = true;
        result += d[u];
        //上面和prim相同
        for (int k = 0; k < G[u].size(); k++)//更新u与其余可连接的未访问节点(共G[u].size()个)的距离。实际是用原距离更新INF
        {
            int tmp = G[u][k].v;//未访问节点的值
            if (!vis[tmp] && G[u][k].dis != INF && G[u][k].dis < d[tmp])
                d[tmp] = G[u][k].dis;
        }
    }
    return result;
}

/*二、kruskal克鲁斯卡尔算法*/
struct Edge
{
    int u, v;
    int weight;
    Edge(int x, int y, int z) :u(x), v(y), weight(z) {}
};
bool cmp(Edge a, Edge b)
{
    return a.weight < b.weight;
}
/*并查集查找。寻找s的所在集合的根节点*/
int findfather(vector<int> &father, int s)
{
    while (father[s] != s)
        s = father[s];
    return s;
}
/*
并查集https://blog.csdn.net/CoderPai/article/details/69388597 
并查集查找优化:对于每个节点,一旦向上走到了一次根节点,就把这个点到父亲的边改为直接连向根*/
int findfather_good(vector<int> &father, int s)
{
    int tmp = s;
    while (s != father[s])//得到s的根节点 给s
        s = father[s];
    while (tmp != father[tmp])//优化s的所有父节点直接指向根,下次找父节点就路径压缩
    {
        int ttmp = tmp;//先记录当前节点值
        tmp = father[tmp];//让tmp指向父节点,用于下一轮更新父节点直接指向根
        father[ttmp] = s;//直接让当前节点的父节点指向根
    }
}

/*
n为图中节点数,m为边数,E为边集合
*/
int kruskal(int n,int m,vector<Edge> &E)
{
    int result = 0;//最小生成树 权重结果
    int numofedge = 0;//已加入最小生成树的边数量
    vector<int> father(n);//并查集。用于判断边的两个节点是否已连接
    for (int i = 0; i < n; i++)//并查集初始化
        father[i] = i;
    sort(E.begin(), E.end(), cmp);//升序排列所有边权重
    for (int i = 0; i < m; i++)//循环边次数
    {
        /*当前边E[i]的两个顶点是否连通*/
        int fau = findfather(father, E[i].u);//并查集查找 u所在集合的根节点
        int fav = findfather(father, E[i].v);
        if (fau != fav)//不连通,合并表示加入最小生成树,连通
        {
            father[fau] = fav;//并查集合并
            result += E[i].weight;
            numofedge++;
            if (numofedge == n - 1)//如果边numofedge=节点n-1则有最小生成树,提前退出
                break;
        }
    }
    if (numofedge != n - 1)//不连通
        return -1;
    else
        return result;
}

int main()
{
    //最小生成树 2种算法实现
    /*1、prim算法*/
    int n = 6;
    /*邻接矩阵*/
    vector<vector<int>> G = { 
    {0,4,INF,INF,1,2},
    {4,0,6,INF,INF,3},
    {INF,6,0,6,INF,5},
    {INF,INF,6,0,4,5},
    {1,INF,INF,4,0,3},
    {2,3,5,5,3,0} };

    /*邻接表*/
    vector<vector<Node>> Adj = { 
    {Node(4,1),Node(5,2),Node(1,4)},
    {Node(0,4),Node(5,3),Node(2,6)},
    {Node(1,6),Node(3,6),Node(5,5)},
    {Node(2,6),Node(4,4),Node(5,5)},
    {Node(0,1),Node(5,3),Node(3,4)},
    {Node(0,2),Node(1,3),Node(2,5),Node(3,5),Node(4,3)} };

    /*for (auto x : Adj)
    {
        for (auto y : x)
            cout << y.v<<"-"<<y.dis << "  ";
        cout << endl;
    }*/

    int res1 = Prim(n, 0, G);//邻接矩阵版
    cout << res1 << endl;
    int res2 = Prim1(n, 0, Adj);//邻接表版
    cout << res2 << endl;

    //2、kruskal克鲁斯卡尔算法*/
    vector<Edge> E = { Edge(0,1,4),Edge(1,2,1),Edge(2,3,6),Edge(3,4,5),Edge(0,4,1),
                          Edge(0,5,2),Edge(1,5,3),Edge(2,5,5),Edge(3,5,4),Edge(4,5,3) };
    int k_n = 6;//节点数
    int k_m = 10;//边数
    int result = kruskal(k_n, k_m, E);
    cout << result << endl;

    return 0;
}

 

posted @ 2019-05-23 20:19  前进的code  阅读(260)  评论(0编辑  收藏  举报