#include <bits/stdc++.h>
#pragma comment(linker, "/stack:64000000")
using namespace std;
template <typename first>
inline first abs(const first& a) {
  return a < 0 ? -a : a;
}
template <typename first>
inline first sqr(const first& a) {
  return a * a;
}
const long double PI = 3.1415926535897932384626433832795;
const long double EPS = 1e-9;
const int INF = 1000 * 1000 * 1000;
const int N = 100 * 1000 + 3;
int n, m;
vector<int> g[N];
int c[N];
vector<int> f1, f2;
void dfs(int v, int col) {
  c[v] = col;
  if (col == 0)
    f1.push_back(v);
  else
    f2.push_back(v);
  for (int i = 0; i < int(int((g[v]).size())); i++) {
    int to = g[v][i];
    if (c[to] == -1)
      dfs(to, col ^ 1);
    else
      assert(c[to] == col ^ 1);
  }
}
int ans[N];
bool check2() {
  if (int((f2).size()) % 3 == 1) swap(f1, f2);
  vector<int> v1, v2;
  for (int i = 0; i < int(int((f2).size())); i++) {
    int v = f2[i];
    if (int((g[v]).size()) > int((f1).size()) - 2) continue;
    set<int> neib;
    for (int j = 0; j < int(int((g[v]).size())); j++) neib.insert(g[v][j]);
    v1.push_back(v);
    for (int j = 0; j < int(int((f1).size())); j++)
      if (!neib.count(f1[j])) {
        v1.push_back(f1[j]);
        if (int((v1).size()) == 3) break;
      }
    swap(v1, v2);
  }
  if (v1.empty()) return false;
  for (int i = 0; i < int(int((v1).size())); i++) ans[v1[i]] = 0;
  for (int i = 0; i < int(int((v2).size())); i++) ans[v2[i]] = 1;
  f1.erase(find((f1).begin(), (f1).end(), v1[1]));
  f1.erase(find((f1).begin(), (f1).end(), v1[2]));
  f1.erase(find((f1).begin(), (f1).end(), v2[1]));
  f1.erase(find((f1).begin(), (f1).end(), v2[2]));
  f2.erase(find((f2).begin(), (f2).end(), v1[0]));
  f2.erase(find((f2).begin(), (f2).end(), v2[0]));
  int cur = 1;
  for (int i = 0; i < int(int((f1).size())); i++) {
    if (i % 3 == 0) cur++;
    ans[f1[i]] = cur;
  }
  for (int i = 0; i < int(int((f2).size())); i++) {
    if (i % 3 == 0) cur++;
    ans[f2[i]] = cur;
  }
  return true;
}
bool check1() {
  if (int((f1).size()) % 3 == 2) swap(f1, f2);
  for (int i = 0; i < int(int((f1).size())); i++) {
    int v = f1[i];
    if (int((g[v]).size()) > int((f2).size()) - 2) continue;
    set<int> neib;
    for (int j = 0; j < int(int((g[v]).size())); j++) neib.insert(g[v][j]);
    vector<int> vs;
    for (int j = 0; j < int(int((f2).size())); j++) {
      int to = f2[j];
      if (!neib.count(to)) vs.push_back(f2[j]);
      if (int((vs).size()) == 2) break;
    }
    assert(int((vs).size()) == 2);
    ans[f1[i]] = 0;
    ans[vs[0]] = 0;
    ans[vs[1]] = 0;
    f2.erase(find((f2).begin(), (f2).end(), vs[0]));
    f2.erase(find((f2).begin(), (f2).end(), vs[1]));
    f1.erase(f1.begin() + i);
    assert(int((f1).size()) % 3 == 0 && int((f2).size()) % 3 == 0);
    int cur = 0;
    for (int j = 0; j < int(int((f1).size())); j++) {
      if (j % 3 == 0) cur++;
      ans[f1[j]] = cur;
    }
    for (int j = 0; j < int(int((f2).size())); j++) {
      if (j % 3 == 0) cur++;
      ans[f2[j]] = cur;
    }
    return true;
  }
  return false;
}
int main() {
  cin >> n >> m;
  for (int i = 0; i < int(m); i++) {
    int a, b;
    scanf("%d%d", &a, &b);
    --a, --b;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  memset(c, -1, sizeof(c));
  for (int i = 0; i < int(n); i++)
    if (c[i] == -1) dfs(i, 0);
  if (int((f1).size()) % 3 == 0) {
    int cur = -1;
    for (int i = 0; i < int(int((f1).size())); i++) {
      if (i % 3 == 0) cur++;
      ans[f1[i]] = cur;
    }
    for (int i = 0; i < int(int((f2).size())); i++) {
      if (i % 3 == 0) cur++;
      ans[f2[i]] = cur;
    }
    puts("YES");
    for (int i = 0; i < int(n); i++) printf("%d ", ans[i] + 1);
    puts("");
  } else {
    if (check1() || check2()) {
      puts("YES");
      for (int i = 0; i < int(n); i++) printf("%d ", ans[i] + 1);
      puts("");
    } else
      puts("NO");
  }
  return 0;
}
