Shortcode strips formatting and returns at content top

the_sub_field echos content. The docs are quite clear about that.

The the_sub_field function is used with the repeater field and the
flexible content field (license key required) to display a sub field
value. When looping through one of these fields, this function
displays a sub field from the current row.

This is the same as echo get_sub_field();

http://www.advancedcustomfields.com/resources/functions/the_sub_field/

Use get_sub_field to construct a string.

The function names follow a common (but not universal) WordPress pattern or use the the_ prefix for functions that echo and the get_ prefix for functions that return.

However, there will still be a problem in that your code completely overwrites $string at each iteration. You need to concatenate a string with all of the results, which you can do easily with .= instead of just =.

function teamlist_shortcode($atts, $content = null) {
  if (get_field('aw_team_members')):
    $string = '';
    while (has_sub_field('aw_team_members')):
      $string .= '<h3>' . the_sub_field('team_name') . '</h3><h3 class="subtitle">' . the_sub_field('team_title') . '</h3>' . the_sub_field('team_bio');
    endwhile;
    return $string;
  endif; 
}
add_shortcode('teamlist', 'teamlist_shortcode');