隣接マトリックス、隣接リスト、チェーン フォワード スターの違いを理解するのに役立つ記事

序文:

私が C++ を学んでいたとき、さまざまな画像保存方法に直面していましたが、私の脳ははるかに大きく、さまざまなアルゴリズムがまだ私に向かって突進していました。その結果、隣接行列/隣接リスト/リンク リストは簡単にクリティカルを与えてくれました KOさせてくださいまだ頭がはっきりしているうちに、これら 3 つの画像保存方法の本質とコード実装を詳しく紹介します。

アイデアの実現:

連鎖フォワード スターと隣接行列は密なグラフの保存に適しており、隣接リストは疎なグラフの保存に適しています。質問を行うときは、さまざまなデータ範囲に応じて 3 つの方法を選択してグラフを保存できます。
さて、写真について触れましたが、まずはそれを紹介しましょう。

1. 絵とは何ですか?

グラフはデータ構造であり、初心者にとっては、多くの点がエッジで接続されたグラフであることがわかります。
グラフには、無向グラフと有向グラフがあり、無向グラフとは、2 点間に明確な方向性がなく、2 点が相互に行き来できることを意味します。
有向グラフはこれとは異なり、方向性が入力されると、コンピュータはあらかじめ構築された有向グラフに従って移動することしかできません。


ここに画像の説明を挿入
2 つの図は似ていますが、いくつかの詳細は大きく異なります。ただし、有向グラフと無向グラフの入力はまったく同じですが、意味が少し異なります。上のグラフの例を次に示します。ここでは、グラフに uv からのエッジがあると仮定します
。 2 つのグラフのうち、次のとおりです。

1 2
1 4
5 1
1 3

ここでは1行目の「1 2」を例にします;
無向グラフではノード①からノード②にアクセスでき、ノード②からノード①にもアクセスできます;
有向グラフでは: 入力シーケンスが ' であるため最初に 1'、次に '2' とグラフを走査してアクセスするプロセスでは、ノード①からノード②にのみアクセスできます。
エッジの関係とは別に、グラフでもう 1 つ重要なのはエッジの重みで、簡単に言うと 2 つのノードを訪問するのに必要な「コスト」です。
ここに画像の説明を挿入
ここで有向グラフを例にとると、上の図でノード①からノード②までのエッジの重みが 3、ノード②からノード③までのエッジの重みが 4 であることを見つけるのは難しくありません。エッジの重みは、いくつかの質問において、特定のノードから特定のノードに移動するために必要なエッジの重みの合計がいくらになるかを問う機能です。
グラフは密グラフと疎グラフにも分けられ、疎グラフは完全グラフよりもエッジが少なく、密グラフは完全グラフに近いエッジを持ちます。
グラフについて説明した後、フォワード スター、隣接行列、隣接テーブル格納グラフの原理とコード実装を 1 つずつ紹介します。

2. チェーンフォワードスター

Chained Forward Star の本質はリンク リストですが、ヘッド ノードを記録するヘッド アレイも備えており、リンク リストを使用して、あるノードが別のノードにアクセスする特性に応じた画像の保存操作を実行できます。n 個の点と m 個のエッジがあるとします。したがって、そのスペース効率とスペース効率は両方とも O(m) であり、密なグラフを格納するのに適しています。以下では、コードを使用して原理を説明します。

struct EDGE{
    
    
	int v,w,next;
	//v变量记录的是当前点到达那一条边。
	//w变量记录的是走过当前这条边所需的"代价"即权值
	//next变量记录的是以某一个定点为起点,读的上一条边的编号。
	//这三个变量的意义一定要搞明白,不搞明白这个,前向星的写法死记硬背也不一定行。
}e[N<<1];
int head[N],tot=1;
void add(int u,int v,int w)
{
    
    
	e[tot].v=v;
	e[tot].w=w;
	e[tot].next=head[u];
	head[u]=tot++;
}

ここにグラフがあるとします。
ここに画像の説明を挿入
これは有向重み付きグラフなので、その辺の間の入力は次のようになります。
ここに画像の説明を挿入
構造体配列の v、w、next に対応する 3 つの変数は次のようになります。
ここに画像の説明を挿入
ここで Tot であることを確認するのは難しくありません。前の行の番号を記録します、つまり、tot を介して前の行の番号を見つけることができ、以前に保存された画像の to、w、next を見つけることができます; では、head 配列は何ですか
? 別のグラフを描いてみましょう。
ここに画像の説明を挿入

この図から、先頭配列は実際には、i 番目の番号が付いた点から開始して読み取った最後のエッジの番号であることがわかります。
理解できない人のために、ブロガーはコードと画像に基づいて説明する例を示しています。
チェーンフォワードスターの核となるアイデアとも言えるヘッドアレイ

開始点が 1、終了点が 4、エッジの重みが 9 のエッジを見つけたいとします。
開始点が 1 であるため、head[1] に格納されている値は 4 であることがわかります。これは、1 から開始して読み取った最後のエッジの番号が 4 であることを意味します。
ここに画像の説明を挿入
この番号に従って、上図の横線にあるデータの集合がわかります。ここまででヘッド配列の役割が終わり、次の変数が登場します。
上の図の赤い線の線の次の変数値は 3 で、1 から始まる前のエッジの番号が 3 であることを示しています。したがって、探しているものではないので、番号 3 の線を見つけます。したがって、「3 の行の次は 2」という番号が付けられた行を見てみましょう。そのため、番号 2 の行にアクセスし、それがまさに探しているエッジであることがわかり、検索を終了します。
ここに画像の説明を挿入
したがって、上図に示すように、3 番のエッジから 2 番のエッジを見つけて、その結果のエッジを見つけます。前進するスター。
原則は明らかです。次に、上記の凡例と原則に従って、コードの各ステップを 1 つずつ説明します。

コードの説明:

これを 2 つの部分に分割します。1 つの部分は構造体配列で、もう 1 つの部分はエッジ構築の追加操作です。

struct EDGE{
    
    
	int v,w,next;
}e[N<<1];

構造体配列に格納される「v,w,next」はそれぞれ、指しているエッジ、エッジの重み、固定点を始点として読み取った前のエッジの番号である。

void add(int u,int v,int w)
{
    
    
	e[tot++].v=v;//将指向的边存进去。这里注意一个点,此时是新的一条边,cnt要++;
	e[tot].w=w;//将当前的边的边权存进去
	//下面两步是最为核心的两步。
	e[tot].next=head[u];
	//我们说了,next数组存的是以某个定点为起点,读的上一条边的编号。head数组里记录的是以某个点为起点,
	//我读进去的最后一条边的编号。想一下,此时的e[tot]的这条边还没有加进去,所以此时的e[tot]的数组的
	//上一条边的编号就是我head数组存的最后一条边的编号。所以我得e[tot].next=head[u]就行啦!
	head[u]=tot;
	//此时我已经读进去了新的一条边,那此时head最后指向的是不是就是当前的边?就是此时的cnt呀,我直接赋
	//值给head[u]就行
}

へえ~これが連鎖前進スターのアイデア+コードなんですが、ブログ主はもっと詳しいと思って書きながら自分のアイデアも練ってたので一番下手くそな方法を採用しました。次に隣接行列について説明します!

3. 隣接行列

行列行列は、その名前を聞いたらすぐに、それが 2 次元配列であると推測できるでしょう (ここで言っておきますが、これを 1 次元配列だと考える人はそれを学ぶ必要はありません。あなたの脳も同様です)バカ…私みたいに )
引いて、引いて〜 しかし、これは単純な 2 次元配列ではなく、境界がない場合、配列に格納される数値 '1' と '0' は、なぜ 2 つの数値が格納されているのか、待ってください。エッジの重みを含むグラフの場合、エッジの重みは配列に格納されることを説明します。
連鎖フォワード スターとは異なり、隣接行列では有向グラフ、無向グラフ、重み付きグラフを考慮する必要があります...ただし、考え方はフォワード スターよりもわずかに単純で、どちらも高密度に格納されたグラフです。
有向グラフと無向グラフの観点から考えてみましょう (1) 有向グラフ: 有向グラフ
の場合、i 番目の点から j 番目の点へ、j 番目の点から i 番目の点へ進みます。全く異なる2つの道路、事前にグラフに追加しておかないと事前申告なしで行くことは不可能、e[i][j]!=e[j][i];としても理解できます(
2 ) 無向グラフ:
無向グラフは有向グラフとはまったく異なります。i から j に移動するエッジがあると言った場合、i から j および j から i に移動することは可能です。 e[i][j]==e[j][i]; になる e[i][j]==e[j][i];
有向グラフと無向グラフについて話した後、権利と権利なしの問題が生じます。これについて話す前に、2 つのグラフを使用する方法について話しましょう-エッジの重みを格納する次元配列、または前述の「0」「1」?
ご存知のとおり、2 次元配列の書き方は a[i][j] で、2 番目の辺から 3 番目の辺までに辺の重みが 4 の辺があると仮定すると、次のようになります。 e[2][
3 ] には値 4 が格納されていますが、どちらの側を指しているのかを確認したい場合は、double for ループを使用してその i と j の値を確認するだけでよいでしょうか。下の写真のようになります。

#include<iostream>
using namespace std;
int main()
{
    
    
	int m;
	int u,v,w;
	int e[10][10];
	for(int i=0;i<m;i++)
	{
    
    
		cin>>u>>v>>w;
		e[u][v]=w;
	}
	return 0;
}


上記のコードでは、2 つの点がエッジの重みを求めるための基礎になっていることがわかります。タイトルの 2 ~ 3 のエッジの重みをクエリしたい場合は、 e[2][3]を直接出力できます。もちろん、これは有向グラフと重み付きグラフのコードにすぎません。無向グラフと重み付きグラフの場合は、e[u][v]=w の後に逆にエッジを構築するだけで済みます。
重み付きのグラフについて話した後、重みなしでグラフを書くにはどうすればよいでしょうか?
私たちのアイデアは、点 ij からのエッジがある場合、e[i][j] の値を 1 に割り当てるというものです。それを一度入力した後、配列 e を再度走査し、その中のすべての値を調べます。ドットはijからのエッジがないことを意味するため、中央にエッジのない値はすべて0に割り当てられます。
ここにはコードがないので、i と j が何度も出てきますが、理解できない学生は、以下のコードの説明を参照してください。

#include<iostream>
using namespace std;
const int N=10005;
int e[N][N];
int main()
{
    
    
	int m;//m条边
	for(int i=0;i<m;i++)
	{
    
    
		int u,v;//说明从u-v有一条边
		e[u][v]=1;//其值赋为1
	}
	for(int i=1;i<=m;i++)
	{
    
    
		for(int j=1;j<=m;j++)
		{
    
    
			if(e[i][j]!=1) e[i][j]=0;//!=1说明之前没有输入过,说明i-j没有边,赋为0;
		}
	}
	return 0;
}

重みなし無向グラフの処理方法は、先ほどの重み付き無向グラフの処理方法とほぼ同じですので、ここでは詳しく説明しません。

3. 隣接リスト

隣接リストは、疎なグラフの保存に適しており、主に、隣接行列を置き換えて、隣接行列で多くのスペースを消費するグラフを保存するために使用されます。私自身の問題解決の経験に基づくと、隣接行列を使用するだけで十分である限り、よほど嫌なことがない限り、空間に閉じ込められることはありません。
フォローアップの理解が徹底されている場合は、別の問題を更新します。

作るのは簡単じゃないよ、無駄に売春しないでねwww!作者にいいね+トリプルをお願いします!ありがとう〜

おすすめ

転載: blog.csdn.net/2301_76331300/article/details/131852295