目录
为什么使用邻接表
当图中的边数相对于顶点较少时,邻接矩阵是对存储空间的极大浪费
我们可以考虑对边或弧使用链式存储的方式来避免空间浪费的问题
我们把这种数组与链表相结合的存储方法称为邻接表
邻接表的结构
图的结构体
1.顶点数、弧数
2.图的类型
3.邻接表
typedef int ArcType;
enum GraphKind {
DG,
DN,
UDG,
UDN,
};
struct Vertex {
char name[MAX_NAME_SIZE];
};
struct Graph {
int verNum, arcNum;
GraphKind kind;
AdjList adj;
};
邻接表AdjList[MAX_VERTEX_NUM]
1.顶点
2.弧指针firstArc
typedef struct VertexNode {
Vertex vertex;
ArcNode* firstArc;
}AdjList[MAX_VERTEX_NUM];
记录弧的结点
1.data 和指针(链式结构)
2.data 里存储的是以该结点为弧尾的弧的弧头,弧的权值
struct ElemType {
int headLoc;
ArcType value;
};
struct ArcNode {
ElemType data;
ArcNode* next;
};
邻接表创建及增删改的操作
创建图
1.输入类型、弧数、点数
2.输入vernum个顶点的name ,并使每一个顶点的firstArc=NULL
3.输入弧尾、弧头,若是网,输入权值
4.使用头插法构建邻接表(若是无向图,把弧尾和弧头反过来再创建一个结点即可)
*输入名字获得顶点下标的函数*
int VertexLoc(Graph G, Vertex v) {
for (int i = 0; i < G.verNum; i++)
if (strcmp(G.adj[i].vertex.name, v.name) == 0)
return i;
return -1;
}
头插法理解
void CreateGraph(Graph& G) {
cout << "请输入图的类型(0:有向图, 1:有向网, 2:无向图, 3:无向网)" << endl;
int kind;
cin >> kind;
G.kind = (GraphKind)kind;
cout << "请输入顶点数、弧数" << endl;
cin >> G.verNum >> G.arcNum;
for (int i = 0; i < G.verNum; i++) {
cout << "请输入第" << i + 1 << "个顶点的名称" << endl;
cin >> G.adj[i].vertex.name;
G.adj[i].firstArc = NULL;
}
Vertex head, tail;
int headLoc, tailLoc;
int value = 1;
for (int i = 0; i < G.arcNum; i++) {
cout << "请输入第" << i + 1 << "条弧的 弧尾、弧头: ";
cin >> tail.name >> head.name;
if (G.kind % 2 == 1) {
//网
cout << "请输入权值: ";
cin >> value;
}
headLoc = VertexLoc(G, head);
tailLoc = VertexLoc(G, tail);
ArcNode* p = new ArcNode;
p->data.headLoc = headLoc;
p->data.value = value;
//头插法,O(1),最先插入的顶点的next = NULL
p->next = G.adj[tailLoc].firstArc;
G.adj[tailLoc].firstArc = p;
if (G.kind > 1) {
//无向
ArcNode* q = new ArcNode;
q->data.headLoc = tailLoc;
q->data.value = value;
q->next = G.adj[headLoc].firstArc;
G.adj[headLoc].firstArc = q;
}
}
}
打印图
循环cout: 从第0个顶点到第verNum个,从每个的顶点的firstArc到NULL
void Display(Graph G) {
ArcNode* p;
for (int i = 0; i < G.verNum; i++) {
p = G.adj[i].firstArc;
cout << G.adj[i].vertex.name << "为弧尾的弧的弧头有:" << endl;
while (p) {
cout << G.adj[i].vertex.name << "->" << G.adj[p->data.headLoc].vertex.name << " : " << p->data.value << endl;
p = p->next;
}
cout << endl;
}
cout << endl;
}
Some Function (≧∇≦)ノ
//获取序号为v的顶点
Vertex GetVex(Graph G, int v) {
if (v < 0 || v >= G.verNum)
exit(ERROR);
return G.adj[v].vertex;
}
//对顶点v赋新值newV
status PutVex(Graph G, Vertex v, Vertex newV) {
int vLoc = VertexLoc(G, v);
if (vLoc == -1)
return ERROR;
G.adj[vLoc].vertex = newV;
return OK;
}
//返回序号为v的顶点的第一个邻接点的序号
int FirstAdjvex(Graph G, int v) {
if (v < 0 || v >= G.verNum)
return -1;
ArcNode* p = G.adj[v].firstArc;
if (p)
return p->data.headLoc;
else
return -1;
}
//判断两个弧头是否相等
status EqualVertex(ElemType e1, ElemType e2) {
if (e1.headLoc == e2.headLoc)
return OK;
else
return ERROR;
}
//返回v的(相对于w的)下一个邻接点的序号,若w是v的最后一个顶点,则返回-1
//v:序号v; w:序号v的顶点的邻接点的序号
int NextVertex(Graph G, int v, int w) {
if (v < 0 || v >= G.verNum)
return -1;
ArcNode* p = G.adj[v].firstArc;
while (p && p->data.headLoc != w)
p = p->next;
if (!p || !p->next)
return -1;
else
return p->next->data.headLoc;
}
顶点和弧的插入删除
插入顶点——数组加一个元素(记得初始化和verNum++)
插入弧——(和创建图中一样,头插法)
删除弧——注意特殊情况
删除顶点——删除和v关联的弧,点数组和弧链表中受到影响的下标都要调整
//插入顶点
void InsertVertex(Graph& G, Vertex v) {
if (G.verNum == MAX_VERTEX_NUM)
exit(OVERFLOW);
G.adj[G.verNum].vertex = v;
G.adj[G.verNum].firstArc = NULL;
G.verNum++;
}
//插入弧
status InsertArc(Graph& G, Vertex tail, Vertex head) {
int tailLoc, headLoc;
tailLoc = VertexLoc(G, tail);
headLoc = VertexLoc(G, head);
if (tailLoc == -1 || headLoc == -1)
return ERROR;
int value = 1;
if (G.kind % 2 == 1) {
//网
cout << "请输入权值: ";
cin >> value;
}
ArcNode* p = new ArcNode;
p->data.headLoc = headLoc;
p->data.value = value;
p->next = G.adj[tailLoc].firstArc;
G.adj[tailLoc].firstArc = p;
if (G.kind > 1) {
//无向
ArcNode* q = new ArcNode;
q->data.headLoc = tailLoc;
q->data.value = value;
q->next = G.adj[headLoc].firstArc;
G.adj[headLoc].firstArc = q;
}
G.arcNum++;
return OK;
}
//删除弧
status DeleteArc(Graph& G, Vertex tail, Vertex head) {
int tailLoc = VertexLoc(G, tail);
int headLoc = VertexLoc(G, head);
if (tailLoc == -1 || headLoc == -1 || tailLoc == headLoc)
return ERROR;
ArcNode* p, * q;
p = G.adj[tailLoc].firstArc;
//如果第一个顶点就是弧头
if (p && p->data.headLoc == headLoc) {
q = p->next;
G.adj[tailLoc].firstArc = q;
free(p);
}
else {
//只有一个顶点
if (p && p->next == NULL)
return ERROR;
while (p && p->next->data.headLoc != headLoc)
p = p->next;
if (!p)
return ERROR;
q = p->next;
p->next = q->next;
free(q);
}
if (G.kind > 1) {
//无向
ArcNode* p2, * q2;
p2 = G.adj[headLoc].firstArc;
//如果第一个就是
if (p2 && p2->data.headLoc == tailLoc) {
q2 = p2->next;
G.adj[headLoc].firstArc = q2;
free(p2);
}
else {
//只有一个顶点
if (p2 && p2->next == NULL)
return ERROR;
while (p2 && p2->next->data.headLoc != tailLoc)
p2 = p2->next;
if (!p2)
return ERROR;
q2 = p2->next;
p2->next = q2->next;
free(q2);
q2 = NULL;
}
}
G.arcNum--;
return OK;
}
//删除点
status DeleteVertex(Graph& G, Vertex v) {
int vLoc = VertexLoc(G, v);
if (vLoc == -1)
return ERROR;
for (int i = 0; i < G.verNum; i++)
DeleteArc(G, v, G.adj[i].vertex); //删除由v发出的弧
//如果有向,手动删除发向v的弧
if (G.kind < 2)
for (int i = 0; i < G.verNum; i++)
DeleteArc(G, G.adj[i].vertex, v);
//调整下标
for (int i = 0; i < G.verNum; i++) {
ArcNode* p = G.adj[i].firstArc;
while (p) {
if (p->data.headLoc > vLoc)
p->data.headLoc--;
p = p->next;
}
}
for (int i = vLoc + 1; i < G.verNum; i++)
G.adj[i - 1] = G.adj[i];
G.verNum--;
return OK;
}