動的配列std::vectorと静的配列[]
C++には、処理の過程で動的に要素数を追加・削除できるstd::vectorと、
変数の宣言時に要素数を静的に決めてしまう配列[]があります。
この記事では、C++で頻繁に使われる両者の長所を組み合わせた深さ優先探索の実装を紹介します。
変数の宣言
まず基本的なことですが、std::vectorと静的配列[]はそれぞれ以下のように宣言することができます。
int a[10]; //変数型 変数名[要素数];
std::vector<int> v; //vector<変数型> 変数名; 要素数指定なし(=要素数0)
std::vector<int> vv(10); //vector<変数型> 変数名(要素数);要素数指定あり
std::vectorを宣言するときはstd::vectorと記述する必要があるのに対し、
静的配列を宣言するときは変数名の後ろに[要素数]を記述すれば良い点に注意してください。
また、std::vectorの宣言時には要素数を指定する(2行目)ことも、要素数を指定しない(3行目)こともできます。
vector宣言時において要素数を指定しないと要素数0のvectorが生成されるため、要素数0指定でvector宣言することと等価です。
std::vectorと静的配列[]を組み合わせて深さ優先探索(DFS)
入力
入力として下記の木構造情報が標準入力から与えられるとします。
n:ノード数
a_1 b_1 :ノード間のリンク情報(無向)
:
a_n-1 b_n-1
例えば、以下のような形式です。
6
1 2
1 3
2 4
3 6
2 5
変数宣言
std::vectorと静的配列[]を組み合わせることで、以下のような変数を宣言することができます。
std::vector<int> v[n]; //静的配列[]の中にvectorが格納されている
この宣言をすることで要素数nの静的配列の中に動的配列(宣言時は要素数0)のvectorが格納されているような変数になります。
ノード数は与えられているが各ノードの子ノードの数はわからない、といったケースではこのような配列が有効です。
この後に出てくる木構造データの学習、探索時にその効果がよくわかると思います。
木構造の学習
下記のコードでは、標準入力で与えられたリンク情報を上記で宣言した配列vecに格納していきます。
for(int i=0;i<n-1;++i){
cin >> a >> b;
--a;--b;
vec[a].push_back(b);
vec[b].push_back(a);
}
変数vec自体は要素数nで初期化しているので、はじめから0~n-1番目の任意の要素にアクセスすることができます。
そして、その各要素は動的配列std::vector(初期要素数0)であるため、push_backすることで動的に要素を追加していくことができます。
上記の例では、ノードaとノードb間にリンクがあることを配列vecに保存しています。
(今回は無向グラフを考えているため、a→bとb→aの双方向のリンク情報を格納しています。)
一般的に、木構造データでは一つのノードが二つ以上の子を持つことがあるため、単純な一次元配列ではこのようなデータ構造を保持するのは難しいと思います。
深さ優先探索(DFS)
では、上記で学習した木構造を使って深さ優先探索を実装してみましょう。
void dfs(int v, int p){ //v:探索中のノード p:親ノード
for(auto u : vec[v]){ //vの子ノードでfor文を回す
if(u != p){ //親ノードは探索しない
//ここに処理を記述する。
dfs(u,v); //探索中ノードを親ノードとし、子ノードを探索する(深さ優先)
}
}
}
上記の関数では、リンク情報を学習した配列vecを基に、あるノードvの子ノードvec[v]を順に探索していきます。
main関数中では以下のように呼び出せば良いでしょう。
dfs(0,-1);//ノード0が一番親である場合。親=-1で親ノードが存在しないことを示す
使用例(AtCoder Beginner Contest 138: D-Ki)
上記の配列を活用することで、例えば以下のような問題をシンプルに解くことができます。
AtCoder Beginner Contest 138: D - Ki
#include<iostream>
#include<vector>
using namespace std;
vector<int> vec[200002]; //vectorと静的配列の組み合わせ
//N<=200000なのでこれくらい要素数があれば足りる
vector<int> ans;
void dfs(int v, int p){ //v:探索中のノード p:親ノード
for(auto u : vec[v]){ //vの子ノードでfor文を回す
if(u == p)continue; //親ノードは探索しない
ans[u] += ans[v];
dfs(u,v); //探索中ノードを親ノードとし、子ノードを探索する(深さ優先)
}
}
int main(){
int a,b,i,n,p,q,x;
cin >> n >> q;
ans.resize(n);
for(i=0;i<n-1;++i){
cin >> a >> b;
--a;--b;
vec[a].push_back(b); //a→bのリンク情報を格納
vec[b].push_back(a); //b→aのリンク情報を格納
}
for(i=0;i<q;++i){
cin >> p >> x;
--p;
ans[p] += x;
}
dfs(0,-1); //深さ優先探索
for(i=0;i<n;++i)cout << ans[i] << endl;
return 0;
}
終わりに
C++のstd::vectorや配列[]を使いこなせるようになると実装力はぐっと上がります。
ぜひ上記の他にも色々な配列の使い方を試してみてください。
内容に不備や不明点などありましたらコメントでお願いします。