发件人:torek@elf.bsdi.com (Chris Torek)
新闻组:comp.lang.c
主题:回复:排序链表
日期:1999年1月10日 05:39:20 -0800
Message-ID:<77aai8$f48@elf.bsdi.com>

Martin (Jambo@home.se) 写道
>> ...有没有更好的方法来排序链表?

在文章 <F5BpFJ.1Ms@fcshome.stoneham.ma.us> 中 fred smith <fredex@fcshome.stoneham.ma.us> 写道
>对已存在的链表进行排序可能很繁琐且
>资源密集。

实际上,归并排序通常既简单又高效。归并排序中最难的部分是将列表一分为二。如果你不关心“稳定性”排序,可以通过交替的方式轻松地将其分成两半

	/*
	 * split the list (turning this into a function, or inserting
	 * it into mergesort() below, is left as an exercise to the reader,
	 * as are better methods of splitting).
	 */
	struct list *left, *right, **lp, **rp;

	/* input: list != NULL; it has an even or odd number of entries */
	lp = &left;
	rp = &right;
	do {
		/* put one node on the left-hand list */
		*lp = list;
		lp = &list->next;
		list = list->next;

		/* that could be the last node, so check */
		if (list == NULL)
			break;

		/* put another node on the right-hand list */
		*rp = list;
		rp = &list->next;
		list = list->next;
		/* and repeat until we run out of nodes */
	} while (list != NULL);

	/*
	 * Make sure the two lists are properly terminated.  One of
	 * these two assignments is unnecessary, but so what?
	 */
	*lp = NULL;
	*rp = NULL;

将列表分成两半后,左右两半应该分别进行归并排序,然后可以将这两个列表合并起来。这就像拿到一副洗乱的扑克牌,把一半交给朋友让他排序,然后将两堆已排序好的牌进行逐对合并

/* merge left and right lists (of any length) */
struct list *merge(struct list *left, struct list *right,
    int (*compare)(struct list *, struct list *)) {
	struct list *new, **np;

	/*
	 * Build the new list by putting in the smaller of each pair.
	 */
	np = &new;
	while (left != NULL && right != NULL) {
		if (compare(left, right) > 0) {	/* left > right */
			*np = right;		/* so put the right node */
			np = &right->next;	/* onto the new list */
			right = right->next;
		} else {			/* left <= right */
			*np = left;		/* so put the left node */
			np = &left->next;	/* onto the new list */
			left = left->next;
		}
	}

	/*
	 * Now at least one of "left" and "right" is NULL, but not both.
	 * If the left list is non-empty, the right one is empty, and we
	 * just need to tack the left one on to the total list.  If the
	 * right list is non-empty, the left one is empty and we just need
	 * to tack on the right-hand one.  If both are empty, we need to
	 * set *np = NULL, but in this case, "*np = right" will do the
	 * trick too, so one single ?: expression suffices.
	 */
	*np = left != NULL ? left : right;
	return (new);
}

这样就只剩下排序一个空列表的退化情况在整个排序例程的顶部,你就会得到类似这样的结果

	struct list *mergesort(struct list *list,
	    int (*compare)(struct list *, struct list *)) {
		struct list *left, *right;

		if (list == NULL)	/* nothing to split */
			return NULL;
		split(list, &left, &right);
		left = mergesort(left, compare);
		right = mergesort(right, compare);
		return merge(left, right, compare);
	}

换句话说,每个要排序半副牌的朋友,都是通过将*那*半副牌一分为二,让另外两个朋友排序,然后再将它们合并起来来完成*他的*工作的。(根据上面的说明,与人不同的是,将“无牌”交给和取回“无牌”比识别出“一张牌”已经排好序要容易。也有其他的归并排序实现方式,包括进行一次初始的“计数节点”传递;在这种情况下,你可以停在“count < 2".)

>我更喜欢在构建时就确保它是排序好的。

在某些应用中,这是一种很好的方法;然而,它的复杂度通常是 O(n^2),而归并排序是 O(n log n)。通常,如果要保持数据沿途有序,使用某种树形结构是合适的。
--
现实生活中:Chris Torek,Berkeley Software Design Inc
El Cerrito, CA 域名:torek@bsdi.com +1 510 234 3167
http://claw.bsdi.com/torek/ (不一定在线) 我会将垃圾邮件报告给 abuse@。