[net.lang.c] Sorting linked lists

rcpilz@ablnc.UUCP (Robert C. Pilz) (03/13/86)

 
I would like some advice on sorting linked lists. I
am running SVR2 UNIX on a VAX 11/780 and 3B20S. I
would like to know if there is a way to sort a linked list.
I know that a table can be sorted via qsort(3c). But
it assumes that every element is contiguous in memory.
In a linked list this is not the case. Do I do a
memory copy of some type to get the link list contiguous?
I would like to know what works.
 
Send replies to:
ihnp4!abfll!rcpilz
Thanks

chris@umcp-cs.UUCP (Chris Torek) (03/16/86)

In article <165@ablnc.UUCP> rcpilz@ablnc.UUCP writes:
 
>I would like to know if there is a way to sort a linked list.

You get to roll your own.  I started to think about this, and blew
a couple of hours writing the code below.  This will work on a
doubly linked list if you turn it into a tree first.  (You can turn
it back into a list when you are done.)  These routines assume that
copying nodes is expensive: instead of swapping values, they move
nodes about in the tree.  If all you have are values, this is
inefficient, but usually each node in the tree has much more data
than that.  Besides, using this method, the code need not know the
size of the nodes, so an abstracted C++ version should be easy to
write.

[hsort.c]

#define STANDALONE
/* #define VERBOSE */

/*
 * hsort - heap sort.
 *
 * Builds a linked list using the `left' pointers.
 *
 * Call hsort only on heaps; use tree = heapify(tree) if tree needs
 * to be formed into a heap first.
 *
 * For convenience, hsort() returns its argument.  I.e., it is legal
 * to say, e.g., `printlist(hsort(heapify(tree)))'.
 */

#include <stdio.h>

/* replace this node structure with your own */
struct node {
	struct node *left, *right;
	int value;
};

/*
 * Build the heap (smallest at the top).
 *
 * This algorithm assumes that copying nodes is expensive.
 *
 * See comments at hsort() for generalisation directions.
 */
struct node *
heapify(root)
	register struct node *root;
{
	register struct node *cand;
	struct node *t;

	if (root == NULL)
		return (NULL);

	/*
	 * Handle no-children case early (simplifies following code).
	 */
	if (root->left == NULL && root->right == NULL)
		return (root);	/* nothing to do */

	/*
	 * Form the left and right sub-heaps.
	 */
	root->left = heapify(root->left);
	root->right = heapify(root->right);

	/*
	 * Put the smallest node at the top of the tree.
	 * If there is a left node, and:
	 *	there is no right node, or
	 *	the left node's value is less than the right node's value,
	 * then the right is not the smallest, so it is either the left
	 * or the root.  Otherwise, it is either the right or the root.
	 * (We have guaranteed that either left or right is non nil.)
	 */
	if ((cand = root->left) != NULL &&
	    (root->right == NULL || cand->value < root->right->value)) {
		if (cand->value < root->value) {
			/*
			 * Swap the root's left node (cand) and the
			 * root itself.  Re-form the left subheap.
			 */
			t = root->right;
			root->right = cand->right;
			cand->right = t;
			root->left = cand->left;
			cand->left = heapify(root);
			return (cand);
		}
	} else {
		cand = root->right;
		if (cand->value < root->value) {
			/*
			 * Swap the root's right node (cand) and the
			 * root itself.
			 */
			t = root->left;
			root->left = cand->left;
			cand->left = t;
			root->right = cand->right;
			cand->right = heapify(root);
			return (cand);
		}
	}

	return (root);
}

/*
 * Do the heap sort (smallest values first).
 *
 * This algorithm assumes that copying nodes is expensive.
 *
 * This would be better written using classes; that and a
 * comparison function would generalise the routine.  (Of
 * course, the heapify() routine would also need the same
 * comparison function.)
 */
struct node *
hsort(root)
	struct node *root;
{
	register struct node *l, *r, *p;
	register struct node **arrow;
	struct node **listp, *top;
	struct node temp;

	top = root;		/* grab the top of the tree */
	root = NULL;		/* in case there is no tree */
	listp = &root;		/* build return list through *listp */
	while ((p = top) != NULL) {
		/*
		 * `top' (and now p) is the current top of the tree, and
		 * thus by the heap property is also the smallest node.
		 * Append it to the list, but first set up the temporary
		 * node to hold on to the rest of the tree.
		 */
		temp.left = p->left;
		temp.right = p->right;
		*listp = p;
		p->left = NULL;
		p->right = NULL;
		listp = &p->left;

		/*
		 * Form a new top at the temporary node, and set the arrow
		 * to point at whatever points at the temporary node (in
		 * this case `top').
		 */
		top = &temp;
		arrow = &top;

		/*
		 * Now bubble the temporary node down to the bottom of
		 * the tree, re-forming the heap in the process (the
		 * temporary node is the `least value' node).  `arrow'
		 * always points at that which points at the temporary node.
		 */
		for (;;) {
			l = temp.left;
			r = temp.right;

			/*
			 * We are done iff there is no left and no right.
			 */
			if (l == NULL && r == NULL) {
				/*
				 * Remove the temporary node from the tree.
				 */
				*arrow = NULL;
				break;
			}

			/*
			 * We know that it goes somewhere.
			 * It goes on the left if there is a left and:
			 *	there is no right, or
			 *	left's value < right's value
			 * Otherwise it goes on the right.
			 */
			if (l != NULL && (r == NULL || l->value < r->value)) {
				/*
				 * Move the temporary node down to the left.
				 */
				temp.left = l->left;
				l->left = &temp;
				temp.right = l->right;
				l->right = r;
				*arrow = l;
				arrow = &l->left;
			} else {
				/*
				 * Move the temporary node down to the right.
				 * We know r != NULL.
				 */
				temp.right = r->right;
				r->right = &temp;
				temp.left = r->left;
				r->left = l;
				*arrow = r;
				arrow = &r->right;
			}
		}
	}
	return (root);
}

#ifdef STANDALONE
/* these routines need not be (and indeed, are not) efficient */

/*
 * Print a list chained via lefts.
 */
printlist(p)
	register struct node *p;
{

	while (p) {
		printf("%d%c", p->value, p->left ? ' ' : '\n');
		p = p->left;
	}
}

#ifdef VERBOSE
/*
 * Print a tree.  If you stand on your left ear it will look a proper tree.
 */
printtree(p)
	struct node *p;
{

	doprinttree(p, 0, (struct node *) NULL);
}

/*
 * Helper function: print node p at depth depth; if p==nil && force!=nil,
 * force a `nil'.  This makes the tree `spread' properly.
 */
doprinttree(p, depth, force)
	register struct node *p;
	int depth;
	struct node *force;
{
	register int i = depth;

	if (p == NULL) {
		if (force != NULL) {
			while (--i >= 0)
				printf("   ");
			printf("nil\n");
		}
		return;
	}
	doprinttree(p->right, depth + 1, p->left);
	while (--i >= 0)
		printf("   ");
	printf("%d\n", p->value);
	doprinttree(p->left, depth + 1, p->right);
}
#endif /* VERBOSE */

char *malloc();

/*
 * Create a node with the given value.
 */
struct node *
make(v)
	int v;
{
	register struct node *p = (struct node *) malloc(sizeof (*p));

	if (p == NULL) {
		fprintf(stderr, "out of memory");
		exit(1);
	}
	p->left = NULL;
	p->right = NULL;
	p->value = v;
	return (p);
}

/*
 * Return the depth of the tree t.
 */
int
depth(t)
	register struct node *t;
{

	return (t == NULL ? 0 : depth(t->left) + depth(t->right) + 1);
}

/*
 * Insert a node into a tree, keeping the left and right weights balanced.
 * Return the new tree.
 */
struct node *
insert(t, p)
	register struct node *t;
	struct node *p;
{

	if (t == NULL)
		return (p);
	if (depth(t->left) <= depth(t->right))
		t->left = insert(t->left, p);
	else
		t->right = insert(t->right, p);
	return (t);
}

struct node *
readtree()
{
	register struct node *tree = NULL;
	int v;

	while ((fputs("> ", stdout), scanf("%d", &v)) == 1)
		tree = insert(tree, make(v));
	printf("\n\n");
	return (tree);
}

/*ARGSUSED*/
main(argc, argv)
	int argc;
	char **argv;
{
#ifdef VERBOSE
	struct node *root;
#endif

	printf("enter tree values\n");
#ifdef VERBOSE
	root = readtree();
	printf("original tree:\n");
	printtree(root);

	root = heapify(root);
	printf("after heapify:\n");
	printtree(root);

	(void) hsort(root);
	printf("after hsort:\n");
	printlist(root);
#else
	printlist(hsort(heapify(readtree())));
#endif
	exit(0);
}
#endif /* STANDALONE */
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1415)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu

shap@bunker.UUCP (Joseph D. Shapiro) (03/18/86)

In article <165@ablnc.UUCP> rcpilz@ablnc.UUCP (Robert C. Pilz) writes:
>
> 
>I would like some advice on sorting linked lists. I
>am running SVR2 UNIX on a VAX 11/780 and 3B20S. I
>would like to know if there is a way to sort a linked list.
>I know that a table can be sorted via qsort(3c). But
>it assumes that every element is contiguous in memory.
>In a linked list this is not the case. Do I do a
>memory copy of some type to get the link list contiguous?
>I would like to know what works.
> 
>Send replies to:
>ihnp4!abfll!rcpilz
>Thanks

try the following:

	traverse your linked list, making an array of pointers to
	each node.

	write a comparison routine to compare two nodes, given two
	pointers to pointers to nodes.

	use qsort to sort your array of pointers to nodes.

	traverse the array of pointers to nodes, re-writing the links
	in the nodes as you go.

hope this helps

-- 
-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_

Joseph D. Shapiro			"He who hesitates
Bunker Ramo Information Systems		 is lunch"

{bellcore, genrad, mcnc, sunybcs}\
{harpo, linus, decwrl, tektronix}->!decvax!ittvax!bunker!shap
{dartvax, ucbvax, wanginst, yale}/

sdcsvax!dcdwest!ittvax!bunker!shap

g-rh@cca.UUCP (Richard Harter) (03/18/86)

In article <> chris@umcp-cs.UUCP (Chris Torek) writes:
>In article <165@ablnc.UUCP> rcpilz@ablnc.UUCP writes:
> 
>>I would like to know if there is a way to sort a linked list.
>
>You get to roll your own.  I started to think about this, and blew
>a couple of hours writing the code below......

A simpler method, given qsort with a compare function as an
argument, is to create an array of pointers to the nodes and
write the appropriate compare routine to compare the keys being
pointed at.  Qsort then shuffles the pointers around to produce
the sorted list.  You can then relink the list in sorted order
using the array of pointers.

	Richard Harter, SMDS Inc.

greg@utcsri.UUCP (Gregory Smith) (03/18/86)

In article <337@umcp-cs.UUCP> chris@umcp-cs.UUCP (Chris Torek) writes:
>In article <165@ablnc.UUCP> rcpilz@ablnc.UUCP writes:
>>I would like to know if there is a way to sort a linked list.
>You get to roll your own.  I started to think about this, and blew
>a couple of hours writing the code below.  This will work on a
>doubly linked list if you turn it into a tree first.  (You can turn
[ 300+ lines of code deleted ]

You can use qsort. Just malloc up an array of pointers ( assume the linked list
is made of struct foo's:)

	struct foo **Index;		/* pointer to the array */
	Index = ( struct foo **) malloc( list_size * sizeof( struct foo *));

Then fill in this array with pointers to each element in the linked list.

	struct foo * Head;		/* head of list */

	register struct foo *rp, **bp;

	rp = Head; bp = Index;
	while( rp ){
		*bp++ = rp;
		rp = rp->link;
	}


Then:

	qsort( Index, list_size, sizeof( struct foo*), comp );

Note that we are sorting the pointer array, not the actual list.
This means that 'comp(a,b)' is handed two pointers to pointers to struct
foo:
	comp(a,b) struct foo **a,**b;
	{	return strcmp( (*a)->name, (*b)->name));
	}
( The above will sort on a 'name' element within foo which is of type
char * or char [] )

After that, you have an array of pointers to the list which is ordered the
way you want it. You can use this to traverse the list, or you can use it
to re-link the list in the correct order:

	for(i=0; i<list_size-1; ++i){
		Index[i]->link = Index[i+1];
			/* indexing used instead of pointers for clarity*/
	}
	Index[ list_size-1] = 0;		/* end of list */
	Head = Index[0];			/* new list head */
	free( Index );			/* done! */

Of course, the actual foo records themselves have not moved.
This is _the_ way to go if the list is long and the records are too large
to copy. The heap sort given by Chris Torek may be slightly faster, but I
don't like writing or debugging big sort routines so I like
to use qsort whenever possible.

-- 
"No eternal reward will forgive us now for wasting the dawn" -J. Morrison
----------------------------------------------------------------------
Greg Smith     University of Toronto       ..!decvax!utzoo!utcsri!greg

gwyn@brl-smoke.ARPA (Doug Gwyn ) (03/20/86)

In article <165@ablnc.UUCP> rcpilz@ablnc.UUCP (Robert C. Pilz) writes:
>I would like some advice on sorting linked lists.

I believe the "list merge" sorting algorithm described in
Knuth Vol. 3 can be adapted to this case.  Since it works
by changing link fields, rather than by actually moving
records, it would not break other pointers in the records.

List merge sorting is one of my favorite in-memory methods,
primarily for its stability (see Knuth for definition).
It was at the heart of an interactive traffic analysis
system I whipped up on a 24Kb paper-tape based CDC 1700.
(Oh, how I miss the good old days!)

chris@umcp-cs.UUCP (Chris Torek) (03/20/86)

In article <6721@cca.UUCP> g-rh@cca.UUCP (Richard Harter) writes:
[in response to my heap sort]

>A simpler method, given qsort with a compare function as an
>argument, is to create an array of pointers to the nodes
[and sort through the pointers].

Indeed you can; I was simply trying to avoid using extra space.
(And I had a lot of fun writing my first ever heap sort.)

Personally, I like the merge sort in article <402@mips.UUCP> by
hansen@mips.  Maybe I will convert that one next.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1415)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu

oster@ucblapis.berkeley.edu (David Phillip Oster) (03/28/86)

In article <402@mips.UUCP> hansen@mips.UUCP (Craig Hansen) writes:
>There's a simpler way, using a merge sort instead of a heap sort.
>The code below is O(n log n), and relies only on singly linked
>be fairly simple to translate.  The basic idea is to take the
>list as pairs, sorting each pair, then take pairs of sorted
>pairs and merge them together, so so on, taking geometrically
>increasing-sized lists, until you have a single sorted list.
>Craig Hansen
>MIPS Computer Systems
>...decwrl!glacier!mips!hansen
>         for ...
>		while ...
>                    i := i - 1;
>                    trailer := point;
>                    point := point^.next;
>                end {while};
>                if sublist[j] <> nil then trailer^.next := nil;
>            end {for};

Sorry guys, this is a stupid way to sort a list. Although your comaprison
function is called O(n log n), the above code is executed n^2 times.  It 
just blows the time codt to hell.  I sort my lists by copying them into an
array, sorting the array using qsort, then copying back to a list.

--- David Phillip Oster
-----------------------  ``What do you look like when you aren't visualizing?''
Arpa: oster@lapis.berkeley.edu
Uucp: ucbvax!ucblapis!oster

hansen@mips.UUCP (Craig Hansen) (03/31/86)

> In article <402@mips.UUCP> hansen@mips.UUCP (Craig Hansen) writes:
> >There's a simpler way, using a merge sort instead of a heap sort.
> >The code below is O(n log n), and relies only on singly linked
> >be fairly simple to translate.  The basic idea is to take the
> >list as pairs, sorting each pair, then take pairs of sorted
> >pairs and merge them together, so so on, taking geometrically
> >increasing-sized lists, until you have a single sorted list.
> >Craig Hansen
> >MIPS Computer Systems
> >...decwrl!glacier!mips!hansen
> >         for ...
> >		while ...
> >                    i := i - 1;
> >                    trailer := point;
> >                    point := point^.next;
> >                end {while};
> >                if sublist[j] <> nil then trailer^.next := nil;
> >            end {for};
> 
> Sorry guys, this is a stupid way to sort a list. Although your comaprison
> function is called O(n log n), the above code is executed n^2 times.  It 
> just blows the time codt to hell.  I sort my lists by copying them into an
> array, sorting the array using qsort, then copying back to a list.
> 
> --- David Phillip Oster
> -----------------------  ``What do you look like when you aren't visualizing?''
> Arpa: oster@lapis.berkeley.edu
> Uucp: ucbvax!ucblapis!oster

David, you can sort your lists anyway stupid way you god-damn please.  ~==~
The code you excerpt above is NOT executed n^2 times, it's
executed O(n log n) times. Try profiling it if you can't reason it out.

To explain further, the outer-most loop is executed O(log n) times:
    
    MergeLength := 1;
    while MergeLength < SymbolCount do begin
        ...
        MergeLength := MergeLength * 2;
    end {while};
	
The inner loop is order n; even though it is composed of nested loops:

	point := table;
	while point <> nil do begin
	    ...
	end {while};

executes n/MergeLength times, and the loop:

                i := MergeLength;
                while (point <> nil) and (i > 0) do begin
                    i := i - 1;
		    ...
		end {while};

executes MergeLength times; the end result is O(n).  Another way of seeing
this is by noting that the inner loop moves "point" through the list exactly
once.

Did I lose anybody?

-- 

Craig Hansen			|	 "Evahthun' tastes
MIPS Computer Systems		|	 bettah when it
...decwrl!mips!hansen		|	 sits on a RISC"

throopw@dg_rtp.UUCP (Wayne Throop) (04/04/86)

I've posted a "C" version of Craig Hansen's NlogN singly-linked list
sort to net.sources.  It is titled "singly-linked list sort in C".  It
is organized as a general-purpose routine, usable to sort lists of any
node type.  It assumes that your C compiler does reasonable things with
structure layout, but this can be made compiler specific fairly easily
if your compiler is peculiar.

The main differences of this routine relative to Craig Hansen's offering
are these:

  - I've heavily commented it to make clear what is going on.
    (I did this because I didn't follow in detail what Craig's did until
     I had translated it to C and tinkered with it a little.  Craig's
     was *clear* enough, I was just a little slow...)
  - I've generalized it as mentioned above.
  - I've traded time spent moving a bookeeping pointer through the list
    against keeping a little more bookeeping information around. (The
    bookeeping space is still constant in N, however.)
  - I've included a small testing routine, so you can see if it works
    for you.

Have fun, sorting freeks, and my apologies if zillions of list sorting
routines have already been posted to net.sources.
-- 
Wayne Throop at Data General, RTP, NC
<the-known-world>!mcnc!rti-sel!dg_rtp!throopw