目录
- Requirements
- Debugging Environment
- Chat
- Code
- 1.graph.h
- 2.test.cpp
Requirements
基于邻接表存储结构实现有向图的典型操作(构造、析构、增加顶点、删除顶点、增加弧、删除弧,查找一个 顶点、判空、判满、图中顶点个数、邻接表中指定顶点的第一个邻接顶点、深度优先遍历、广度优先遍历),测试和调试程序。
Debugging Environment
visual studio 2019
Chat
之前的图论问题中对于图的存储基本上都是用的链式向前星,刚好趁着这次课程实验了解一下邻接表,可能是第一次接触邻接表还不是很熟悉的缘故吧,总觉得邻接表好像比链式向前星麻烦一点。因为只是随手写的,可能会有一些小bug,在graph.h后面还有个test.cpp文件,那个是老师是给的测试文档,我跑了一下都还ok,但是你们在使用的时候如果发现了什么不对的地方,记得评论提醒我哈。
Code
(代码仅供参考)
1.graph.h
#ifndef GRAPH_H_INCLUDED
#define GRAPH_H_INCLUDED
/* This header file contains the declarations and prototype functions for the graph ADT
class. It also contains a debugging function to print a vertex.
*/
#include<iostream>
#include<map>
#include<queue>
#include<stack>
using namespace std;
#define DEBUG 1 //On includes printVertex
template <class TYPE> struct Vertex;//存储每个顶点的信息
template <class TYPE> struct Arc;//先简单声明,存储弧的信息
template <class TYPE> struct Vertex //存图中各顶点
{
Vertex<TYPE>* pNextVertex;//指向下一个头顶点
TYPE data;//顶点有效信息
int inDegree;//入度
int outDegree;//出度
short processed;//标识该顶点被处理的程度或状态
Arc<TYPE>* pArc; //指向第一条弧,这里用到了 Arc,所以在前面要先简单声明下
}; // Vertex
template <class TYPE> struct Arc //存图中各弧
{
Vertex<TYPE>* destination;//注意这里是指针,指向弧头顶点
Arc<TYPE>* pNextArc;//指向下一条弧
}; // Arc
template <class TYPE, class KTYPE> //KTYPE 表示各顶点中关键字的类型(关键字用于查询)
class Graph
{
private:
int count;
Vertex<TYPE>* first;
public:
Graph(void);
~Graph(void);
int insertVertex(TYPE dataIn);//插入顶点,数据域为 dataIn
int deleteVertex(KTYPE dltKey);//删除顶点,其数据域中关键字为 dltKey
int insertArc(KTYPE fromKey, KTYPE toKey);//插入弧,该弧从 fromKey 指向 toKey
int deleteArc(KTYPE fromKey, KTYPE toKey);//删除弧,该弧从 fromKey 指向 toKey
int retrieveVertex(KTYPE key, TYPE& DataOut);//查关键字为 key 的顶点,数据存入 DataOut
int firstArc(KTYPE key, TYPE& DataOut);//查关键字为 key 的顶点所指的首弧,数据存 DataOut
bool emptyGraph(void);//判图是否为空
bool graphFull(void);//判图是否为满
int graphCount(void); //返回图顶点个数
void depthFirst(void (*process)(TYPE dataProc));//深度优先遍历
void breadthFirst(void (*process)(TYPE dataProc)); // 广度优先遍历
#ifdef DEBUG
// The following function is used only for debugging
void printVertex(KTYPE key);//该方法仅在调试时使用,输出该顶点关键字
#endif
}; // Graph
template <class TYPE, class KTYPE>
Graph<TYPE, KTYPE>::Graph()
{
count = 0;
first = nullptr;
}
template <class TYPE, class KTYPE>
Graph<TYPE, KTYPE>::~Graph(void)
{
Vertex<TYPE>* p = first;
while (p) {
Vertex<TYPE>* q = p;
p = p->pNextVertex;
delete q;
}
}
template <class TYPE, class KTYPE>
int Graph<TYPE, KTYPE>::insertVertex(TYPE dataIn)//插入顶点,数据域为 dataIn
{
Vertex<TYPE>* p = new Vertex<TYPE>;
p->data = dataIn;
p->inDegree = 0;
p->outDegree = 0;
p->pArc = nullptr;
p->pNextVertex = nullptr;
count++;
Vertex<TYPE>* temp = first;
if (!first)
first = p;
else {
while (temp->pNextVertex) {
temp = temp->pNextVertex;
}
temp->pNextVertex = p;
}
return 1;
}
template <class TYPE, class KTYPE>
int Graph<TYPE, KTYPE>::deleteVertex(KTYPE dltKey)//删除顶点,其数据域中关键字为 dltKey
{
Vertex<TYPE>* p = first, * q = nullptr;
while (p) {
if (p->data.key == dltKey) {//删除节点表中的节点
if (p->inDegree||p->outDegree)
return -1;
Vertex<TYPE>* temp = p;
p = p->pNextVertex;
if (temp == first)//头删
first = p;
else
q->pNextVertex = p;
delete temp;
count--;
return 1;
}
q = p;
p = p->pNextVertex;
}
return -2;
}
template <class TYPE, class KTYPE>
int Graph<TYPE, KTYPE>::insertArc(KTYPE fromKey, KTYPE toKey)//插入弧,该弧从 fromKey 指向 toKey
{
int flag1 = 0, flag2 = 0;
Vertex<TYPE>* p = first, * q1 = nullptr, * q2 = nullptr;
while (p) {
if (p->data.key == fromKey) {
q1 = p;
++flag1;
}
if (p->data.key == toKey) {
q2 = p;
++flag2;
}
p = p->pNextVertex;
}
if (!flag1)
return -2;
if (!flag2)
return -3;
Arc<TYPE>* a = new Arc<TYPE>;
a->destination = q2;
a->pNextArc = nullptr;
Arc<TYPE>* temp = q1->pArc;
if (!temp)
q1->pArc = a;
else {
while (temp->pNextArc)
temp = temp->pNextArc;
temp->pNextArc = a;
}
a->destination->inDegree += 1;
q1->outDegree += 1;
return 1;
}
template <class TYPE, class KTYPE>
int Graph<TYPE, KTYPE>::deleteArc(KTYPE fromKey, KTYPE toKey)//删除弧,该弧从 fromKey 指向 toKey
{
int flag1 = 0, flag2 = 0;
Vertex<TYPE>* p = first, * q1 = nullptr;
while (p) {
if (p->data.key == fromKey) {
q1 = p;
++flag1;
}
p = p->pNextVertex;
}
if (!flag1)
return -2;
Arc<TYPE>* q2 = q1->pArc, * temp = nullptr;
while (q2) {
if (q2->destination->data.key == toKey)
{
if (temp)
temp->pNextArc = q2->pNextArc;
else
q1->pArc = q2->pNextArc;
q2->destination->inDegree -= 1;
q1->outDegree -= 1;
delete q2;
return 1;
}
temp = q2;
q2 = q2->pNextArc;
}
return -3;
}
template <class TYPE, class KTYPE>
int Graph<TYPE, KTYPE>::retrieveVertex(KTYPE key, TYPE& DataOut)//查关键字为 key 的顶点,数据存入 DataOut
{
Vertex<TYPE>* p = first;
while(p){
if (p->data.key == key) {
DataOut = p->data;
return 1;
}
p = p->pNextVertex;
}
return 0;
}
template <class TYPE, class KTYPE>
int Graph<TYPE, KTYPE>::firstArc(KTYPE key, TYPE& DataOut)//查关键字为 key 的顶点所指的首弧,数据存 DataOut
{
Vertex<TYPE>* p = first;
while (p) {
if (p->data.key == key) {
if (p->pArc) {
DataOut = p->pArc->destination->data;
return 1;
}
return -1;
}
p = p->pNextVertex;
}
return -2;
}
template <class TYPE, class KTYPE>
bool Graph<TYPE, KTYPE>::emptyGraph(void)//判图是否为空
{
return count == 0;
}
template <class TYPE, class KTYPE>
bool Graph<TYPE, KTYPE>::graphFull(void)//判图是否为满
{
return false;
}
template <class TYPE, class KTYPE>
int Graph<TYPE, KTYPE>::graphCount(void) //返回图顶点个数
{
return count;
}
template <class TYPE, class KTYPE>
void Graph<TYPE, KTYPE>::depthFirst(void (*process)(TYPE dataProc))//深度优先遍历
{
if (first) {
Vertex<TYPE>* p = first;
int cnt = 0;
stack<Arc<TYPE>* >st;
while (p) {//标记置位
p->processed = 0;
p = p->pNextVertex;
}
p = first;
while (!st.empty() || cnt == 0) {
Vertex<TYPE>* ptemp = first;
if (cnt != 0) {
ptemp = st.top()->destination;
st.pop();
}
process(ptemp->data);
Arc<TYPE>* temp = ptemp->pArc;
while (temp) {
if (!temp->destination->processed) { //遇到当前弧表的未处理弧
st.push(temp);
temp->destination->processed = 1;
}
temp = temp->pNextArc;
}
cnt++;
}
}
}
template <class TYPE, class KTYPE>
void Graph<TYPE, KTYPE>::breadthFirst(void (*process)(TYPE dataProc)) // 广度优先遍历
{
if (first) {
Vertex<TYPE>* p = first;
Arc<TYPE>* a = p->pArc;
queue<Vertex<TYPE>* > q;
while (p) {//初始化
p->processed = 0;
p = p->pNextVertex;
}
if (first)
q.push(first);
while (!q.empty()) {
Vertex<TYPE>* ptemp = q.front();
q.pop();
if (!ptemp->processed) {
process(ptemp->data);
ptemp->processed = 1;
}
Arc<TYPE>* temp = ptemp->pArc;
while (temp) {
if (!temp->destination->processed)
{
process(temp->destination->data);
temp->destination->processed = 1;
q.push(temp->destination);
}
temp = temp->pNextArc;
}
}
}
}
template <class TYPE, class KTYPE>
void Graph<TYPE, KTYPE>::printVertex(KTYPE key)//该方法仅在调试时使用,输出该顶点关键字
{
Vertex<TYPE>* p = first;
while (p) {
if (p->data.key == key) {
cout << "Adjacency list for " << key;
cout << " " << "inDegree " << p->inDegree << "--outDegree " << p->outDegree << endl;
Arc<TYPE>* temp = p->pArc;
while (temp) {
cout << " " << temp->destination->data.key;
temp = temp->pNextArc;
}
break;
}
p = p->pNextVertex;
}
}
#endif // GRAPH_H_INCLUDED
2.test.cpp
/* This program tests the graph ADT */
#include <iostream>
#include <iomanip>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>
#include "graph.h"
using namespace std;
struct DATA
{
int key;
bool operator<(const DATA& p)const {
return key < p.key;
}
};
//Prototype Functions
void doInsertVertex(Graph<DATA, int>& graph);
void doInsertArc(Graph<DATA, int>& graph);
void doDeleteVertex(Graph<DATA, int>& graph);
void doDeleteArc(Graph<DATA, int>& graph);
void search(Graph<DATA, int>& graph);
void doRetrieveVertex(Graph<DATA, int>& graph);
void doFirstArc(Graph<DATA, int>& graph);
void testUtilties(Graph<DATA, int>& graph);
void buildAGraph(Graph<DATA, int>& graph);
char doMenu(void);
void process(DATA data);
int main(void)
{ // Local Declarations
char option;
int key;
DATA datum;
Graph<DATA, int> graph;
// Statements
cout << "Start Explore Graphs\n";
while ((option = doMenu()) != 'q')
{
switch (option)
{
case 'i': doInsertVertex(graph); break;
case 'd': doDeleteVertex(graph); break;
case 'a': doInsertArc(graph); break;
case 'b': doDeleteArc(graph); break;
case 'p': cout << "Enter vertex key: ";
cin >> key;
graph.printVertex(key);
break;
case 'r': doRetrieveVertex(graph); break;
case 's': doFirstArc(graph); break;
case 't': datum.key = INT_MIN;
process(datum);//Set count
cout << "\nBegin depth first traversal\n";
graph.depthFirst(process);
cout << "\nEnd depth first traversal\n";
cout << "\nBegin breadth first traversal\n";
process(datum); //Set count
graph.breadthFirst(process);
cout << "\nEnd breadth first traversal\n";
break;
case 'x': testUtilties(graph); break;
case '!': buildAGraph(graph); break;
default: cout << "\a\a\nInvalid option. Please try again.\n";
} // switch
} // while
return 0;
cout << "End Explore Graphs\n";
} // main
/* ============================= doMenu =============================
This function prints a menu and reads the user selection.
Pre Nothing.
Post option returned.
*/
char doMenu(void)
{ // Local Declarations
char option;
// Statements
cout << "\n============ Menu =============\n";
cout << " i : Insert new item \n";
cout << " d : Delete a vertex \n";
cout << " a : Insert an arc \n";
cout << " b : Delete an arc \n";
cout << " p : Print Vertex Adjacency \n";
cout << " r : Retrieve Vertex \n";
cout << " s : Retrieve First Arc \n";
cout << " t : Traverse graph \n";
cout << " x : Test ADT utilities \n";
cout << " q : Quit \n\n";
cout << "=============================== \n";
cout << "Please enter your choice: ";
cin >> option;
option = tolower(option);
return option;
} // doMenu
/* =================== doRetrieveVertex ===================
This function locates a vertex in the graph.
Pre graph exists.
Post vertex retrieved and printed.
*/
void doRetrieveVertex(Graph<DATA, int>& graph)
{ // Local Declarations
int key;
int success;
DATA dataOut;
// Statements
cout << "Please enter key to be found: ";
cin >> key;
success = graph.retrieveVertex(key, dataOut);
if (success == 1)
cout << "\nKey found. Contains " << dataOut.key << endl;
else
cout << "\n\aKey " << key << " not found\n";
return;
} // doRetrieveVertex
/* =================== doFirstArc ===================
This function locates the first arc of a vertex.
Pre graph exists.
Post vertex first arc retrieved and printed.
*/
void doFirstArc(Graph<DATA, int>& graph)
{ // Local Declarations
int key;
int success;
DATA dataOut;
// Statements
cout << "Please enter key of first arc: ";
cin >> key;
success = graph.firstArc(key, dataOut);
if (success == 1)
cout << "\nFirst arc of " << key << " contains " << dataOut.key;
else if (success == -2)
cout << "\n\aVertex Key " << key << " not found\n";
else
cout << key << " has no arcs\n";
return;
} // doFirstArc
/* ===================== doInsertVertex =====================
This function gets input from the user and passes it to
vertex insert vertex function.
Pre graph exists.
Post vertex has been inserted.
*/
void doInsertVertex(Graph<DATA, int>& graph)
{ // Prototype Statements
// Local Declarations
DATA newData;
int success;
// Statements
cout << "\nPlease enter interger to be inserted: ";
cin >> newData.key;
success = graph.insertVertex(newData);
if (success)
cout << newData.key << " inserted\n";
else
cout << newData.key << " NOT inserted\a\n";
return;
} // doInsertVertex
/* ======================= doInsertArc =======================
This function gets input from the user and passes it to
arc insert vertex function.
Pre graph exists.
Post arc has been inserted.
*/
void doInsertArc(Graph<DATA, int>& graph)
{
// Prototype Statements
// Local Declarations
int fromKey;
int toKey;
int success;
// Statements
cout << "\nPlease enter from key: ";
cin >> fromKey;
cout << "\nPlease enter to key: ";
cin >> toKey;
success = graph.insertArc(fromKey, toKey);
switch (success)
{
case 1: cout << "arc " << fromKey << "-to-" << toKey << " inserted\n";
break;
case -1: cout << "Overflow: arc NOT inserted\a\n";
break;
case -2: cout << "From Key " << fromKey << " Error\a\n";
break;
case -3: cout << "To Key " << toKey << " Error\a\n";
break;
default: cout << "Unknown error " << success << " in doInsertArc\n";
cout << "ABORTING 100\a\a\n";
exit(100);
} // switch
return;
} // doInsertArc
/* ======================= doDeleteArc =======================
This function gets input from the user and passes it to
the delete arc function.
Pre graph exists.
Post arc has been deleted.
*/
void doDeleteArc(Graph<DATA, int>& graph)
{ // Local Declarations
int fromKey;
int toKey;
int success;
// Statements
cout << "\nPlease enter from key: ";
cin >> fromKey;
cout << "\nPlease enter to key: ";
cin >> toKey;
success = graph.deleteArc(fromKey, toKey);
switch (success)
{
case 1: cout << "arc " << fromKey << "-to-" << toKey << " deleted\n";
break;
case -2: cout << "From Key " << fromKey << " Error\a\n";
break;
case -3: cout << "To Key " << toKey << " Error\a\n";
break;
default: cout << "Unknown error " << success << "in doDeleteArc\n";
cout << "ABORTING 101\a\a\n";
exit(101);
} // switch
return;
} // doDeleteArc
/* ========================== doDeleteVertex ==========================
This function deletes a node from the graph. It asks the user for the
key of the node to be deleted and then removes it from the graph.
Pre graph must be initialized. Null graph is OK.
Post The node is deleted and its space recylced
-or- An error message is printed.
*/
void doDeleteVertex(Graph<DATA, int>& graph)
{ // Prototype Statements
// Local Declarations
int dltKey;
int success;
DATA dltData;
// Statements
cout << "\nPlease enter integer to be deleted: ";
cin >> dltKey;
graph.retrieveVertex(dltKey, dltData);//即将被删的元素读出来到 dltDat 中
success = graph.deleteVertex(dltKey);//真正删除被删的元素
switch (success)
{
case 1: cout << dltKey << " deleted\n";
break;
case -1: cout << dltKey << "'s degree not zero\a\n";
break;
case -2: cout << dltKey << "'s key not found\a\n";
break;
default: cout << "UNKNOWN ERROR 101 in delete\a\n";
cout << "ABORTING\a";
exit(101);
} // switch
return;
} // doDeleteVertex
/* =================== process ==================
This function "processes" a graph by printing a
sequential number and the vertex data.
Pre dataPtr is pointer to user data structure
Post Data (integer) printed.
*/
void process(DATA data)
{ // Local Declarations
static int count = 1;
// Statements
if (data.key == INT_MIN)
count = 1;
else
cout << "Vertex #" << setw(3) << count++
<< " contains: " << setw(3) << data.key << endl;
return;
} // process
/* =================== testUtilties ==================
This function tests the ADT utilities by calling
each in turn and printing their results.
Pre graph has been created.
Post Results printed.
*/
void testUtilties(Graph<DATA, int>& graph)
{
cout << "Graph contains " << graph.graphCount() << " nodes\n";
if (graph.emptyGraph())
cout << "The graph IS empty\n";
else
cout << "The graph IS NOT empty\n";
if (graph.graphFull())
cout << "The graph IS full\a\n";
else
cout << "The graph IS NOT full\n";
return;
} // testUtilities
/* =================== buildAGraph ==================
This function builds the graph in Figure 12-1(a)
using 1 for A, 2 for B, etc.
Pre graph has been created.
Post graph built.
*/
void buildAGraph(Graph<DATA, int>& graph)
{
// Local Declarations
DATA data;
int i;
int success;
int fromKey;
int toKey;
// Statements
for (i = 1; i <= 6; i++)
{
data.key = i;
success = graph.insertVertex(data);
if (!success)
cout << "Insert error in buildAGraph\a\a\n", exit(100);
} // for
// Vertices built. Now build arcs
fromKey = 1;
toKey = 2;
success = graph.insertArc(fromKey, toKey);
fromKey = 2;
toKey = 3;
success = graph.insertArc(fromKey, toKey);
fromKey = 3;
toKey = 4;
success = graph.insertArc(fromKey, toKey);
fromKey = 3;
toKey = 5;
success = graph.insertArc(fromKey, toKey);
fromKey = 5;
toKey = 4;
success = graph.insertArc(fromKey, toKey);
fromKey = 5;
toKey = 6;
success = graph.insertArc(fromKey, toKey);
fromKey = 2;
toKey = 5;
success = graph.insertArc(fromKey, toKey);
if (!success)
cout << "Error adding arcs in buildAGraph\a\a\n", exit(100);
return;
}