A ListView will do this fairly easily, as it has grouping templates. A Repeater for the Categories, with nested GridViews for the items will also work.
If you're set on a GridView, it'll take some hackery. You can either modify the data to put in categorized rows, subclass the GridView to add new rows, add a custom control to inject markup into the GridView's HTML (I use one that closes the current row and adds a new colspanned row), or use JQuery on the client.
Subclassing is the "right" way - and can be reusable - but it's a bit of work. Modifying the data is fairly easy, but it's pretty ugly and dependent on how you're binding to the data. JQuery may actually be the easiest route here. Set the category in the 1st column, create new rows as the category changes, and then hide the 1st column. It downgrades nicely as well, since noscript will be category in 1st column. This isn't tested, but it should get you close:
var lastCategory = '';
$('TR > TD:first').each(function() {
var thisCategory = $(this).text().trim();
if (thisCategory != lastCategory) {
// new category - insert subheading row
var thisRow = $(this).parent();
var colspan = $('> TD', thisRow).length;
thisRow.insertBefore(
'<tr class="subheading">' +
'<td colspan="' + colspan.toString() + '">' +
thisCategory +
'</td>' +
'</tr>'
);
lastCategory = thisCategory;
}
}).hide(); // hide 1st columns