图的表达方式

邻接表

上图的邻接表表示为

1:2,7;

2:3;

3:4,7;

4:5;

6:5,3;

邻接矩阵

上图的邻接矩阵表达为

0, 1,0, 0, 0, 0, 1

0, 0,1, 0, 0, 0, 0

0, 0,0, 1, 0, 0, 1

0, 0,0, 0, 1, 0, 0

0, 0,0, 0 , 0,0 ,0

0, 0,0, 0, 1, 0,0

0, 0,0, 0, 0, 0, 0

图的表达

可以使用邻接表,也可以使用邻接矩阵。事实上图的表示非常自由,可以根据自己的实际需要设计表达方式。以后的算法采用邻接表的方式来表达,类声明如下

class Edge;

class Node{
public:
    int in;//该节点的入度
    int out;//出度
    int value;//该节点的值
    vector<Node*> nexts;//与其相连的点
    vector<Edge*> edges;//相连边的边
    Node(int value){
        this->value = value;
        this->in = 0;
        this->out = 0;
    }
    Node(){
        this->value = 0;
        this->in = 0;
        this->out = 0;
    }
};

class Edge{
public:
    int weight;//边的权重
    Node* from;//起始点
    Node* to;//结束点
    Edge(int weight,Node* from,Node* to){
        this->weight = weight;
        this->from = from;
        this->to = to;
    }
};

class Graph{
public:
    map<int,Node*> nodes;//点的序号,点本身
    vector<Edge*> edges;
}

图的生成

生成上述声明的图的方式如下:

 //创建有向图
    //matrix为n行3列的矩阵,第一列存储边的权值,第二列存储变得起始点,第三列存储边的结束点
    static Graph* createGraph(int matrix[10][3],int n){
        Graph* graph = new Graph();
        if(matrix == NULL || n <= 0)
            return NULL;
        for(int i = 0; i < n; i++){
            int weight = matrix[i][0];
            int from = matrix[i][1];
            int to = matrix[i][2];
            //cout << weight << " " << from << " " << to << endl;
            Node* fromNode = (graph->nodes)[matrix[i][1]] == NULL ? new Node(from) : graph->nodes[matrix[i][1]];
            fromNode->out++;
            Node* toNode = graph->nodes[matrix[i][2]] == NULL ? new Node(to) : graph->nodes[matrix[i][2]];
            toNode->in++;
            fromNode->nexts.push_back(toNode);
            Edge* edge = new Edge(weight,fromNode,toNode);
            fromNode->edges.push_back(edge);
            graph->nodes[from] = fromNode;
            graph->nodes[to] = toNode;
            graph->edges.push_back(edge);
        }
        return graph;
    }

    //创建无向图
    static Graph* createUndigraph(int matrix[10][3],int n){
        if( n <= 0 || matrix == NULL){
            return NULL;
        }
        Graph* graph = new Graph();
        set<Node*> s;//存储已经进入图中的节点
        for(int i = 0; i < n; i++){
            int weight = matrix[i][0];
            int from = matrix[i][1];
            int to = matrix[i][2];
            //cout << weight << " " << from << " " << to << endl;
            Node* fromNode = graph->nodes[from] == NULL ? new Node(from) : graph->nodes[from];
            Node* toNode = graph->nodes[to] == NULL ? new Node(to) : graph->nodes[to];
            //cout << fromNode->value << " " << toNode->value << endl;
            fromNode->in++;
            fromNode->out++;
            toNode->in++;
            toNode->out++;

            fromNode->nexts.push_back(toNode);
            toNode->nexts.push_back(fromNode);
            Edge* edge_1 = new Edge(weight,fromNode,toNode);
            fromNode->edges.push_back(edge_1);
            toNode->edges.push_back(edge_1);

            //Edge* edge_2 = new Edge(weight,toNode,fromNode);
            graph->nodes[from] = fromNode;
            graph->nodes[to] = toNode;
            //cout << graph->nodes[from]->value << " " << graph->nodes[to]->value << endl;
            graph->edges.push_back(edge_1);
            //graph->edges.push_back(edge_2);
            //cout << graph->edges.size() << endl;
        }
        return graph;

    }

  

图的遍历

宽 / 广度优先遍历

图的宽度优先遍历与树的层序遍历相同,可以采用队列来实现。

 static void BFS(Node* node){
        if(node == NULL){
            return;
        }
        queue<Node*> q;
        set<Node*> s;//用来存储已经入过队的节点
        q.push(node);
        s.insert(node);
        Node* p = NULL;
        while(!q.empty()){
            p = q.front();
            cout << p->value << " ";
            q.pop();
            for(int i = 0; i < p->nexts.size();i++ ){
                if(p->nexts[i] != *(s.find(p->nexts[i]))){
                    q.push(p->nexts[i]);
                    s.insert(p->nexts[i]);
                }

            }
        }
    }
 static void BFS(Graph& graph){
        if(graph.nodes.size() <= 0 || graph.edges.size() < 0)
            return;
        else if(graph.nodes.size() == 1){
            cout << graph.nodes[0]->value << endl;
        }
        BFS(graph.nodes.begin()->second);

    }

深度优先遍历

广度优先遍历类似于树的前序遍历,可以使用栈来实现。

//深度优先遍历
    static void DFS(Node* node){
        if(node == NULL){
            return;
        }
        stack<Node*> s;
        set<Node*> se;//存储已经入栈的节点
        s.push(node);
        cout << s.top()->value << " ";
        se.insert(s.top());
        while(!s.empty()){
            int i = 0;
            //cout << "begin find: " << (*(se.find(s.top())))->value << endl;
            for(; i < s.top()->nexts.size() && (s.top()->nexts)[i] == *(se.find((s.top()->nexts)[i])); i++);
            //cout << "i: " <<i << " size: " << s.top()->nexts.size() << " data:" << s.top()->value << endl;
            if(i == s.top()->nexts.size()){
               // cout << "zhixing pop" << endl;;
                s.pop();
            }else{
               // cout << "zhixing push" << endl;
                s.push(s.top()->nexts[i]);
                cout << s.top()->value << " ";
                se.insert(s.top());
            }
        }
    }

    static void DFS(Graph* graph){
        if(graph == NULL || graph->nodes.size() <= 0 || graph->edges.size() < 0){
            return;
        }
        DFS(graph->nodes.begin()->second);
    }

相关算法

拓扑排序

适用范围:当我们在编译程序时,可能会发现自己的程序依赖于多个包,而包和包之间也存在一定的依赖关系。如我的程序依赖包A、B、C才能运行,包B依赖包A,包A依赖包D,所以在编译程序时,这些包的编译顺序未D/C -> A -> B;

使用拓扑排序的前提:有向无环图;

实现思路:

首先寻找图中入度为0的点;

消除该点对其他点的影响(与该点相连的其他点出度都 - 1);

继续寻找入度为0 的点。

  //拓扑排序
    static list<Node*> topologySort(Graph* graph){
        list<Node*> l;
        if(graph == NULL || graph->nodes.size() <= 0){
            return l;
        }
        map<int,Node*>::iterator iter;
        set<Node*> s;//存储已经进入链表的节点
        for(int count = 0; count < graph->nodes.size();){
            for(iter = graph->nodes.begin(); iter != graph->nodes.end(); iter++){
                if(iter->second->in == 0 && s.find(iter->second) == s.end()){ //入度为0且没有进入链表
                    count++;
                    //cout << count << " node:" << iter->second->value << endl;
                    l.push_back((iter->second));
                    s.insert(iter->second);
                    for(int i = 0; i < ((iter->second))->nexts.size(); i++)
                        ((iter->second))->nexts[i]->in--;
                }
            }
        }
        return l;
    }

kruskal算法

该算法与下面的prim算法都用来解决最小生成图/树的问题。

最小生成图/树:在保持图的联通性的基础上,只保留权值最小的边。需要注意的是最小生成图都是无向图。

实现思路:

将图中的边按大小排列;

遍历边,判断最小边的两个节点是否在同一个集合;

在则继续遍历,不在则合并两个节点所在集合后继续遍历。

 //kruskal算法
    //判断两个节点所在的链表是否属于同一个,fatherMap<节点本身,父节点>
    static bool isSameSet(Node* n1,Node* n2,map<Node*,Node*> fatherMap){
        return fatherMap[n1] == fatherMap[n2];
    }
    static Graph* kruskal(Graph* graph){
        if(graph == NULL || graph->nodes.size() <= 1){
            return graph;
        }
        map<Node*,Node*> fatherMap;//map<节点本身,父节点>
        for(map<int,Node*>::iterator iter = graph->nodes.begin();iter != graph->nodes.end();iter++){//初始化fatherMap,使每一个节点的父节点都是它自身
            fatherMap[iter->second] = iter->second;
        }
        sort(graph->edges.begin(),graph->edges.end(),Compare);//按权重对边进行排序
        for(int i = 0; i < graph->edges.size(); i++){
            //cout << "边:" << graph->edges[i]->from->value << " " << graph->edges[i]->to->value << " " << graph->edges[i]->weight << endl;
            //如果边上的两个点不在同一个集合,就合并
            if(!isSameSet(graph->edges[i]->from,graph->edges[i]->to,fatherMap)){
                //边上的两个点分别是a,b  遍历图上的所有点寻找头节点为b的节点,将这些节点全部的头节点全部改为a
                int temp = fatherMap[graph->edges[i]->to]->value;//存储b节点的编号
                for(map<int,Node*>::iterator iter = graph->nodes.begin(); iter != graph->nodes.end(); iter++){
                    fatherMap[iter->second] = fatherMap[iter->second]->value == temp ? fatherMap[graph->edges[i]->from] : fatherMap[iter->second];
                }
               // fatherMap[graph->edges[i]->to] = fatherMap[graph->edges[i]->from];
            }else{
                graph->edges.erase(graph->edges.begin()+ i);
                Node* from = graph->nodes.find(graph->edges[i]->from->value)->second;
                Node* to = graph->nodes.find(graph->edges[i]->to->value)->second;
                from->in--;
                from->out--;
                to->in--;
                to->out--;
                i--;
            }
//            for(map<Node*,Node*>::iterator iter = fatherMap.begin();iter != fatherMap.end();iter++){//初始化fatherMap,使每一个节点的父节点都是它自身
//                cout << iter->first->value << " 父:" << iter->second->value << endl;
//            }
//            cout << endl<< endl;
        }
        return graph;
    }

实现kruskal算法的最优结构是并查集。

prim算法

该算法与上面的克鲁斯卡尔算法都用来解决最小生成图/树的问题。

实现思路:

选择一个点加入集合,解锁与该点相接的一组边;

选择其中权值最小的一个一条,将对应节点加入集合,解锁与对应点相接的一组边;

如果对应点在集合中已存在,放弃该边,选择下一条;

继续解锁节点知道所有节点加入集合。

//prim算法
    //该算法需要保证传入的图是连通图
    static vector<Edge*> prim(Graph* graph){
        if(graph == NULL || graph->nodes.size() <= 1){
            return graph;
        }
        set<Node*> nodeSet;//已解锁的点
        priority_queue<Edge*,vector<Edge*>,ComEdges > edgeSet;//小根堆c已解锁的边
        vector<Edge*> e;
        for(map<int,Node*>::iterator iter = graph->nodes.begin(); iter != graph->nodes.end(); iter++){
            if(*nodeSet.find(iter->second) != iter->second){//如果选取的点不在集合中,将它加入集合
                nodeSet.insert(iter->second);
                for(int i = 0; i < iter->second->edges.size(); i++){
                    edgeSet.push(iter->second->edges[i]);
                }
            }
            while(!edgeSet.empty()){
                Edge* edge= edgeSet.top();
                edgeSet.pop();
                Node* toNode = edge->to;
                Node* fromNode = edge->from;
                //cout << "edgeSetSize:" << edgeSet.size() << " EdgeWeight" << edge->weight << "|" << fromNode->value << " "  << toNode->value << endl;
                if(*nodeSet.find(toNode) != toNode){//如果边上的点没有进入集合,加入点,并将与点相连的边加入小根堆
                    nodeSet.insert(toNode);
                    e.push_back(edge);
                    for(int i = 0; i < toNode->edges.size(); i++){
                        if(*nodeSet.find(toNode->edges[i]->to) != toNode->edges[i]->to)
                            edgeSet.push(toNode->edges[i]);
                        else if(*nodeSet.find(toNode->edges[i]->from) != toNode->edges[i]->from)
                            edgeSet.push(toNode->edges[i]);
                    }
                }
                 else if(*nodeSet.find(fromNode) != fromNode){
                    nodeSet.insert(fromNode);
                    e.push_back(edge);
                    for(int i = 0; i < fromNode->edges.size(); i++){
                        if(*nodeSet.find(fromNode->edges[i]->from) != fromNode->edges[i]->from)
                            edgeSet.push(fromNode->edges[i]);
                       else if(*nodeSet.find(fromNode->edges[i]->to) != fromNode->edges[i]->to)
                            edgeSet.push(fromNode->edges[i]);
                    }
                }
            }
        }
        return e;
    }

Dijkstra算法

该算法用于解决最短路径问题,适用于边的权值不存在负值的图。

实现思路:

选定一个点,列出它到各个点的边的权值,没有边的权值为无穷大;

解锁其中权值最小的边对应的点,该对应点会解锁一批新的边,更新第一个点到各个点的权值;

每个点都被解锁后程序结束。

 //Dijkstra算法
    //用于解决最短路径问题,要求边的权值不存在负值
    static Node* getMinDistanceAndUnselectedNode(map<Node*,int> distanceMap,set<Node*> selectedNodes){
        Node* minNode = NULL;
        int minDistance = INT_MAX;

        for(map<Node*,int>::iterator iter = distanceMap.begin();iter != distanceMap.end(); iter++){
            Node* node= iter->first;
            int distance = iter->second;
            if(selectedNodes.find(node) == selectedNodes.end() && distance < minDistance){
                minNode = node;
                minDistance = distance;
                cout << minNode->value << "-" << distance << "  ";
            }

        }
        if(minNode != NULL)
        cout << "minNode.value: " << minNode->value << endl;
        return minNode;
    }

    static map<Node*,int> dijkstra(Graph* graph,Node* node){
        map<Node*,int> distanceMap;//存储node节点到图上各点的距离
        if(graph == NULL || graph->nodes.size() <= 1){
            return distanceMap;
        }
        set<Node*> selectedNodes;  //存储已经加入到同一个集合中的节点
        selectedNodes.insert(node);
        distanceMap[node] = 0;
        for(int i = 0; i < node->edges.size(); i++){
            Node* toNode = node->edges[i]->to;
            distanceMap[toNode] = node->edges[i]->weight;
        }

        Node* minNode = getMinDistanceAndUnselectedNode(distanceMap,selectedNodes);

        while(minNode != NULL){
            int distance = distanceMap[minNode];
            for(int i = 0; i < minNode->edges.size(); i++){
                Node* toNode = minNode->edges[i]->to;
                if(distanceMap.find(toNode) == distanceMap.end()){
                    distanceMap[toNode] = minNode->edges[i]->weight + distance;
                }else{
                    distanceMap[toNode] = distanceMap[toNode] <= (minNode->edges[i]->weight + distance) ? distanceMap[toNode] : minNode->edges[i]->weight + distance;
                }
            }
            selectedNodes.insert(minNode);
            minNode = getMinDistanceAndUnselectedNode(distanceMap,selectedNodes);
        }
        return distanceMap;
    }

使用堆结构(小根堆)可以对上面的代码进行优化

class NodeRecord{
public:
    int distance;
    Node* node;
    NodeRecord(Node* node,int distance){
        this->node = node;
        this->distance = distance;
    }
};

class NodeHeap{
private:
    vector<Node*> nodes;
    map<Node*,int> heapIndexMap;//节点在vector中的下标
    map<Node*,int> distanceMap;//最短距离
    int size;

    bool isEntered(Node* node){
        return heapIndexMap.find(node) == heapIndexMap.end() ? false : true;
    }

    bool isInHeap(Node* node){
        return (isEntered(node) && heapIndexMap[node] != -1) ? true : false;
    }

    void insertHeapify(Node* node,int index){
        // cout << "zhixing " << endl;
        while(distanceMap[nodes[index]] < distanceMap[nodes[(index - 1) / 2]]){
            swap(nodes[index],nodes[(index - 1) / 2]);
            index = (index - 1) / 2;
        }
    }

    void heapify(Node* node,int index){
        int left = index * 2 + 1;
        while(left < size){
            int small = (left + 1 < size) && distanceMap[nodes[left]] > distanceMap[nodes[left + 1]] ? left + 1 : left;
            int smallest = distanceMap[nodes[small]] < distanceMap[node] ? small : index;
            if(index == smallest){
                break;
            }
            swap(nodes[index],nodes[smallest]);
            index = smallest;
            left = index * 2 + 1;
        }
    }
public:
    NodeHeap(){
        this->size = 0;
    }
    bool isEmpty(){
        return this->size == 0;
    }

    void addOrUpdataOrIgnore(Node* node,int distance){
        if(isInHeap(node)){
            distanceMap[node] = distance < distanceMap[node] ? distance : distanceMap[node];
            insertHeapify(node,heapIndexMap[node]);
        }
        if(!isEntered(node)){
            nodes.insert(nodes.begin() + size, node);
            heapIndexMap[node] = size;
            distanceMap[node] = distance;
            insertHeapify(node,size++);

        }
    }

    NodeRecord pop(){
        NodeRecord node(nodes[0],distanceMap[nodes[0]]);
//        nodes[0].node = nodes[size - 1].node;
//        nodes[0].distance = nodes[size - 1].distance;
        swap(nodes[0],nodes[size - 1]);
        heapIndexMap[nodes[size - 1]] = -1;
        distanceMap.erase(nodes[size - 1]);
        size--;
        heapify(nodes[0],0);


        return node;
    }
};

//改进后的Dijkstra算法
map<Node*,int> dijkstra(Graph* graph,Node* node,int size){
    map<Node*,int> res;
    if(graph == NULL || node == NULL || graph->nodes.size() <= 0){
        return res;
    }
    NodeHeap heap;

    heap.addOrUpdataOrIgnore(node,0);

    while(!heap.isEmpty()){
        NodeRecord nodeRecord = heap.pop();
        Node* n = nodeRecord.node;
        int distance = nodeRecord.distance;
        for(int i = 0; i < n->edges.size(); i++){
            heap.addOrUpdataOrIgnore(n->edges[i]->to,n->edges[i]->weight + distance);\
        }
        res[n] = distance;
    }
    return res;
}

系统的堆结构只能提供pop与push操作,不能直接对堆中的元素进行操作,所以这里的堆结构是自己实现的堆结构。提供了两个接口,分别是pop()与addOrUpdateOrIgnore()函数,前者用于取出堆顶元素,后者用于有选择的添加新元素,添加时直接对堆中的元素进行更改。

将第一个节点加入堆(依据到第一个节点的距离排序的小根堆);

取出堆顶元素,将与它相连的节点加入堆中;

继续知道堆为空。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值