ディレクトリ
図は、隣接行列によって実装しました
図は、隣接行列実装構造は次のように定義されます
typedef struct graph *Graph; //Graph是一个类型,为graph型指针
struct graph
{
WItem NoEdge;//无边标记
int n; //顶点数
int e; //边数
WItem **a; //邻接矩阵,a是一个二级指针
}AWDgraph;
関数GraphInit(N、noEdge)Nの単離された頂点を作成します
Graph GraphInit(int n,WItem noEdge) //返回指向一个图的指针
{
Graph G=malloc(sizeof*G); //G是变量名,sizeof要加*
G->n=n;
G->e=0;
G->a=Make2DArray(G->n+1,G->n+1,noEdge); //创建一个权值全为noEdge的矩阵
//不赋权图将上一句的NoEdge改为0
return G;
}
関数GraphVerticles(G)及びGraphEdges(G)は、それぞれの頂点の数を返し、グラフGのエッジ
int GraphVerticles(graph G)
{
return G->n;
}
int GraphEdges(graph G)
{
return G->e;
}
関数GraphExits(I、J、G)は、現在のエッジグラフG(i、j)の存在下または非存在下で決定されます
int GraphExits(int i,int j,graph G)
{
if(i<1||j<1||i>G->n||j>G->n||G->a[i][j]==G->NoEdge) return 0;
//输入不在合法范围内或该边值为NoEdge
//不赋权图把if条件中的NoEdge改为0
return 1;
}
関数GraphAdd(I、J、W、 G)は、 G(i、j)のエッジを結合します
void GraphAdd(int i,int j,WItem w,Graph G)
{
if(i<1||j<1||i>G->n||j>G->n||i==j||G->a[i][j]!=G->NoEdge)
//若输入不在合法范围或在对角线上或该边已经存在,则报错
//不赋权图把if条件中的NoEdge改为0
Error("Bad input");
G->a[i][j]=w;
//G->a[j][i]=w; 无向图加一句这个
//不赋权图只需要将上述语句的w改成1
G->e++; //更新边数
}
関数GraphDeleteは(I、J、G)Gのエッジ(i、j)を消去します
void GraphDelete(int i,int j,graph G)
{
if(i<1||j<1||i>G->n||j>G->n||G->a[i][j]==G->NoEdge)
//输入不在合法范围内或待删边本不存在
//不赋权图把if条件中的NoEdge改为0
Error("Bad input");
G->a[i][j]=NoEdge;
//G->a[j][i]=NoEdge; 无向图加一句这个
//不赋权图只需要将上述语句的w改成0
G->e--; //更新边数
}
関数入次数(I、G)はグラフGの頂点の度合いを返します
int InDegree(int i,graph G)
{
int sum=0;
/*可单独作为求入度的方法*/
for(int j=0;j<G->n;j++)
{
if(G->a[i][j]!=NoEdge) //无向图改NoEdge为0
sum++;
}
/*如果是无向图加上以下代码*/
/*也可单独作为求出度的方法*/
for(int j=0;j<G->n;j++)
{
if(G->a[j][i]!=NoEdge) //无向图改NoEdge为0
sum++;
}
/**/
return sum;
}
図は、隣接リストを使用して実装しました
ノード隣接リスト構造の定義
typedef struct lnode *glink; //glink是一个类型,为lnode型指针
struct lnode
{
int v; //边的另一个顶点
glink next; //邻接表指针
//若是赋权图,加上w属性
//WItem w;
};
新しいノードの隣接リストを作成します。
glink NewLNode(int v,glink next) //返回指向lnode的指针
{
glink x=malloc(sizeof*x); //x是变量名,sizeof取变量名长度要加*
x->v=v;
x->next=next;
//若是赋权图
//x->w=w;
return x;
}
図は、定義された隣接するテーブル構造を使用して実装しました
typedef struct graph *Graph;
struct graph
{
int n; //顶点数
int e; //边数
glink *adj; //glink型的一维数组,数组中的成员都是指针
}Lgraph;
図は、単離された頂点関数GraphInit(n)はn個と隣接表されるテーブルを作成します
Graph GraphInit(int n)
{
Graph G=malloc(sizeof*G) //G是变量名,sizeof前加*
G->n=n;
G->e=0;
G->adj=malloc(n+1)*sizeof(glink)) //adj是一个指针数组,成员都是glink指针,v此时是0
for(int i=0;i<=n)
G->adj[i]=0;
return G;
}
関数GraphVerticles(G)及び機能GraphEdge(G)は、わずかに図の隣接行列の文言と頂点と辺の数を返します。
関数GraphExist(I、J、G)グラフGのエッジが現在存在するかどうかを決定する(i、j)は
int GraphExist(int i,int j,graph G)
{
if(i<1||j<1||i>G->n||j>G->n) return 0;
glink p=G->adj[i];
while(p&&p->v!=j) p=p->next; //不走到最后且不为目标,则继续向前
if(p) return 1;
else return 0;
}
関数GraphAdd(I、J、G)のエッジ(i、j)の数値を達成するために加え、隣接する頂点Iの標本の最初の挿入頂点jによって作動されます
void GraphAdd(int i,int j,Graph G) //赋权图加上WItem w
{
if(i<1||j<1||i>G->n||j>G->n||i==j||GraphExits(i,j,G))
Error("Bad input");
G->adj[i]=NewLNode(j,G->adj[i]); //直接取代G->a[i]
//无向图加上下一句
//G->adj[j]=NewLNode(i,G->adj[j]);
//赋权图加上w
//G->adj[i]=NewLNode(j,w,G->adj[i]);
G->e++;
}
関数GraphDelete(I、J、G)エッジグラフG(i、j)を削除します
void GraphDelete(int i,int j,Graph G)
{
glink p,q;
if(i<1||j<1||i>G->n||j>G->n||!GraphExists(i,j,G))
{
Error("Bad input");
}
p=G->adj[i];
if(p->v==j) //如果第一个节点就是待删除节点,直接令其成为邻接表数组中的成员
{
G->adj[i]=p->next;
free(p);
}
else
{
while(p&&p->v!=j) p=p->next; //若未走到最后且未找到目标则继续向前
if(p) //若找到目标
{
q=p->next;
p->next=q->next;
free(q);
}
}
//有向图加上下面的代码
/*
p=G->adj[j];
if(p->v==i) //如果第一个节点就是待删除节点,直接令其成为邻接表数组中的成员
{
G->adj[j]=p->next;
free(p);
}
else
{
while(p&&p->v!=i) p=p->next; //若未走到最后且未找到目标则继续向前
if(p) //若找到目标
{
q=p->next;
p->next=q->next;
free(q);
}
}
*/
G->e--;
}
関数出次数(I、G)は、頂点の隣接リストによって算出iが長く、グラフGの頂点への復帰があります
int OutDegree(int i,Graph G)
{
glink p;
int sum=0;
if(i<1||i>G->n) Error("Bad input");
p=G->adj[i];
while(p)
{
sum++;
p=p->next;
}
return sum;
}
IグラフGの頂点の次数を持つ関数入次数(I、G)戻ります
int InDegree(int i,Graph G)
{
int sum=0;
for(int j=0;j<G->n;j++)
{
if(GraphExists(i,j,G))
sum++;
}
return sum;
}
BFS
//用邻接矩阵实现的无向图G中的广度优先搜索算法bfs描述
bfs(G,i)
{
/*从顶点v开始,广度优先搜索图G的算法*/
标记顶点i;
用顶点i初始化顶点队列Q;
while(!QueueEmpty(Q))
{
i=DeleteQueue(Q);
设j是i的邻接顶点;
while(j)
{
if(j未标记)
{
标记顶点j;
EnterQueue(j,Q);
}
j=i的下一个邻接顶点
}
}
}
===================================================================
//具体实现时,用一个数组pre来记录搜索到的顶点的状态。初始时对所有顶点v有pre[v]=0。
//用一全局变量cnt记录算法对图中顶点的访问次序。算法结束后,数组pre[i]中的值是算法访问顶点i的序号。
void bfs(Graph G,int i)
{
Queue Q=QueueInit();
EnterQueue(i,Q); //顶点先入队
while(!QueueEmpty(Q))
{
if(pre[i=DeleteQueue(Q)]==0) //未访问过
{
pre[i]=cnt++; //访问该结点并标记
for(int j=1;j<=G->n;j++)
{
if(G->adj[i][j]&&pre[j]==0) //若边存在且未访问过,入队
EnterQueue(j,Q);
}
}
}
}
//图中可能不止一个连通分量,遍历全图算法如下
void GraphSearch(Graph G)
{
cnt=1;
for(int i=1;i<=G->n;i++) pre[i]=0;
for(int i=1;i<=G->n;i++)
{
if(pre[i]==0)bfs(G,i); //对每个连通分量调用一次bfs
}
}
//天勤上面用邻接表表示图的bfs写法
int visit[MAXSIZE]=0; //访问状态数组
void bfs(Graph *G,int v)
{
ArcNode *p;
visit[v]=1;
//Visit(v);
Queue<int> q;
q.push(v);
while(!IsEmpty(q))
{
int t=q.top();
q.pop();
p=G->adj[t].firstarc; //p指向当前结点第一条边
while(p)
{
if(visit[p->adjvex]==0)
{
visit[p->adjvex]=1;
//Visit(p->adjvex);
q.push(p->adjvex); //若当前邻接顶点未被访问过,则置为已访问,并压入队列
}
p=p->nextarc; //否则,p指向当前结点的下一条邻边
}
}
}
void BFS(Graph *G)
{
for(int i=0;i<G->n;i++)
if(visit[i]==0)
bfs(G,i);
}
深さ優先探索
//用邻接矩阵实现的无向图G中的深度优先搜索算法dfs如下
void dfs(Graph G,int i)
{
pre[i]=cnt++; //初始化时所有结点pre[i]=0,当pre[i]不为0使表示该结点已访问过,pre[i]的值为算法访问结点i的序号
for(int j=1;j<=G->n;j++) //顺序遍历与结点i相邻的结点,
{
if(G->[i][j]) //若边<i,j>存在
{
if(pre[j]==0)dfs(G,j); //若结点j还未被访问过,对j递归调用dfs,若访问过,j++探测下一个相邻结点
}
}
}
//用邻接表实现的有向图G中的深度优先搜索算法dfs如下
void dfs(Graph G,int i)
{
glink p;
pre[i]=cnt++;
for(p=G->adj[i];p;p=p->next)
{
if(pre[i]==0)dfs(G,p->v); //若p指向的结点已访问过,结束当前层次的递归,返回上一层递归,执行p=p->next探测下一个相邻结点
}
}
void GraphSearch(Graph G)
{
cnt=1;
for(int i=1;i<=G->n;i++) pre[i]=0;
for(int i=1;i<=G->n;i++) if(pre[i]==0) dfs(G,i);
}
//天勤上面用邻接表表示图的dfs写法
int visit[MAXSIZE]=0; //全局变量用来保存访问状态
int dfs(Graph *G,int v)
{
ArcNode *p;
visit[v]=1; //置当前结点为已访问
Visit(v);
p=G->adj[v].firstarc; //p指向顶点v的第一条边
while(p)
{
if(visit[p->adjvex])==0 //若当前结点未被访问过,则往深处继续访问
{
dfs(G,p->adjvex)
}
p=p->nextarc; //否则检测下一个与v邻接的结点
} //执行到这一步时,所有与v邻接的结点已经检查完毕,该层dfs也执行到了最后一句,自动返回上一层递归
}
void DFS(Graph *G)
{
for(int i=0;i<G->n;i++)
if(visit[i]==0)
dfs(G,i);
}
//天勤上判断图G是否是树的算法,基于dfs
int vc=0,ec=0; //定义全局变量vc和ec,就不用在函数里面传了
int visit[MAXSIZE]=0;
void dfs(Graph *G,int v)
{
ArcNode *p;
visit[v]=1; //置当前结点为已访问
vc++; //结点数自增1
p=G->adj[v].firstarc;
while(p)
{
if(visit[p->adjvex]==0) //若当前顶点的邻接点未被访问过,则访问它,并对ec自增1
{
ec++;
dfs(G,p->adjvex);
}
p=p->next;
}
}
int IsTree(Graph *G)
{
dfs(G,1);
if(vc==G->n&&(ec/2)==G->e) //若vc等于图中顶点数,ec等于图中边数的2倍
return 1;
return 0;
}
//由于每次访问顶点ec都会累加上之前所有与访问过的结点关联的边数,因此遍历完之后,相当于ec中的数值是图的度数,(一条边2个度),因此ec/2就是访问过的边数。
最短パス
ダイクストラ法(単一始点最短経路)
アルゴリズムのアイデア:
頂点Sのセットを設定し、そして貪欲としてこのコレクションを拡大していきます。集合に属する頂点は、ソース頂点からの最短経路の長さを有する場合にのみ、S。初期の、Sはソースだけが含まれています。Uは、Gのみ、UおよびU中間体ソースからの特別なパスするソースの頂点Sを通る経路の頂点であり、現在の頂点の各々に対応する最短経路の長さを記録するための特別な配列DISTで処理しましょう。uはVSから除去される各頂点のダイクストラ法は、アレイは、必要な変更を加えDISTながらUは、集合Sに追加され、最短特別な経路長を有しています。一旦V Sは、ソース頂点からのすべての他の間の最短経路長のレコード上のすべての頂点、DISTを含んでいます。
アルゴリズムの説明:
ステップ1:初期化DIST [V] = [S] [V]
すべての頂点が反対の事前に隣接Vは[V] = S。
他の頂点の集合Uプレ[U] = 0;
表Lは[V]≠頂点v 0は初期時間全て確立プレ含ま@すなわち、L sはソースに直接隣接する頂点であります
ステップ2:空のリストL場合、アルゴリズムは終了するが、そうでない場合は、ステップ3に進みます
ステップ3:頂点リストLから最新のつまりソース頂点v // S DISTの最小値を削除します
ステップ4:頂点vに隣接するすべての頂点のuはDIST対向[U] =分{DIST [U]、DIST [V] + [V] [U]}
DIST [U]変更した場合、すなわち、特別な頂点vパスの添加が短くなった後、設定されたプリ[U] = V;リストLにおいてUは、L uが付加されていない場合には、ステップ2に進み
//在用邻接矩阵实现的赋权有向图中,单源最短路径问题的Dijkstra算法实现如下
void Dijkstra(int s,WItem dist[],int prev[],Graph G)
{
int i,j;
List L=ListInit(); //初始化L表
if(s<1||s>G->n) Error("Out of bounds") //处理非法输入
/*初始化dist,prev和L*/
for(int i=1;i<=G->n;i++)
{
dist[i]=a[s][i]; //顶点i到源点s的初始距离为a[s][i]的值
if(dist[i]==G->NoEdge) prev[i]=0; //若顶点i与源点s之间不存在边,则置i的前驱结点为0
else{prev[i]=s;ListInsert(0,i,L);} //否则,置i的前驱结点为s,并将顶点i加入L表,参数啥意思?
}
dist[s]=0; //源点s的前驱为0
/*修改dist和prev*/
while(!ListEmpty(L))
{
/*找L中具有最小dist值得顶点v*/
/*将顶点v从表L中删除,并修改dist的值*/
i=ListDelMin(L,dist); //顶点i是L表中所有已知特殊路径长度的结点中路径最短的
for(int j=1;j<=G->n;j++) //遍历所有与顶点i相邻的顶点
{
if(G->a[i][j]!=G->NoEdge&&(!prev[j]||dist[j]>dist[i]+G->a[i][j])) //若边(i,j)存在,且加入结点i之后,结点j到源点s的距离缩短
{
/*dist减少*/
dist[j]=dist[i]+G->a[i][j]; //更新结点j到源点s的特殊路径距离
/*顶点j插入表L*/
if(!prev[j]ListInsert(0,j,L)) //结点j的特殊路径长度已知,加入表L
prev[j]=i; //更新结点j的前驱
}
}
}
}
//天勤上的写法,我觉得更好理解一些
void Dijkstra(Graph G,int v,int dist[],int path[]) //dist是v到某点vu的距离,path是vu到v最短路径上前驱结点的下标,dist是一维数组,因为将它看作二维数组时,有一维是不变的,下标都是v
{
int sex[MAXSIZE]; //set记录顶点是否被加入路径,取值为1则为加入,0为未加入
/*以下是对各数组进行初始化*/
for(int i=0;i<G.n;i++)
{
dist[i]=G.edges[v][i];
path[i]=v;
set[i]=0;
}
set[v]=1;
/*初始化完毕*/
/*以下是找到剩余结点中与已生成的路径距离最短的结点*/
int min=INF;
for(int i=0;i<G.n;i++)
{
if(set[i]==0&&dist[i]<min)
{
u=i;
min=G.dist[i];
}
}
set[u]=1;
/*找到该结点后,加入路径中*/
/*加入顶点u到路径中之后,更新dist和path*/
for(int i=0;i<G.n;i++)
{
if(set[i]==0&&dist[u]+G.edges[u][i]<dist[i])
{
dist[i]=dist[u]+G.edges[u][i];
path[i]=u; //u为i在路径上的前驱
}
}
}
フロイドのアルゴリズム(任意の2点間の最短経路)
アルゴリズムのアイデア:
行列Cに配置され、最初にC [I] [J] = [I] [J]。次いで、C-マトリックス行うN回の反復、最初の反復kの後、C [I] [J]私頂点から頂点jへの値であり、そして中間経路長を通過しない頂点の数kよりも大きいです。Cのk番目の反復を行う、C [I] [J] =分{C [i]と[K] + C [K] [J]、C [I] [J]}を計算C [iを、 ] [j]は、現在のC [I] [J]とC [i]は[K] + C [K] [j]の大きさと比較することができます。iは中間頂点番号はK-1の最短経路長より大きくない、jに頂点から示すC [I] [J]の値現在; [i]とC [K] + C [K] [j]は、頂点Iを表します次いでkからjに、kに、中間経路長を通過しないと、頂点kの数よりも大きいです。
二次元アレイ経路は最短経路を記録するために使用されます。kは最小値の整数を取得する操作C [i] [j]がある場合は、P [I] [J] = kに設定されています。経路[I] [j]が0 =とき、iはjにノードからの最短経路を表し、IからJへのエッジです。簡単に対応する最短経路を見つけるためのパス情報によって記録されたC [I] [j]の値を算出した後。
void Floyd(WItem **c,int **path,Graph G)
{
/*初始化c[i][j]*/
for(int i=1;i<=G->n;i++)
for(int j=1;j<=G->n;j++)
{
if(i==j)
c[i][j]=0;
else
{
c[i][j]=G->a[i][j];
path[i][j]=0; //path=0表示当前顶点i到顶点j的最短路径就是它们之前的边
}
}
for(int k=1;k<=G->n;k++)
for(int i=1;i<=G->n;i++)
for(int j=1;j<G->n;j++)
{
if(c[i][k]!=NoEdge&&c[k][j]!=NoEdge&&(c[i][j]==NoEdge||c[i][j]>c[i][k]+c[k][j]))
//确保加入的顶点k与顶点i,顶点k与顶点j之前是可达的,当顶点i到顶点j无边或小于顶点i到k加上顶点k到j的距离时,更新顶点i到j的路径长度
{
c[i][j]=c[i][k]+c[k][j];
p[i][j]=k; //加入的点k是使c[i][j]取最小值的数
}
}
}
最小スパニングツリー
プリム計算複雑度0(N ^ 2)
アルゴリズムのアイデア:
iが点jで、VSを集合Sの点を選択し、A:長いSは次のように貪欲選択する(まだすべてのノードが含まれていない)Vのサブセットであるとして最初集合S = {1}、および、 [I] [J]最小エッジ及び頂点が集合S jに添加しました。フィードS = Vアップにこのプロセス・タイプ。(異なる組の頂点の両側ので、ループか否かを判断する必要はありません)
iとjの条件を見つけるためにどのように、我々は2つの配列クローゼットと低コストを設定する必要があります。VSの頂点jに対して最も近い[j]はS内のj及びkは、隣接する他の頂点は[J]と比較された頂点S jに隣接している[最も近い[J] <= A [J] [K]。低コスト[j]は[J]の値である[最も近い[J]
アルゴリズムの実行、最小VS操作低コスト頂点jを見つけ、そしてアレイ側から最も近い(J、最も近い[J])を選択し、最後にSを追加するために、J、及び必要かつ低コストに最も近い時プリム変更。
void Prim(WItem *lowcost,int *closest,Graph G)
{
/*初始化*/
int *s; //集合S,值为1表示在集合中,为0表示不在集合中
s=malloc((G->n+1)*sizeof(int));
for(int i=1;i<=G->n;i++)
{
lowcost[i]=a[1][i]; //先设置顶点1在S中
closest[i]=1;
s[i]=0; //除1以外所有点都不在集合S中
}
s[1]=1; //把顶点1加入集合S
for(int i=1;i<=G->n;i++) //执行n次
{
min=G->NoEdge; //要找出最小值,先把min设为最大
j=1; //j用来记录到集合S距离最短的顶点序号,一开始先设为1作为初始值
for(int k=2;k<=G->n;k++) //k=2是因为一开始只有1在S中,V-S要从2开始
{
if(lowcost[k]<min)&&(!s[k]) //k到集合S最短距离小于min且k不在集合S中,即k在V-S中
{
min=lowcost[k]; //更新最小值,j为当前V-S中里S最近的顶点,现在令j为k
j=k;
}
}
//此时j是到集合S距离最短的顶点序号
s[j]=1; //把j加入集合S
for(int k=2;k<=G->n;k++) //j加入集合S后,要修改原来的closest和lowcost
{
if((G->a[k][j]<lowcost[k])&&(!s[k])) //若K不在集合S且顶点j和k之间距离小于j加入集合S前顶点k到集合S的最短距离
{
//更新closest,lowcost
closest[k]=j;
lowcost[k]=G->a[k][j]
; }
}
}
}
//V-S中的顶点为焦点向S中的顶点逐个连线找最短,j是通过k来更新的
//利用closest还能找到最短路径
//while(k>0)
//p=closest[k];
//k=p;
クラスカルのアルゴリズムの時間計算量O(eloge)
まず、GのN単離された分岐通信としてn個の頂点。エンドポイントがある場合、最初のエッジから小から大重量、右側の昇順に各側面図、全辺はk番目(V、W)、v及びwの縁を見たとき現在の頂点二つの異なる連結成分T1とT2、(W V)エッジを使用することは、T1とT2に接続されている構成要素に接続され、次いでK + 1の側を表示、または直接K + 1へのアクセスを継続側、プロセスは、接続されたコンポーネントまで一度だけまで続きます。
ツリーアルゴリズムスパニング上記構成クラスカル最小値は、すべてのエッジの重みのG参照図の昇順を必要とします。これは、すべてのGの側縁部は、重量でソートが必要です。構造定義は、各エッジに格納されています。
typedef struct edge
{
int u;
int v;
WItem w;
}Edge;
/*函数EDGE(u,v,w)创造一条权威w的边(u,v)*/
Edge EDGE(int u,int v,WItem w)
{
Edge e;
e->u=u;
e->v=v;
e->w=w;
return e;
}
/*函数Edges(a,G)抽取图G的所有边到赋权边数组a中,并返回图G的边数*/
int Edges(Edges a[],Graph G)
{
int k=0;
for(int i=1;i<=G->n;i++)
for(int j=1;j<=G->n;j++)
if(G->a[i][j]!=G->NoEdge)
a[k++]=EDGE(i,j,G->a[i][j]);
return k;
}
void Kruskal(Edge mst[],Graph G)
{
Edge a[maxE];
UFset U; //并查集u
int e=Edges(a,G); //抽取G的所有边,e为边数
quicksort(a,0,e-1); //对边数组a排序,参数意义为对数组a从元素0到元素e-1进行排序
U=UFinit(G->n); //初始化并查集U
for(int i=0,int k=0;i<e&&k<G->n-1;i++) //k记录循环次数,执行n-1次结束
{
int s=UFind(a[i].u,U); //a[i].u是当前最短边的一个端点,找出它所属的连通分支
int t=UFind(a[i].v,U); //a[i].v是当前最短边的另一个端点,找出它所属的连通分支
if(s!=t) //若s和t不是同一个连通分支
{
mst[k++]=a[i]; //更新最小生成树
UFunion(s,t,U); //合并连通分支s和t
}
}
}