图的表达方式
邻接表
上图的邻接表表示为
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()函数,前者用于取出堆顶元素,后者用于有选择的添加新元素,添加时直接对堆中的元素进行更改。
将第一个节点加入堆(依据到第一个节点的距离排序的小根堆);
取出堆顶元素,将与它相连的节点加入堆中;
继续知道堆为空。