408数据结构图基于邻接矩阵的基本操作实现以及关于图部分考研学习的学习方法的思考

//实现有向图无向图的基本操作,
        //插入删除顶点,插入删除边
        //设置边权值
        // 找firstedge,firstarchead,firstarctail.nextedge,nextarchead,nextarctail
        // 获取边权值get_weight_edge\getweight_arc
        // 判断边的存在性

关于图这一部分的基本操作,C++语言基于自己想象的需求实现的,花费了3天的时间,其中最重要的两个部分firstneighour、nextneighour的返回值和输入值和即将要实现的算法不一样,

我当时的想法是,不就是随便取一个邻居么,先找一个然后再在它的下一个位置找下一个,但这种算法是“无记忆性”的,而在图的遍历算法中要依靠这个两个基本操作来实现“记忆性”遍历,所有的操作都是基于代码的“记忆性”的,包括在遍历中定义的visited数组也是这一思想的体现,而当时采取的方案就是,我认为我本身只知道firstneighour,我要只基于这个信息找nextneighour写一个解决方案,甚至认为我都不需要知道它的firstneighour是什么就能知道它的nextneighour而沾沾自喜,忽略了代码实现与外部接口的联动,这是一定要杜绝的一点,我们的开发一定要将需求的联动接口在开发之前一定要做良好的规定,否则在开发复杂功能时实现的基础功能接口与期望不同就会造成非常严重的后果,损失时间与精力!

所以对于在那一部分的代码谨以此篇文章作为吃一堑长一智,我在那里返回了firstneighour和nextneighour元素对应在vertex集合中的迭代器,输入的也仅仅是“当前结点”,在nextneibhour中没有添加主语,默认以firstneighour为主语了,这是错误的开发思路,应该还要传入一个参数,“当前结点”,这个结点可以在代码逻辑中不断的变换,这个结点可以不是firstneighour,可以是neighour里的任意一个结点!然后输入这个再求它的下一个结点才对!

另外还要强调一点,实现的目的是为了记住基本操作的接口!

以下代码仅供参考,仅供警示自己。

#pragma once
#include
#include
#include
#include
#define INFINITY pow(2,8*sizeof(int))
using namespace std;

vector error = { '#'};//用于在故障流程中作输出控制


//template
//class Vertex//顶点结构定义
//{
//	friend class Graph;
//public:
//
//
//private:
//	char val;//T可选,比如选char类型或者int类型,简化步骤,注重理解,根据需求随意选一个实现即可
//
//};

//template
class Graph//简单图,无自环无重边//可以是负权图
{
public:
	Graph(vector>*edges,vector*vertexs)
	{
		this->Edge.resize((*edges).size());//size为edge矩阵的行数
		this->Edge = *edges;
		this->Vertex.resize((*vertexs).size());
		this->Vertex = *vertexs;
	}
	
	void printedge();
	void printvertex();

	//实现通过成员函数访问私有属性
	vector& getvertex();
	vector>& getedge();

	//查找并返回顶点val域==A的顶点数组的(迭代器-begin())作为bias,与数组定义的下标一一对应
	int findvertex(char A);//返回偏移量的find,std和algorithm中还定义了一个find是返回目标元素的迭代器

	//获取边/弧权值
	int getweight_edge(char A, char B);
	int getweight_arc(char A, char B);

	设置顶点信息
	//void setvertex(char A);

	//修改从A到B的边的权值
	void setedge(char A,char B,int weight);//改两个位置或者对角线位置只改一次

	//修改从A到B的弧的权值
	void setarc(char A, char B, int weigh);//只需改A行B列的位置

	//判断是否存在边A到B
	bool edgeexist(char A,char B);

	//判断是否存在弧A到B
	bool arcexist(char A,char B);

	//返回与A第一个邻接的顶点
	vector::iterator first_adjacent_edge_vertex(char A);
	vector::iterator next_adjacent_edge_vertex(char A);

	//返回A的第一个出度弧头所指的顶点
	vector::iterator first_adjacent_archead_vertex(char A);

	//返回A的第一个入度弧尾所指顶点
	vector::iterator first_adjacent_arctail_vertex(char A);

	//有向图中A的第二个出度弧头所指邻接的顶点//不存在时我应该如何处理?
	vector::iterator next_adjacent_archead_vertex(char A);
	//有向图中A的第二个入度弧尾所指邻接的顶点
	vector::iterator next_adjacent_arctail_vertex(char A);

	//单纯插入顶点和在边权值矩阵中增加行和列,要设置权值另外设置
	void addvertex(char A);
	//删除结点//从顶点集中删除,另外还要对边权值二维数组定界进行一个pop()删除
	void delvertex(char A);


	//可以用在setarc、setedge中设置鲁棒性来实现边的插入和删除

private:
	//其实在数学表述上是集合,边集、顶点集,
	//不过在实现的时候还是采用有顺序对应关系的vector向量来存储,可以看作有顺序也可以看作无顺序,主要是方便和邻接矩阵的行列坐标作对应关系
	vector Vertex;//用一个专门的顶点表存储Graph的顶点,和vertex的分量有--对应关系,j结点可以不必是char型,可以是自定义类型
	vector> Edge;//邻接矩阵中数组下标为Vertex,而矩阵中的元素为边的权值
	//V_num,E_num顶点数和边数可以通过size()自动获得
};


int Graph::findvertex(char A)
{
	vector::iterator it = this->Vertex.begin();
	while (*it != A&&it!=this->Vertex.end()-1)
		it++;//简化流程了,这里没有结点,顶点集合就是一个非结构体数组里面存的是纯的char
	if (it == this->Vertex.end() - 1&& * it != A)
	{
		//cout << "不存在该节点";
		return -1;
	}
	if ((*it) == '#')
	{
		//cout << "不存在该节点";
		return -1;
	}
	return it - this->Vertex.begin();//顶点数组下标和边二维数组行列下标有对应关系
	
}

int Graph::getweight_edge(char A, char B)
{
	//查找,返回顶点val域==A 和B的顶点数组的迭代器-begin()而后再通过偏移方式(%)找到对应的列,再通过两重迭代器访问和修改
	int bias_i = this->findvertex(A);
	int bias_j = this->findvertex(B);



	if (bias_i == bias_j)
	{
		cout << "该图为简单图,您输入的为自环,不存在自环!" << endl;
		return 0;
	}
	else
	{
		vector>::iterator i = this->Edge.begin() + bias_i;

		//vector>::iterator target = this->Edge.begin()+ this->Vertex.size() * bias_i+bias_j ;//无需

		vector::iterator j = (*i).begin();
		while (j != (*i).begin() + bias_j)//it自动转向下一行数组起始,无需计算前面有几行究竟和整个二维表的第一个位置相比偏移了多少单元
		{
			j++;
		}
		if (j == (*i).begin() + bias_j )
			return *j;
		
			
	}
}
int Graph::getweight_arc(char A, char B)
{
	//查找,返回顶点val域==A 和B的顶点数组的迭代器-begin()而后再通过偏移方式(%)找到对应的列,再通过两重迭代器访问和修改
	int bias_i = this->findvertex(A);
	int bias_j = this->findvertex(B);



	if (bias_i == bias_j)
	{
		cout << "该图为简单图,您输入的为自环,不存在自环!" << endl;
		return 0;
	}
	else
	{
		vector>::iterator i = this->Edge.begin() + bias_i;

		//vector>::iterator target = this->Edge.begin()+ this->Vertex.size() * bias_i+bias_j ;//无需

		vector::iterator j = (*i).begin();
		while (j != (*i).begin() + bias_j)//it自动转向下一行数组起始,无需计算前面有几行究竟和整个二维表的第一个位置相比偏移了多少单元
		{
			j++;
		}
		if (j == (*i).begin() + bias_j)
			return *j;
	}

}

bool Graph::edgeexist(char A, char B)//只需找一个位置是因为对称存储
{
	int bias_i = this->findvertex(A);
	int bias_j = this->findvertex(B);
	vector>::iterator i = this->Edge.begin() + bias_i;
	vector::iterator j = (*i).begin();
	while (j != (*i).begin() + bias_j)//it自动转向下一行数组起始,无需计算前面有几行究竟和整个二维表的第一个位置相比偏移了多少单元
	{
		j++;
	}
	if (j == (*i).begin() + bias_j&&*j!=0)
		return true;
	return false;

}

bool Graph::arcexist(char A, char B)//只需找A到B是因为有方向
{
	int bias_i = this->findvertex(A);
	int bias_j = this->findvertex(B);
	vector>::iterator i = this->Edge.begin() + bias_i;
	vector::iterator j = (*i).begin();
	while (j != (*i).begin() + bias_j)//it自动转向下一行数组起始,无需计算前面有几行究竟和整个二维表的第一个位置相比偏移了多少单元
	{
		j++;
	}
	if (j == (*i).begin() + bias_j&&*j!=0)
		return true;
	return false;

}


void Graph::setarc(char A,char B,int weight)
{
	//查找,返回顶点val域==A 和B的顶点数组的迭代器-begin()而后再通过偏移方式(%)找到对应的列,再通过两重迭代器访问和修改
	int bias_i = this->findvertex(A);
	int bias_j = this->findvertex(B);


	if (bias_i == -1 || bias_j == -1)
	{
		cout << "禁止设置不存在的顶点的边!"<>::iterator i = this->Edge.begin() + bias_i;

		//vector>::iterator target = this->Edge.begin()+ this->Vertex.size() * bias_i+bias_j ;//无需

		vector::iterator j = (*i).begin();
		while (j != (*i).begin() + bias_j)//it自动转向下一行数组起始,无需计算前面有几行究竟和整个二维表的第一个位置相比偏移了多少单元
		{
			j++;
		}
		if (j == (*i).begin() + bias_j)
			*j = weight;
	}
	
}

void Graph::setedge(char A, char B, int weight)//改两个位置或者对角线位置只改一次
{
	//从A到B
	int bias_i = this->findvertex(A);
	int bias_j = this->findvertex(B);
	//设置
	if (bias_i == -1 || bias_j == -1)
	{
		cout << "禁止设置不存在的顶点的边!" << endl;
		return;
	}
	else if (bias_i == bias_j)
	{
		cout << "禁止设置自环的权值!" << endl;
		return;
	}
	else
	{
		vector>::iterator i = this->Edge.begin() + bias_i;

		//vector>::iterator target = this->Edge.begin()+ this->Vertex.size() * bias_i+bias_j ;//无需

		vector::iterator j = (*i).begin();
		while (j != (*i).begin() + bias_j)//it自动转向下一行数组起始,无需计算前面有几行究竟和整个二维表的第一个位置相比偏移了多少单元
		{
			j++;
		}
		if (j == (*i).begin() + bias_j)
			*j = weight;
		//从B到A
		if (bias_i != bias_j)//对角线位置只改一次
		{
			swap(bias_i, bias_j);//C++自带
			//bias_j = this->findvertex(A);
			//bias_i = this->findvertex(B);
			i = this->Edge.begin() + bias_i;

			//vector>::iterator target = this->Edge.begin()+ this->Vertex.size() * bias_i+bias_j ;//无需

			j = (*i).begin();
			while (j != (*i).begin() + bias_j)//it自动转向下一行数组起始,无需计算前面有几行究竟和整个二维表的第一个位置相比偏移了多少单元
			{
				j++;
			}
			if (j == (*i).begin() + bias_j)
				*j = weight;

		}
	}
	

	
	

}

vector& Graph::getvertex()
{
	return this->Vertex;
}

vector>& Graph::getedge()
{
	return this->Edge;
}
//template
void Graph::printvertex()
{
	for (vector::iterator it = this->Vertex.begin(); it != this->Vertex.end(); it++)
	{
		cout << *it << " ";
	}
}

void Graph::printedge()
{
	for (vector>::iterator it = this->Edge.begin(); it != this->Edge.end(); it++)
	{
	//(*it)   是小容器vector
		for (vector::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
		{
			cout << *vit << " ";
		}
		cout << endl;

	}
}


//有向图中A的第二个出度弧头所指邻接的顶点
vector::iterator Graph::next_adjacent_archead_vertex(char A)
{
	int bias_A = this->findvertex(A);
	//A顶点第一个出度弧头顶点假设为B,从B的下一个位置开始横向查找第一个权值不为0的结点
	int bias_B = this->first_adjacent_archead_vertex(A) - this->Vertex.begin();

	//出度结点应该在A行中横向查找,记录bias
	//先找到A对应的行,再从A的第一个邻接顶点处开始向右遍历,直到第一个权值不为0的边,返回该边对应的顶点的迭代器否则抛出异常
	vector>::iterator i = this->Edge.begin() + bias_A;//在A所在的行上从B位置开始向右找
	vector::iterator j = (*i).begin() + bias_B + 1;//注意,从B位置的下一个顶点开始向右找
	while ((*j) == 0 && j <(*i).end()-1)
	{
		j++;
	}
	if (j == (*i).end()-1&& (*j) == 0)
	{
		cout << A << "没有A的第二个出度弧头所指邻接的顶点" << endl;
		vector::iterator temp = error.begin();
		return temp;
		
	}
	else
	{
		int next_bias = j - (*i).begin();
		return this->Vertex.begin() + next_bias;
	}
}
//有向图中A的第二个入度弧尾所指邻接的顶点
vector::iterator Graph::next_adjacent_arctail_vertex(char A)
{
	int bias_A = this->findvertex(A);
	//A顶点第一个邻接入度弧尾顶点假设为B,从B的下一个位置开始竖向查找第一个权值不为0的结点
	int bias_B = this->first_adjacent_arctail_vertex(A) - this->Vertex.begin();

	//入度结点应该在A行中竖向查找,记录bias
	//先找到A对应的行,再从A的第一个邻接顶点处开始向右遍历,直到第一个权值不为0的边,返回该边对应的顶点的迭代器否则抛出异常
	vector>::iterator i = this->Edge.begin() + bias_B+1;//注意,从B位置的下一个顶点开始向右找
	vector::iterator j = (*i).begin() + bias_A ;//在A所在的行上从B位置开始向右找
	while ((*j) == 0&& i < this->Edge.end()-1)
	{
		i++;
	}
	if (i== this->Edge.end()-1&&(*j)==0)
	{
		cout << A << "没有A的第二个入度弧头所指邻接的顶点" << endl;
		vector::iterator temp = error.begin();
		return temp;
	}
	else
	{
		int next_bias =i-this->Edge.begin();
		return this->Vertex.begin() + next_bias;
	}

}


//返回与A第一个邻接的顶点
vector::iterator Graph::first_adjacent_edge_vertex(char A)
{
	//先找A在哪个位置而后在边矩阵中定位该行,返回第一个不为0的vertex
	int bias_i = this->findvertex(A);
	vector>::iterator i = this->Edge.begin() + bias_i;//定位该行
	//返回第一个不为0的vertex
	vector::iterator j = (*i).begin();
	while (*j == 0)
	{
		j++;
	}
	if (j == (*i).end())
	{
		cout << "该节点为孤立结点";
		vector::iterator temp = error.begin();
		return temp;
	}
	else 
	{
		int bias_j = j - (*i).begin();

		return this->Vertex.begin() + bias_j;//统一在顶点集中定位和返回,就是说,顶点集啊,它通过代码逻辑使他逻辑上变成了图,
											//所有顶点的插入和操作都应该在顶点集中完成顶点的数据部分,逻辑部分就是修改边的关系,这就给出插入删除操作的逻辑实现的方法
	}


}
//返回A的第一个出度弧头所指的顶点
vector::iterator Graph::first_adjacent_archead_vertex(char A)
{
	//先找A在哪个位置而后在边矩阵中定位该行,返回第一个不为0的vertex
	int bias_i = this->findvertex(A);
	vector>::iterator i = this->Edge.begin() + bias_i;//定位该行
	//返回第一个不为0的vertex
	vector::iterator j = (*i).begin();
	while (*j == 0)
	{
		j++;
	}
	if (j == (*i).end())
	{
		cout << "该顶点没有出度";
		vector::iterator temp = error.begin();//故障流程控制
		return temp;
		
	}
	else
	{
		int bias_j= j - (*i).begin();

		return this->Vertex.begin() + bias_j;
	}
}

//返回A的第一个入度弧尾所指顶点
vector::iterator Graph::first_adjacent_arctail_vertex(char A)
{
	//先找A在哪个位置而后在边矩阵中定位该列
	int bias_j=this->findvertex(A);
	//而后每行都找,返回第一个bias_j处不为零的行对应的Vertex,若找到最后行指针指向end()则说明没有入度
	vector>::iterator i = this->Edge.begin();
	
	while( i < this->Edge.end())
	{
		vector::iterator j = (*i).begin() + bias_j;
		if (*(j) != 0)
		{
			int bias_i = i - this->Edge.begin();
			return this->Vertex.begin() + bias_i;
		}
		i++;
	}
	if (i == this->Edge.end())
	{
		cout << "该顶点不存在入度" << endl;
		vector::iterator temp = error.begin();
		return temp;
	}

	
	
}

//A顶点邻接第一个顶点的下一个顶点,如果有则返回该顶点在vertex中的迭代器,若没有则输出文字(抛出异常)
vector::iterator Graph::next_adjacent_edge_vertex(char A)
{
	
	int bias_A = this->findvertex(A);
	//A顶点第一个邻接顶点假设为B
	int bias_B =this->first_adjacent_edge_vertex(A)-this->Vertex.begin();
	//先找到A对应的行,再从A的第一个邻接顶点处开始向右遍历,直到第一个权值不为0的边,返回该边对应的顶点的迭代器否则抛出异常
	vector>::iterator i = this->Edge.begin() + bias_A;//在A所在的行上从B位置开始向右找
	vector::iterator j = (*i).begin()+bias_B+1;//注意,从B位置的下一个顶点开始向右找
	while ((*j) == 0 && j < (*i).end() - 1)
	{
		j++;
	}
	if (j == (*i).end()-1&&(*j)==0)
	{
		cout << A<<"没有下一个邻接顶点" << endl;
		vector::iterator temp = error.begin();
		return temp;
	}
	else
	{
		int next_bias = j -(*i).begin();
		return this->Vertex.begin() + next_bias;
	}

}

//单纯插入顶点和在边权值矩阵中增加行和列,要设置权值另外设置
void Graph::addvertex(char A)
{
	if (this->findvertex(A) != -1)
	{
		cout << "该节点已存在,插入失败" << endl;
		return;
	}
	//在结点集中插入
	this->Vertex.push_back(A);

	//在边权矩阵中插入
	vector>::iterator i = this->Edge.begin();
	while (i <=this->Edge.end() - 1)
	{
		//在每一行的后插入一个0权
		(*i).push_back(0);
		i++;
	}
	//新的行大小
	int i_size = (*this->Edge.begin()).size();
	//申请一块新的vector向量,大小为新的行大小
	vector* newvec = new vector(i_size, 0);//必须在这个地方申请
	this->Edge.push_back(*newvec);//把这个插入到边权矩阵的下方
}
//删除结点//从顶点集中删除,另外还要对边权值二维数组定界进行一个pop()删除
void Graph::delvertex(char A)
{
	int bias = this->findvertex(A);
	if (bias== -1)
	{
		cout << "无该节点,无法删除" << endl;
		return;
	}
	//vector中删除中间结点


	//在边权矩阵中删除
	vector>::iterator i = this->Edge.begin();
	while (i <= this->Edge.end() - 1)
	{
		//每行删除vector内部结点
		(*i).erase((*i).begin() + bias);
		i++;
	}
	this->Edge.erase(this->Edge.begin() + bias);//删除vector>内部的vector
	this->Vertex.erase(this->Vertex.begin() + bias);//最后再在顶点集中删除
	

}
#define _CRT_SECURE_NO_WARNINGS
#include"graph_adjacent_matrix.hpp"



int main()
{
	//理解二维数组的vector实现代码
	//vector> a(25, vector (5,0));//这是一个初始化元素全部为0的25*5的数组,
	//											//前一个位置是二维数组的行数(a.size()指示a矩阵一个列向量中有几个元素)
	//											//后一个位置的括号中的5指示a数组的列数(指示a矩阵一个行向量中元素个数)
	//
	vector二维数组的删除操作测试
	//a.pop_back();
	//cout << a.size();
	//cout << endl;
	//cout << a.end() - a.begin();
	//vector>::iterator i = a.begin();
	//while (i <= a.end() - 1)
	//{
	//	//只需要pop即可
	//	(*i).pop_back();
	//	i++;
	//}
	//i = a.begin();
	//cout << endl;

	//cout << (*i).size();
	//cout << endl;
	//cout << (*i).end() - (*i).begin();
	//cout << endl;

	vector二维数组的插入操作测试
	//vector newvertex(4,0);
	//a.push_back(newvertex);
	//cout << a.size();
	//cout << endl;
	//cout << a.end() - a.begin();
	// i = a.begin();
	//while (i <= a.end() - 1)
	//{
	//	//只需要pop即可
	//	(*i).push_back(0);
	//	i++;
	//}
	//i = a.begin();
	//cout << endl;

	//cout << (*i).size();
	//cout << endl;
	//cout << (*i).end() - (*i).begin();


	//遍历输出二维数组a
	//for (vector>::iterator it = a.begin(); it != a.end(); it++)
	//{
	//	//(*it)   是小容器vector
	//	for (vector::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
	//	{
	//		cout << *vit << " ";
	//	}
	//	cout << endl;

	//}
	//cout << a.size();//是二维数组a的行数(一列的元素个数),
	//				//在a的定义a(25, vector (5,0))后一个位置的括号中的5指示a数组的列数(一行的元素个数)

	//以上为理解vector实现的二维数组代码
	//------------------------------------------------------------------
	//有向图待实现算法:
		//DFS
		//BFS
		//Dijkstra
		//Floyd
		//拓扑排序
		//逆拓扑排序
		//关键路径
		
	//无向图待实现算法:
		//生成树
		//BFS
		//DFS
	//--------------------------------------------------
	//cout << INFINITY;

	//-----------------------------------------------------------
	//以下为有向图无向图的基本操作,
		//插入删除顶点,插入删除边
		//设置边权值
		// 找firstedge,firstarchead,firstarctail.nextedge,nextarchead,nextarctail
		// 获取边权值get_weight_edge\getweight_arc
		// 判断边的存在性
	有向图
	vector a={ 'A', 'B', 'C', 'D', 'E', 'F' };
	vector* digraph1_vertex = new vector(a.size(), '#');
	vector>* digraph1_arc=new vector>(a.size(), vector (a.size(), 0));//每个结点初始设置为0可能有些问题,主对角线全0没问题,其余的应该代表“不可达”如何实现?
	//定义INIFINITY为无穷大,不可达,为int类型表示的最大值
	//调整主对角线位置设置为0
	//简化流程暂时不设置,因为不好打印,就用0表示不可达也是完备的,因为自己到自己为0也就是不可达,说的通。


	*digraph1_vertex = a;
	//简化了流程,没有设置setvertex修改顶点信息的功能



	Graph digraph1(digraph1_arc, digraph1_vertex);//其实应该把这个开辟在堆上,这个语句是把他开辟在栈上
	digraph1.printedge();
	
	//cout << endl;
	//digraph1.printvertex();
	//cout << endl;

	//设置弧的权值
	digraph1.setarc('A', 'B', 1);
	digraph1.setarc('C', 'A', 1);
	digraph1.setarc('D', 'A', 1);
	digraph1.setarc('E', 'B', 1);
	digraph1.setarc('E', 'C', 1);
	digraph1.setarc('F', 'B', 1);
	digraph1.setarc('F', 'D', 1);

	digraph1.printedge();

	//判断是否存在弧
	cout << digraph1.arcexist('A', 'B')<* undigraph1_vertex = new vector(a.size(), '#');
	vector>* undigraph1_arc= new vector>(a.size(), vector (a.size(), 0));
	*undigraph1_vertex = a;

	Graph undigraph1(undigraph1_arc, undigraph1_vertex);
	
	undigraph1.printedge();
	cout << endl;
	undigraph1.printvertex();
	cout << endl;

	//设置边的权值
	undigraph1.setedge('A', 'B', 1);
	undigraph1.setedge('A', 'C', 1);
	undigraph1.setedge('A', 'D', 1); 
	undigraph1.setedge('B', 'E', 1);
	undigraph1.setedge('B', 'F', 1);
	undigraph1.setedge('C', 'E', 1);
	undigraph1.setedge('D', 'F', 1); 
	

	undigraph1.printedge();
	//判断是否存在边
	cout << undigraph1.arcexist('A', 'C')<

你可能感兴趣的:(C++编程,图,数据结构,学习,学习方法)