图是多对多的结构
图的基本概念为G=(V,E)
V是顶点的集合
E是边的集合
G是图
一个图其实是顶点和边的二元组合
V={V1,V2,V3,V4,V5}
E={(V1,V2),(V1,V3),(V1,V5),(V2,V3),(V2,V5),(V3,V4),(V3,V5),(V4,V5)}
G是V和E的二元组合
这里将无向图和有向图进行对比说明(如果有相同的概念那么不会在有向图中重复书写)
依附:V1到V2这条边就是依附于V1和V2这两个顶点的
n个节点的无向完全图边有Cn2个(n在C的右下角,2在n的左上角)
此图中的极大连通子图为2
n个节点的无向完全图边有An2个(n在C的右下角,2在n的左上角)
将下图分别用邻接矩阵和邻接链表进行存储
如下图
顶点之间没有边的正常应该是无穷这里用0来进行代替
行代表指向谁,如第一行代表V1指向V2,V3,V5
列代表被谁指向,如第一列代表V1被V2,V3,V5指向
如下图
邻接矩阵适用于边多的图
邻接链表适用于边少的图
邻接矩阵是唯一的
邻接链表不唯一
1.创建一个结构体(此结构体中的属性有顶点的个数,边的个数,指向邻接矩阵的指针)
1.得到顶点的个数,边的个数
2.申请一个邻接矩阵(动态申请空间,记得要赋初值)
3.添加边
#include
#include
#include
typedef struct Node {
int nVertex;
int nEdge;
int* pMatrix;
}Graph;
Graph* CreateGraph() {
//申请一个结构体类型的变量
Graph * pGraph= (Graph*)malloc(sizeof(Graph));
int nV;
int nE;
printf("请输入点数\n");
scanf_s("%d", &nV);
printf("请输入边数\n");
scanf_s("%d", &nE);
pGraph->nVertex = nV;
pGraph->nEdge = nE;
//申请矩阵
pGraph->pMatrix= (int*)malloc(sizeof(int) * nV * nE);
memset(pGraph->pMatrix, 0, sizeof(int) * nV * nE);
//添加边
int v1, v2;
int i;
for (int i = 0; i < nE; i++) {
printf("输入两个顶点确定一条边\n");
scanf_s("%d%d", &v1, &v2);
if (v1 != v2 && v1 >= 1 && v1 <= nV && v2 >= 1 && v2 <= nV && pGraph->pMatrix[(v1 - 1) * nV + (v2 - 1)] == 0) {//如果符合创建边的条件,那么创建这条边
pGraph->pMatrix[(v1 - 1) * nV + (v2 - 1)] = 1;
pGraph->pMatrix[(v2 - 1) * nV + (v1 - 1)] = 1;
}
else {//如果输入的两个顶点不符合这个图创建边的条件,那么这里的i变量的值不会增大,以此保证边的总数不变
i--;
}
}
return pGraph;
}
int main() {
Graph* pGraph = CreateGraph();
for (int i = 0; i < pGraph->nVertex * pGraph->nVertex; i++) {
if (i % pGraph->nVertex == 0) printf("\n");
printf("%d ", pGraph->pMatrix[i]);
}
return 0;
}
图的广度优先遍历就是BFS
图的深度优先遍历就是DFS
1.求子集的问题
2.求排列的问题
3.求集合的问题
4.求棋盘的问题
循环嵌套递归
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
//下面代码不是明显的标记写法,但是和标记的思路相同
class Solution {
public:
void BT(vector>& result, vector nums, vector temp, int x) {
//如果这个排列是全排列的一个答案(这里是以是否达到答案排列的长度进行判断的)
if (temp.size() == x) {
result.push_back(temp);
return;
}
//对可能的元素进行选择(每次选择一个元素放到排列的当前位置上)
for (int i = 0; i < nums.size(); i++) {
temp.push_back(nums[i]);
vector::iterator ite = nums.begin();
ite += i;
ite = nums.erase(ite);
//继续看当前排列的下一个位置
BT(result, nums, temp, x);
//为了当前排列当前位置下一个元素的正常判断,进行还原
ite = temp.end()-1;
vector::iterator ite2 = nums.begin();
nums.insert(ite2, *ite);
temp.pop_back();
}
}
vector> permute(vector& nums) {
vector> result;
vector temp;
BT(result,nums,temp,nums.size());
return result;
}
};
1.申请一个标记数组,赋初值
2.从给出的顶点开始遍历
(1)打印当前顶点
(2)对当前顶点进行标记(就是给标记数组相应的位置进行值的修改)
(3)找当前顶点邻接的且未标记的顶点进行处理,重复2
#include
#include
#include
typedef struct Node {
int nVertex;
int nEdge;
int* pMatrix;
}Graph;
Graph* CreateGraph() {
//申请一个结构体类型的变量
Graph* pGraph = (Graph*)malloc(sizeof(Graph));
int nV;
int nE;
printf("请输入点数\n");
scanf_s("%d", &nV);
printf("请输入边数\n");
scanf_s("%d", &nE);
pGraph->nVertex = nV;
pGraph->nEdge = nE;
//申请矩阵
pGraph->pMatrix = (int*)malloc(sizeof(int) * nV * nE);
memset(pGraph->pMatrix, 0, sizeof(int) * nV * nE);
//添加边
int v1, v2;
int i;
for (int i = 0; i < nE; i++) {
printf("输入两个顶点确定一条边\n");
scanf_s("%d%d", &v1, &v2);
if (v1 != v2 && v1 >= 1 && v1 <= nV && v2 >= 1 && v2 <= nV && pGraph->pMatrix[(v1 - 1) * nV + (v2 - 1)] == 0) {//如果符合创建边的条件,那么创建这条边
pGraph->pMatrix[(v1 - 1) * nV + (v2 - 1)] = 1;
pGraph->pMatrix[(v2 - 1) * nV + (v1 - 1)] = 1;
}
else {//如果输入的两个顶点不符合这个图创建边的条件,那么这里的i变量的值不会增大,以此保证边的总数不变
i--;
}
}
return pGraph;
}
void MyDFS(Graph* pGraph, int begin, char* flagArr) {
//打印
printf("%d ", begin);
//标记
flagArr[begin - 1] = 1;
//遍历当前顶点的邻接点
for (int i = 0; i < pGraph->nVertex; i++) {
if (pGraph->pMatrix[(begin - 1) * pGraph->nVertex + i] == 1 && flagArr[i] != 1) {//如果是邻接且未处理的顶点,那么就遍历此顶点
MyDFS(pGraph, i + 1, flagArr);
}
}
}
void DFS(Graph* pGraph,int begin) {
//申请一个标记数组
char* flagArr = (char*)malloc(sizeof(char) * pGraph->nVertex);
memset(flagArr, 0, sizeof(char) * pGraph->nVertex);
MyDFS(pGraph, begin, flagArr);
}
int main() {
Graph* pGraph = CreateGraph();
//进行DFS遍历
DFS(pGraph, 2);
return 0;
}
1.申请一个标记数组和一个队列,给标记数组赋初值
2.将遍历的第一个顶点放入到队列中,并对此顶点进行标记(就是给标记数组相应的位置进行值的修改)
3.将队列中的首元素弹出,输出,将弹出顶点的邻接的且未标记的顶点入队,并对此顶点进行标记,然后重复3,直到队列为空此操作结束
void BFS(Graph* pGraph, int begin) {
if (pGraph == NULL || begin<1 || begin > pGraph->nVertex)return;
//申请一个标记数组
char* flagArr = (char*)malloc(sizeof(char) * pGraph->nVertex);
memset(flagArr, 0, sizeof(char) * pGraph->nVertex);
//申请一个队列
queue qe;
//起点入队
qe.push(begin);
flagArr[begin - 1] = 1;
//对队列进行处理
while (!qe.empty()) {
//弹出 打印
int x = qe.front();
cout<nVertex; i++) {
if (pGraph->pMatrix[(x - 1) * pGraph->nVertex + i] == 1 && flagArr[i] != 1) {
//入队 标记
qe.push(i + 1);
flagArr[i] = 1;
}
}
}
//释放空间
free(flagArr);
flagArr = NULL;
}
int main() {
Graph* pGraph = CreateGraph();
//进行BFS遍历
BFS(pGraph, 1);
return 0;
}
它可以求在有向的、带正权值的图中,点与点之间的最短路径
迪杰斯特拉的实现用到了贪心的算法
在有多个选择的时候不考虑长远的情况,只考虑眼前的这一步,在眼前这一步选择当前的最好的方案
1.画出该图的邻接矩阵如下图
2.我们从上图可以知道V1到各个顶点的路径大小为{X,6,31,15}(注意集合中分别表示V1到各个顶点的距离,到自己本身为X,因为不需要算到自己的距离)
那我们运用贪心的算法取当前V1所到顶点最短的那个顶点V2(且V1到此顶点的最短距离就是当前的这个距离,之后不需要再进行改变了),然后从V1->V2再从V2到其他的各个点(如果此时所得的路径小的话,就对集合进行更新,),得到的新的路径大小为{X,6,31,9}
然后再从集合中选择一个最小的且没有选择过的顶点,这里选择的是V4(且V1到此顶点的最短距离就是当前的这个距离,之后不需要再进行改变了),那此时就是先从V1->V4,再从V4到其他的各个点(如果此时所得的路径小的话,就对集合进行更新),得到的新的路径大小为{X,6,31,9}
重复选择一个最小的且没有选择过的顶点(且V1到此顶点的最短距离就是当前的这个距离,之后不需要再进行改变了),直到所有顶点都被选择过了,就会得到V1到其他各个顶点的最短路径,结束操作
使用迪杰斯特拉的时间复杂度为O(n的平方)
将一个连通图的总长度变成最小的且该图还是一个连通图
克鲁斯卡尔是从边出发进行操作的
1.先将之前图中的所有顶点都画出来
2.对图中所有的边进行排序,选择最小的边,添加到图中(注意此边的连接的两个垫顶点和之前图中的一样),依次选择直到所有的顶点都连上了结束(注意如果将一条添加到图中后,形成了环,那么这条边我们不用,舍弃掉,继续找下一个最小的边)
普利姆是从点出发进行操作的
1.任选一个顶点,然后从当前顶点到另外顶点的边中,找到那条最小的边,然后在图中添加这条边和这条边连接的顶点
2.然后从当前所有顶点到另外所有顶点的边中,找到那条最小的边,然后在图中添加这条边和这条边连接的顶点(注意如果找到的边所连接的那个顶点式已经存在的那么这条边和这个顶点舍弃掉,因为如果使用了那就会形成环了),重复操作2直到所有的顶点都连上了结束
拓扑排序的服务结构是有向无环图(DAG )(全称为Directed Acylic Graph)
注意:它们两个是充分必要条件,拓扑排序能验证它所服务的结构是不是DAG,并且它也只能为DAG服务
为一个项目中具备依赖关系的多个活动求得可执行的线性顺序
小李的课程表如下,你该如何给他安排课程,才能让他把所有的课修完
注意:此图的顶点代表的是活动,边代表的是活动之间的依赖关系,所以此图也叫做AOV网
AVO网和DAG的关系:DAG一定是AOV网,但是AOV网不一定是DAG
看拓扑序列中的顶点总数是不是图中的顶点总数相同,
如果是那么它所服务的结构就是DAG
如果不是那么它所服务的结构就不是DAG
G=(U,V,E)
U和V是两个顶点的集合(U和V各自内部是没有边关系的,U和V这两个集合之间有边关系),E是产生在U和V之间的边的集合
此图不是二部图
看下图进行理解
我们将此图中的顶点进行阵营的划分,对号为一个顶点的集合(第一个阵营),圆圈为另一个顶点的集合(第二个阵营),我们发现此图进行划分阵营后,相同阵营的顶点之间会有连接,所以此图不是二部图
此图是二部图
看下图进行理解
我们将此图中的顶点进行阵营的划分,对号为一个顶点的集合(第一个阵营),圆圈为另一个顶点的集合(第二个阵营)可以发现相同阵营的顶点之间是没有连接的,所以此图是二部图