Olá Matheus, tudo bem?
Vamos lá, * + ? aprensentar uma característica "gulosa". E o que isso quer dizer? Eles irão dar match, com a maior string possível.
Aplicando a sua regex n° 1 na string "<a> bbb <c> ddd" o match seria "<a> bbb <c>". A ideia é aqui é que ele atinge a primeira combinação "<a>", mas fala "Opa achei uma combinação, mas como estou com fome vou continuar até quebrar o meu padrão"
!", assim ele segue até a última tag. Por esse motivo a sua regex retorna "<a> bbb <c>".
Agora ao utilizar ? como sulfixo de para *, + e ? ele deixa de ser "guloso" e retorna a menor combinação possível. No caso sua 2° regex retorna no nosso exemplo 2 match um para "<a>" e outro para "<c>". Aqui ele encontra o primeiro valor, retorna esse valor, encontra a segunda combinação e retorna e assim sucessivamente. Algo que pode te ajudar, o caracter especial aqui não é * , + ou ? separadamente, mas sim a combinação *?, +?, ??.
Se você dar uma olhada na documentação python, ele coloca *?, +?, ?? como caracteres especiais assim como *, + ? individualmente.
Espero que tenha ajudado!