FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
cpp-taskflow/examples/tsp.cpp at master · feiyunwill/cpp-taskflow · GitHub
feiyunwill
/
cpp-taskflow
Public
forked from
taskflow/taskflow
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
0
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
cpp-taskflow
/
examples
/
tsp.cpp
Copy path
More file actions
More file actions
Latest commit
History
History
History
174 lines (152 loc) · 6.69 KB
Breadcrumbs
cpp-taskflow
/
examples
/
tsp.cpp
Copy path
File metadata and controls
174 lines (152 loc) · 6.69 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//
tsp.cpp: parallel branch-and-bound solver for the Travelling Salesman Problem
//
using tf::TaskGroup for recursive task parallelism.
//
//
The example uses a hand-constructed 4-city instance:
//
//
A B C D
//
A [ 0 10 15 20 ]
//
B [ 10 0 35 25 ]
//
C [ 15 35 0 30 ]
//
D [ 20 25 30 0 ]
//
//
Optimal tour: A -> B -> D -> C -> A = 80
#
include
<
taskflow/taskflow.hpp
>
//
── problem constants ────────────────────────────────────────────────────────
//
Use NUM_CITIES instead of N to avoid shadowing template parameter 'N'
//
inside Taskflow's internal headers (e.g. SmallVector, UnboundedWSQ).
static
constexpr
int
NUM_CITIES
=
4
;
static
constexpr
int
INF
= std::numeric_limits<
int
>::max();
static
const
int
DIST
[
NUM_CITIES
][
NUM_CITIES
] = {
{
0
,
10
,
15
,
20
},
//
A
{
10
,
0
,
35
,
25
},
//
B
{
15
,
35
,
0
,
30
},
//
C
{
20
,
25
,
30
,
0
},
//
D
};
//
── shared state ─────────────────────────────────────────────────────────────
static
tf::Executor executor;
static
std::atomic<
int
> best_cost{
INF
};
//
── lower bound ──────────────────────────────────────────────────────────────
//
//
Any valid completion of the partial tour must include:
//
(1) at least one edge leaving the current city to some unvisited city
//
(2) at least one edge touching each remaining unvisited city
//
//
We use the cheapest available option for each of these, giving a floor on
//
the best possible tour cost from this node onward.
//
If this floor already meets or exceeds best_cost, the subtree is pruned.
//
//
Example: at A->C (cost=15, best_cost=80 after first leaf):
//
(1) min edge from C to unvisited {B,D}: min(35,30) = 30
//
(2) min edge from B to anywhere: min(10,35,25) = 10
//
min edge from D to anywhere: min(20,25,30) = 20
//
lb = 15 + 30 + 10 + 20 = 75 < 80 => not pruned
//
//
At A->C->B (cost=50):
//
(1) min edge from B to unvisited {D}: 25
//
(2) min edge from D to anywhere: 20
//
lb = 50 + 25 + 20 = 95 >= 80 => PRUNED
//
static
int
lower_bound
(
int
current,
const
std::vector<
bool
>& visited,
int
cost_so_far
) {
int
bound = cost_so_far;
//
(1) cheapest edge out of the current city to any unvisited city
int
min_curr =
INF
;
for
(
int
v =
0
; v <
NUM_CITIES
; v++) {
if
(!visited[v]) {
min_curr =
std::min
(min_curr,
DIST
[current][v]);
}
}
if
(min_curr ==
INF
)
return
bound;
//
no unvisited cities: tour is complete
bound += min_curr;
//
(2) cheapest outgoing edge from each unvisited city to any other city
for
(
int
u =
0
; u <
NUM_CITIES
; u++) {
if
(visited[u])
continue
;
int
min_u =
INF
;
for
(
int
v =
0
; v <
NUM_CITIES
; v++) {
if
(v != u) {
min_u =
std::min
(min_u,
DIST
[u][v]);
}
}
bound += min_u;
}
return
bound;
}
//
── branch and bound ─────────────────────────────────────────────────────────
//
//
Each call represents one node in the TSP search tree.
//
It creates a tf::TaskGroup and spawns one async task per unvisited city
//
(except the first, which runs on the current worker to avoid task overhead).
//
tg.corun() cooperatively waits for all spawned branches without blocking
//
the worker thread.
//
static
void
branch_and_bound
(
int
current,
//
city we are currently at
std::vector<
bool
> visited,
//
which cities have been visited
int
cost_so_far,
//
accumulated tour cost so far
int
depth
//
number of cities visited so far
) {
//
compute lower bound and prune if it cannot improve on best_cost
int
lb =
lower_bound
(current, visited, cost_so_far);
if
(lb >= best_cost.
load
(std::memory_order_relaxed))
return
;
//
all cities visited: close the tour by returning to city 0
if
(depth ==
NUM_CITIES
) {
int
total = cost_so_far +
DIST
[current][
0
];
//
update best_cost if this tour is cheaper; compare_exchange_weak retries
//
on spurious failure so the true minimum is always stored correctly
int
cur = best_cost.
load
(std::memory_order_relaxed);
while
(total < cur &&
!best_cost.
compare_exchange_weak
(
cur, total,
std::memory_order_relaxed,
std::memory_order_relaxed));
return
;
}
//
create a task group for parallel branching.
//
task_group() is valid here because every call to branch_and_bound runs
//
inside a worker thread: either the root executor.async() task or a
//
tg.silent_async() task spawned by a parent call.
auto
tg = executor.
task_group
();
bool
first =
true
;
for
(
int
v =
0
; v <
NUM_CITIES
; v++) {
if
(visited[v])
continue
;
//
prepare state for the branch where city v is visited next
std::vector<
bool
> new_visited = visited;
new_visited[v] =
true
;
int
new_cost = cost_so_far +
DIST
[current][v];
if
(first) {
//
run the first unvisited branch directly on this worker to avoid
//
unnecessary task creation overhead
first =
false
;
branch_and_bound
(v, new_visited, new_cost, depth +
1
);
}
else
{
//
spawn remaining branches as independent async tasks in the group.
//
all spawned tasks share best_cost, so a better solution found in any
//
branch immediately tightens the pruning bound for all other branches.
tg.
silent_async
([=]() {
branch_and_bound
(v, new_visited, new_cost, depth +
1
);
});
}
}
//
cooperatively wait for all spawned branches to complete.
//
corun() re-enters the executor's work-stealing loop rather than blocking,
//
preventing worker starvation in deep recursion.
tg.
corun
();
}
//
── main ─────────────────────────────────────────────────────────────────────
int
main
() {
std::vector<
bool
>
visited
(
NUM_CITIES
,
false
);
visited[
0
] =
true
;
//
start from city A (index 0)
//
the root call must be submitted as an async task so it runs inside a
//
worker thread, which is required for executor.task_group() to be valid
executor.
async
([&]() {
branch_and_bound
(
0
, visited,
0
,
1
);
}).
get
();
const
char
* names[] = {
"
A
"
,
"
B
"
,
"
C
"
,
"
D
"
};
printf
(
"
Optimal tour cost: %d
\n
"
, best_cost.
load
());
(
void
)names;
return
0
;
}
Back
|
FazBrowse Home
|
New Git URL