Nesse texto vamos abordar um tipo especifico de Árvore Rubro Negra, as esquerdistas, ou em inglês left-leaning, isso porque elas são um pouco mais simples de entender do que o caso geral.
Nessas árvores os nós podem ter duas cores: Vermelho e Negro. Entretanto também é possível fazer uma definição equivalente em que as ligações entre os nós é que tem essas cores.
Árvores binárias de busca no geral seguem as seguintes propriedades:
São árvores binárias, então cada nó tem dois filhos: o esquerdo e o direito, e cada filho é por si, também uma árvore binária.
Uma árvore binária pode ser vazia, nesse caso representamos ela simplesmente pelo valor None.
São de busca: Dado alguma forma de comparar o valor armazenado em cada nó, os valores menores do que o pai estão na subárvore esquerda, e os valores maiores do que o pai estão na subárvore direita.
As árvores binárias de busca rubro-negras seguem mais algumas propriedades:
Todo nó é vermelho ou negro.
Nas árvores esquerdistas um nó vermelho sempre é filho esquerdo do seu pai. Como o nó raiz é negro.
Um nó vermelho só tem filhos negros.
As árvores vazias (None) são as folhas e são negras.
Para cada nó x, o caminho para qualquer folha descendente (None) tem o mesmo número de nós negros. Não contamos o próprio nó x e chamamos essa quantidade de altura negra.
Verifique que a seguinte árvore é rubro-negra esquerdista.
Por quê esse tipo de árvore é balanceado? Essa pergunta exigiria uma prova formal, mas vou ficar com uma intuição que deve ser suficiente para te convencer: Como não existem dois nós vermelhos consecutivos, para alcançar uma folha, o melhor caso seria se o caminho só tivesse nós negros, digamos h nós. Para alcançar qualquer outro nó folha, o pior caso seria um caminho composto por nós vermelhos e negros alternados. Mas como o número de nós negros é o mesmo, o caminho total no pior caso seria 2h.
Vamos fazer um código em python para representar um nó. Vou usar o atributo cor como um boleano, se o nó tiver cor (se cor = True) o nó é vermelho, se ele não tem cor (se cor = False) ele é negro. Suponha que uma árvore rubro-negra esteja respeitando todas as propriedades, como vamos adicionar um novo nó, se ele for preto podemos estragar a propriedade da altura negra, por isso, sempre que adicionamos um novo nó, ele será vermelho.
Considere então que vamos adicionar nosso primeiro nó na árvore. Para respeitar a propriedade que a raiz é sempre negra, nós inserimos um novo nó e logo em seguida deixamos ele negro. Depois vamos inserir o número 7 como em uma árvore binária de busca tradicional obtendo o seguinte resultado
→
Problema detectado! Essa árvore não está respeitando a propriedade que um nó vermelho deve ser o filho esquerdo do seu pai. Note que não podemos simplesmente remover o vermelho do 7, já que isso estragaria a propriedade da altura negra. Nem podemos pintar o 5 de vermelho, já que isso estragaria a propriedade de que a raiz é negra e um nó vermelho tem filhos negros.
A solução é fazer uma rotação a esquerda. Dessa forma queremos fazer uma raiz com um filho para esquerda. Para generalizar, ao invés dos None, vou colocar subárvores que poderiam estar ali, e desejamos não mexer nessas subárvores.
→
O código para essa rotação seria assim:
Mas agora suponha que inserimos o nó 3 (ou que ele já estivesse lá e fosse vermelho. Novamente não podemos simplesmente remover a cor do 3 ou do 5, pois isso afetaria a propriedade da altura negra. Então a solução é fazer uma rotação a direita no 7, que vai transformar a linha colocando o 5 na raiz e o 3 e o 7 como irmãos.
→
O código dessa rotação segue abaixo:
Só que agora temos um nó vermelho que é filho direito. Mas se fizermos uma rotação a esquerda voltamos no problema anterior. Então vamos precisar de mais uma operação, que é a sobeVermelho. Note que dois filhos vermelhos e um pai negro, se o vermelho vai para o Pai a altura negra permanece a mesma. E mais, note que a raiz é o único nó que pode trocar de vermelho para negro sem prejudicar a propriedade da altura negra. Segue o código da sobeVermelho.
→
Nesse caso, se o 5 for a raiz, ela também precisa ficar negra.
Por fim precisamos de 2 funções auxiliares:
e a inserção:
A busca é igual ao da árvore binária.