Hmm...
If you have a source of randomness (a few bits worth during configuration), you could do something shitty like...
Master: "Generate random address."
Master: "Report address."
(in unison)
Node 0: "I'm node 0"
Node 1: "I'm node 10"
Node 2: "I'm node 6"
Node 3: "I'm node 0"
...
(You'll need something like I2C to arbitrate different nodes -- at least, the ones that split up.)
The lowest (bitwise) wins arbitration, so the master records address 0.
Master: "Everyone but node 0, report address."
(in unison)
Node 1: "I'm node 10"
Node 2: "I'm node 6"
...
Master reads 10 (or 6, depending on which direction the bitstream goes; I forget).
And so on. Repeat until no more unique node addresses are reported.
Now we have addresses, but we don't know if we have collisions. So,
Master: "Node 0, pick a new random address."
Node 0: "I'm node 27"
Node 3: "I'm node 0"
And so on, through the same enumeration process.
Now: this will NOT guarantee zero collisions. If the statistics of your RNG are good, you can guarantee a probability, and after repeating the splitting process over all nodes (so this becomes an O(N^2) or worse algorithm), you can guarantee P chance of getting no collisions within O(N^2 / P) sort of time. Or something like that. That's very crude, and the real answer depends on choice between space (do you reserve the 127 addresses of I2C, or implement your own identifier of whatever size in the data format?) and number of nodes (>64 nodes is very likely to have a collision for each split command, <12 is fairly unlikely to).
If the master has knowledge of total nodes, it can count them, and terminate the algorithm when all have been found, but it has no way to tell which ones have collided, so the process still goes slowly.
Problems like these are best avoided at design time (e.g., daisy chain), or with external means (IDs, manual enumeration, etc.).
Tim