Algebraic Data Types are distinct in that they can be constructed from several types of "things". For instance, a Tree can contain either nothing (Empty), a Leaf, or a Node.
data Tree = Empty
| Leaf Int
| Node Tree Tree
Since a Node is composed of two Trees, algebraic data types can be recursive.
Pattern matching allows algebraic data types to be deconstructed in a way that maintains type safety. Consider the following implementation of depth and its pseudocode equivalent:
depth :: Tree -> Int
depth Empty = 0
depth (Leaf n) = 1
depth (Node l r) = 1 + max (depth l) (depth r)
switch on (data.constructor)
let l = data.field1
let r = data.field2
return 1 + max (depth l) (depth r)
This has the disadvantage that the programmer must remember to case Empty before Leaf so that field1 is not accessed on an Empty tree. Likewise, the Leaf case must be declared before the Node case so that field2 is not accessed on Leaf. Thus type safety is thus not maintained by the language but rather imposes additional cognitive load on the programmer. By the way, I'm grabbing these examples directly from the wikipedia pages.
Of course, a duck-typing langauge could do something like this:
attr_accessor :field1, :field2
1 + [field1.depth, field2.depth].max
So algebraic data types may not be strictly better than their OOP equivalent, but they do provide a different set of tensions to work with when constructing software.