Option #3(indexed tree) looks non-ideal but it actually has some really cool properties if you do it right.
1. If you know traversal order you can get really great cache coherency. We used to do this all the time with animation DAGs and the like in gamedev.
2. If everything is an "offset"/index instead of a pointer you can do things like in-place loading where creating something is just a single read() call. No constructors or annoying allocations to slow down loading. If you want to take this even further you can mmap() the entire file on disk and use massive(500mb+) files without using much actual memory overhead through letting the kernel page it in/out for you.
1. If you know traversal order you can get really great cache coherency. We used to do this all the time with animation DAGs and the like in gamedev.
2. If everything is an "offset"/index instead of a pointer you can do things like in-place loading where creating something is just a single read() call. No constructors or annoying allocations to slow down loading. If you want to take this even further you can mmap() the entire file on disk and use massive(500mb+) files without using much actual memory overhead through letting the kernel page it in/out for you.