グラフ理論の部分で出てきたアルゴリズムとかでコード書いたのでいくつか。
Bellman-Ford algorithm
まずはBellman-Ford法。これは本には出てこなかったけど、Dijkstra法は書いたことあるのにBellman-Ford法は書いた事ないなぁと思った程度のアルゴリズム力なので、とりあえず書いてみた。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <stdint.h> | |
#include <stdlib.h> | |
using namespace std; | |
const size_t SIZE = 100; | |
int64_t V[SIZE], E[SIZE][SIZE]; | |
int main() { | |
for(int i = 0; i < SIZE; i++) | |
for(int j = 0; j < SIZE; j++) | |
E[i][j] = INT64_MAX; | |
for(int i = 0; i < SIZE; i++) | |
V[i] = INT64_MAX; | |
// |V| = N, |E| = M | |
int N, M; | |
cin >> N >> M; | |
int start, end; | |
cin >> start >> end; | |
// input format: endpoint_a endpoint_b cost | |
for(int i = 0; i < M; i++) { | |
int a, b, cost; | |
cin >> a >> b >> cost; | |
E[a][b] = E[b][a] = cost; | |
} | |
V[start] = 0; | |
int cnt = 0; | |
bool updated = true; | |
while(updated && cnt++ <= N) { | |
updated = false; | |
for(int i = 0; i < N; i++) { | |
if(V[i] == INT64_MAX) | |
continue; | |
for(int j = 0; j < N; j++) { | |
if(i == j || E[i][j] == INT64_MAX) | |
continue; | |
if(V[j] > E[i][j] + V[i]) { | |
V[j] = E[i][j] + V[i]; | |
updated = true; | |
} | |
} | |
} | |
if(!updated) | |
break; | |
} | |
if(updated) | |
cout << -1 << '\n'; // the graph has negative-weight cycle | |
else | |
cout << V[end] << '\n'; | |
} |
一応無向グラフ想定して書いたけど、ちょっと変えれば有向グラフにも適用出来る……はず。一応辺は行列で表現してるので。
Kruskal's algorithm
次にKruskal法。最小全域木を求めるアルゴリズムで、O(ElogE)? O(ElogV)? なアルゴリズム。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include <algorithm> | |
#include <cstdlib> | |
#include <cstdint> | |
struct UnionFind { | |
private: | |
std::vector<int64_t> data; | |
public: | |
UnionFind(size_t size); | |
bool unionSet(size_t x, size_t y); | |
bool findSet(size_t x, size_t y); | |
size_t root(size_t x); | |
int64_t size(size_t x); | |
}; | |
UnionFind::UnionFind(size_t size) : data(size, -1) {} | |
bool UnionFind::unionSet(size_t x, size_t y) { | |
x = root(x); | |
y = root(y); | |
if(x != y) { | |
if(size(x) > size(y)) | |
std::swap(x, y); | |
data[x] += data[y]; | |
data[y] = x; | |
} | |
return x != y; | |
} | |
bool UnionFind::findSet(size_t x, size_t y) { | |
return root(x) == root(y); | |
} | |
size_t UnionFind::root(size_t x) { | |
if(data[x] < 0) | |
return x; | |
else | |
return root(data[x]); | |
} | |
int64_t UnionFind::size(size_t x) { | |
return -data[root(x)]; | |
} | |
struct Edge { | |
size_t start, end; | |
int64_t cost; | |
bool operator==(const Edge &e) { | |
return cost == e.cost; | |
} | |
bool operator<(const Edge &e) const { | |
return cost < e.cost; | |
} | |
}; | |
auto main(void) -> int { | |
using namespace std; | |
int32_t N, M; // N = |V|, M = |E| | |
cin >> N >> M; | |
auto edges = new vector<Edge>(M); | |
for(auto it = edges->begin(), eit = edges->end(); it != eit; ++it) { | |
// Edge format: start_point end_point cost | |
size_t st, ed; | |
int64_t c; | |
cin >> st >> ed >> c; | |
Edge edge{st, ed, c}; | |
*it = edge; | |
} | |
sort(edges->begin(), edges->end()); | |
auto minTree = new vector<Edge>; | |
auto uft = new UnionFind(N); | |
for(auto it = edges->begin(), eit = edges->end(); it != eit; ++it) { | |
auto edge = *it; | |
if(!uft->findSet(edge.start, edge.end)) { | |
minTree->push_back(edge); | |
uft->unionSet(edge.start, edge.end); | |
} | |
} | |
for(auto e : *minTree) { | |
cout << e.start << ' ' << e.end << ' ' << e.cost << '\n'; | |
} | |
} |
同じ木に属しているかどうかはUnion-Find木で書けるので、本の通りにいけば、まずは辺をコストでソートして、小さい辺から、その辺が連結でない2つの木を連結するならば、最小全域木の辺として加えていくというアルゴリズム。
Reverse Polish Notation
アルゴリズムではないけども、逆ポーランド記法をプログラミング3年目?にしてようやく実装したので。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdint> | |
#include <cstdlib> | |
#include <cstring> | |
#include <iostream> | |
#include <memory> | |
#include <sstream> | |
#include <string> | |
#include <stack> | |
#include <vector> | |
#include <boost/algorithm/string/split.hpp> | |
#include <boost/algorithm/string/classification.hpp> | |
struct Node { | |
virtual int32_t value(){return 0;} | |
virtual void print(){} | |
virtual ~Node(){} | |
}; | |
struct OpNode : Node { | |
char op; | |
Node *left, *right; | |
OpNode(char o, Node* l, Node* r) : | |
op(o), | |
left(l), | |
right(r) {} | |
virtual ~OpNode() { | |
delete left; | |
delete right; | |
} | |
virtual int32_t value() { | |
auto l = left->value(); | |
auto r = right->value(); | |
int32_t ret; | |
switch(op) { | |
case '+': | |
ret = l + r; | |
break; | |
case '-': | |
ret = l - r; | |
break; | |
case '*': | |
ret = l * r; | |
break; | |
case '/': | |
ret = l / r; | |
break; | |
default: | |
ret = 0; | |
break; | |
} | |
return ret; | |
} | |
virtual void print() { | |
left->print(); | |
right->print(); | |
std::cout << op << ' '; | |
} | |
}; | |
struct ValNode : Node { | |
int32_t val; | |
ValNode(int32_t v) : val(v) {} | |
virtual ~ValNode(){} | |
virtual int32_t value() { | |
return val; | |
} | |
virtual void print() { | |
std::cout << val << ' '; | |
} | |
}; | |
bool isOperator(char *e) { | |
using std::strcmp; | |
return strcmp(e, "+") == 0 || strcmp(e, "-") == 0 || strcmp(e, "*") == 0 || strcmp(e, "/") == 0; | |
} | |
bool isOperator(std::string e) { | |
return e == "+" || e == "-" || e == "*" || e == "/"; | |
} | |
Node *makeTree(std::vector<std::string> vc) { | |
std::unique_ptr<std::stack<Node*>> st(new std::stack<Node*>); | |
for(auto e : vc) { | |
if(isOperator(e)) { | |
auto right = st->top(); | |
st->pop(); | |
auto left = st->top(); | |
st->pop(); | |
auto node = new OpNode(e[0], left, right); | |
st->push(node); | |
} else { | |
std::stringstream ss; | |
ss << e; | |
int32_t v; | |
ss >> v; | |
auto node = new ValNode(v); | |
st->push(node); | |
} | |
} | |
return st->top(); | |
} | |
Node *makeTree(int n, char **arr) { | |
std::unique_ptr<std::stack<Node*>> st(new std::stack<Node*>); | |
for(int i = 0; i < n; i++) { | |
auto e = arr[i]; | |
if(isOperator(e)) { | |
auto right = st->top(); | |
st->pop(); | |
auto left = st->top(); | |
st->pop(); | |
auto node = new OpNode(e[0], left, right); | |
st->push(node); | |
} else { | |
std::stringstream ss; | |
ss << e; | |
int32_t v; | |
ss >> v; | |
auto node = new ValNode(v); | |
st->push(node); | |
} | |
} | |
return st->top(); | |
} | |
auto main(int argc, char **argv) -> int { | |
Node *tree; | |
if(argc > 1) { | |
tree = makeTree(--argc, ++argv); | |
} else { | |
std::unique_ptr<std::vector<std::string>> vc(new std::vector<std::string>); | |
std::string str; | |
getline(std::cin, str); | |
boost::algorithm::split(*vc, str, boost::is_any_of(" ")); | |
if(vc->size() == 0 || vc->at(0).length() < 1) { | |
std::cerr << "Input Error" << std::endl; | |
exit(EXIT_FAILURE); | |
} | |
tree = makeTree(*vc); | |
} | |
tree->print(); | |
std::cout << std::endl; | |
std::cout << tree->value() << std::endl; | |
exit(EXIT_SUCCESS); | |
} |
ある数式を表す木構造を考えるときに、木の各頂点に番号を付けることを考えると、プリオーダーとポストオーダーという2つの方法が考えられる。
プリオーダーって言うのは、自分自身、左の子ノード、右の子ノードの順に再帰的に番号を付けていく方法で、このやり方だと式木はポーランド記法になる……はず。
ポストオーダーは左の子ノード、右の子ノード、自分自身の順に番号をつけていく方法で、このやり方だと式木は逆ポーランド記法になる。
実装の方法としては、入力をスペース区切りの数字、演算子の列だとして、数字が来たらスタックにそのままプッシュ、演算子が来たら右、左の順にスタックから取り出した値を子ノードとして割り当てて、演算子を根とした木構造にしてスタックにプッシュ。最後にスタックに残った木を取り出すと、これが式木になってる。後はまぁコードの通りに各頂点ごとの値を計算するだけ。
とりあえずグラフは(まだフローとかあるけど)ひと通り分かった……と思うので、次はオートマトンのあたりで何かしらコードを書きたい。
0 件のコメント:
コメントを投稿