fn main() {
    let (n, m) = readln();
    let mut g = vec![Edges::new(); n];
    for _ in 0..m {
        let (x, y): (usize, usize) = readln();
        g[x].push(Edge {
            src: x,
            dst: y,
            weight: 0,
        });
        g[y].push(Edge {
            src: y,
            dst: x,
            weight: 0,
        });
    }
    let (mut art, _) = articulation_point(&g);
    art.sort();
    for x in art {
        println!("{}", x);
    }
}

#[allow(unused_imports)]
#[allow(unused_imports)]

pub trait FromLn {
    fn fromln(s: &str) -> Self;
}
pub fn readln<T: FromLn>() -> T {
    let mut buf = String::new();
    let _ = ::std::io::stdin().read_line(&mut buf).unwrap();
    T::fromln(buf.trim())
}
pub fn readlns<T: FromLn>(n: usize) -> Vec<T> {
    let mut vs = vec![];
    for _ in 0..n {
        vs.push(readln());
    }
    vs
}
macro_rules! fromln_primitives {
    ($($t:ty),*) => { $(
        impl FromLn for $t {
            fn fromln(s: &str) -> $t {
                s.parse().unwrap()
            }
        }
    )* }
}
fromln_primitives!(String, bool, f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64);
impl<T> FromLn for Vec<T>
where
    T: FromLn,
{
    fn fromln(s: &str) -> Vec<T> {
        s.split_whitespace().map(T::fromln).collect()
    }
}
impl FromLn for Vec<char> {
    fn fromln(s: &str) -> Vec<char> {
        s.chars().collect()
    }
}
macro_rules! fromln_tuple {
    ($($t:ident),*) => {
        impl<$($t),*> FromLn for ($($t),*) where $($t: FromLn),* {
            fn fromln(s: &str) -> ($($t),*) {
                let mut it = s.split_whitespace();
                let t = ($($t::fromln(it.next().unwrap())),*);
                assert_eq!(it.next(), None);
                t
            }
        }
    }
}
fromln_tuple!(A, B);
fromln_tuple!(A, B, C);
fromln_tuple!(A, B, C, D);
fromln_tuple!(A, B, C, D, E);
fromln_tuple!(A, B, C, D, E, F);

use std::cmp::Ordering;

pub type Weight = i32;

#[derive(Eq, PartialEq, Debug, Clone)]
pub struct Edge {
    pub src: usize,
    pub dst: usize,
    pub weight: Weight,
}

impl Ord for Edge {
    fn cmp(&self, other: &Edge) -> Ordering {
        self.weight
            .cmp(&other.weight)
            .reverse()
            .then_with(|| self.src.cmp(&other.src))
            .then_with(|| self.dst.cmp(&other.dst))
    }
}

impl PartialOrd for Edge {
    fn partial_cmp(&self, other: &Edge) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

pub type Edges = Vec<Edge>;
pub type Graph = Vec<Edges>;

pub type Array = Vec<Weight>;
pub type Matrix = Vec<Array>;

use std::cmp::{max, min};
use std::collections::{BTreeSet, VecDeque};

pub fn articulation_point(g: &Graph) -> (Vec<usize>, Vec<EdgeSet>) {
    let n = g.len();
    let mut low = vec![0; n];
    let mut num = vec![0; n];
    let mut art = BTreeSet::new();
    let mut bcomp = Vec::new();
    let mut s = VecDeque::new();
    for u in 0..n {
        if num[u] == 0 {
            let mut time = 0;
            visit(
                g, u, &mut art, &mut bcomp, &mut s, &mut num, &mut low, &mut time,
            );
        }
    }
    (art.into_iter().collect(), bcomp)
}

fn visit(
    g: &Graph,
    v: usize,
    art: &mut BTreeSet<usize>,
    bcomp: &mut Vec<EdgeSet>,
    s: &mut VecDeque<Edge>,
    num: &mut Vec<i32>,
    low: &mut Vec<i32>,
    time: &mut i32,
) {
    *time += 1;
    low[v] = *time;
    num[v] = *time;
    for e in &g[v] {
        let w = e.dst;
        if num[w] < num[v] {
            s.push_back(e.clone());
        }
        if num[w] == 0 {
            visit(g, w, art, bcomp, s, num, low, time);
            low[v] = min(low[v], low[w]);
            if (num[v] == 1 && num[w] != 2) || (num[v] != 1 && low[w] >= num[v]) {
                art.insert(v);
            }
            if low[w] >= num[v] {
                bcomp.push(EdgeSet::new());
                while let Some(f) = s.pop_back() {
                    let n = bcomp.len();
                    let end = f.src == v && f.dst == w;
                    bcomp[n - 1].insert(UndirectEdge(f));
                    if end {
                        break;
                    }
                }
            }
        } else {
            low[v] = min(low[v], num[w]);
        }
    }
}

pub type EdgeSet = BTreeSet<UndirectEdge>;

#[derive(Eq, PartialEq, Debug)]
pub struct UndirectEdge(Edge);

impl UndirectEdge {
    pub fn min_v(&self) -> usize {
        min(self.0.src, self.0.dst)
    }
    pub fn max_v(&self) -> usize {
        max(self.0.src, self.0.dst)
    }
}

impl Ord for UndirectEdge {
    fn cmp(&self, other: &UndirectEdge) -> Ordering {
        self.min_v()
            .cmp(&other.min_v())
            .then_with(|| self.max_v().cmp(&other.max_v()))
    }
}

impl PartialOrd for UndirectEdge {
    fn partial_cmp(&self, other: &UndirectEdge) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}



Measured memory_bytes: 18232