Best method to sort a linked list (in my case)?

Discussion in 'C Programming' started by Kent, Oct 21, 2003.

  1. Kent

    Kent Guest

    Hi!

    I want to store data (of enemys in a game) as a linked list, each node will
    look something like the following:

    struct node
    {
    double x,y; // x and y position coordinates
    struct enemy *enemydata; // Holds information about an enemy (in a game)
    // Its a double linked list node
    struct node *prev;

    struct node *next;
    };

    Each node´s x and y coordinates in the linked list changes for every frame.
    Before drawing the enemys to the game screen (graphic memory) i would like
    to sort the entire list based on the y coordinate (lowest first).

    My question is, wich sort algorithm do you recommend? As it is a linked
    list, i thought that insertion sort would work as a linked list is insertion
    sort´s "best case". But i would like some opinions from more experienced
    programmers about this. Please excouse my poor english, it is not my native
    language.

    Best regards,
    Kent
    Kent, Oct 21, 2003
    #1
    1. Advertising

  2. Kent

    Eric Sosman Guest

    Kent wrote:
    >
    > Hi!
    >
    > I want to store data (of enemys in a game) as a linked list, each node will
    > look something like the following:
    >
    > struct node
    > {
    > double x,y; // x and y position coordinates
    > struct enemy *enemydata; // Holds information about an enemy (in a game)
    > // Its a double linked list node
    > struct node *prev;
    >
    > struct node *next;
    > };
    >
    > Each node´s x and y coordinates in the linked list changes for every frame.
    > Before drawing the enemys to the game screen (graphic memory) i would like
    > to sort the entire list based on the y coordinate (lowest first).
    >
    > My question is, wich sort algorithm do you recommend? As it is a linked
    > list, i thought that insertion sort would work as a linked list is insertion
    > sort´s "best case". But i would like some opinions from more experienced
    > programmers about this. Please excouse my poor english, it is not my native
    > language.


    This does not seem to be a question about C, but about
    choosing an algorithm. comp.graphics.algorithms would be a
    better forum for your question. You will probably want to
    use an algorithm that takes advantage of the fact that your
    list is "almost sorted" to begin with, even if that method
    is not especially good for "random" lists.

    --
    Eric Sosman, Oct 21, 2003
    #2
    1. Advertising

  3. Kent

    Kent Guest

    > This does not seem to be a question about C, but about
    >choosing an algorithm. comp.graphics.algorithms would be a
    >better forum for your question. You will probably want to
    >use an algorithm that takes advantage of the fact that your
    >list is "almost sorted" to begin with, even if that method
    >is not especially good for "random" lists.


    It isn´t about a graphical algorithm either. If someone else has an actual
    answer i would appreciate it much.
    Kent, Oct 21, 2003
    #3
  4. In article <>,
    Eric Sosman <> wrote:

    > Kent wrote:
    > >
    > > Hi!
    > >
    > > I want to store data (of enemys in a game) as a linked list, each node will
    > > look something like the following:
    > >
    > > struct node
    > > {
    > > double x,y; // x and y position coordinates
    > > struct enemy *enemydata; // Holds information about an enemy (in a game)
    > > // Its a double linked list node
    > > struct node *prev;
    > >
    > > struct node *next;
    > > };
    > >
    > > Each node´s x and y coordinates in the linked list changes for every frame.
    > > Before drawing the enemys to the game screen (graphic memory) i would like
    > > to sort the entire list based on the y coordinate (lowest first).
    > >
    > > My question is, wich sort algorithm do you recommend? As it is a linked
    > > list, i thought that insertion sort would work as a linked list is insertion
    > > sort´s "best case". But i would like some opinions from more experienced
    > > programmers about this. Please excouse my poor english, it is not my native
    > > language.

    >
    > This does not seem to be a question about C, but about
    > choosing an algorithm. comp.graphics.algorithms would be a
    > better forum for your question. You will probably want to
    > use an algorithm that takes advantage of the fact that your
    > list is "almost sorted" to begin with, even if that method
    > is not especially good for "random" lists.


    shakersort (bubblesort in alternating directions) is quite good for this
    situation, and works quite well with a doubly linked list.
    Christian Bau, Oct 21, 2003
    #4
  5. Kent

    CBFalconer Guest

    Kent wrote:
    >
    > I want to store data (of enemys in a game) as a linked list,
    > each node will look something like the following:
    >
    > struct node
    > {
    > double x,y; // x and y position coordinates
    > struct enemy *enemydata; // Holds information about an enemy
    > // Its a double linked list node
    > struct node *prev;
    > struct node *next;
    > };
    >
    > Each node´s x and y coordinates in the linked list changes for
    > every frame. Before drawing the enemys to the game screen
    > (graphic memory) i would like to sort the entire list based on
    > the y coordinate (lowest first).
    >
    > My question is, wich sort algorithm do you recommend? As it is
    > a linked list, i thought that insertion sort would work as a
    > linked list is insertion sort´s "best case". But i would like
    > some opinions from more experienced programmers about this.
    > Please excouse my poor english, it is not my native language.


    Definitely mergesort, because it will work on the lists directly.
    It is also O(NlogN) at all times. You can find an example in
    wdfreq.c demo in hashlib.zip, at:

    <http://cbfalconer.home.att.net/download/>

    --
    Chuck F () ()
    Available for consulting/temporary embedded and systems.
    <http://cbfalconer.home.att.net> USE worldnet address!
    CBFalconer, Oct 22, 2003
    #5
  6. Kent

    pete Guest

    Kent wrote:
    >
    > Hi!
    >
    > I want to store data (of enemys in a game) as a linked list,
    > each node will look something like the following:
    >
    > struct node
    > {
    > double x,y; // x and y position coordinates
    > struct enemy *enemydata;
    > // Its a double linked list node
    > struct node *prev;
    > struct node *next;
    > };
    >
    > Each node´s x and y coordinates in the linked list
    > changes for every frame.
    > Before drawing the enemys to the game screen
    > (graphic memory) i would like to sort the entire list
    > based on the y coordinate (lowest first).
    >
    > My question is, wich sort algorithm do you recommend?
    > As it is a linked list,
    > i thought that insertion sort would work as a
    > linked list is insertion sort´s "best case".
    > But i would like some opinions from more experienced
    > programmers about this. Please excouse my poor english,
    > it is not my native language.


    Check out the funky goto, in l_sort().

    /* BEGIN node.c */

    #include <stdio.h>
    #include <stdlib.h>

    #define NODES 20

    #define GTE(A, B) ((A) -> y >= (B) -> y)

    #define LU_RAND_SEED 123456789LU
    #define LU_RAND(S) ((S) = (S) * 69069 + 362437 & 0xffffffff)

    struct node {
    double x, y;
    struct enemy *enemydata;
    struct node *prev;
    struct node *next;
    };

    void l_free(struct node *);
    struct node *l_make(size_t);
    long unsigned l_init(struct node *, long unsigned);
    void l_print(struct node *);
    struct node *l_sort(struct node *, const size_t);

    int main(void)
    {
    struct node *head;

    head = l_make(NODES);
    if (head) {
    l_init(head, LU_RAND_SEED);
    l_print(head);
    head = l_sort(head, NODES);
    l_print(head);
    l_free(head);
    } else {
    puts("The list was not allocated.");
    }
    return 0;
    }

    void l_free(struct node *pointer)
    {
    while (pointer) {
    struct node *next = pointer -> next;

    free(pointer);
    pointer = next;
    }
    }

    struct node *l_make(size_t nodes)
    {
    struct node *pointer, *head;

    head = nodes ? malloc(sizeof *pointer) : NULL;
    if (head) {
    pointer = head;
    pointer -> prev = NULL;
    while (--nodes != 0) {
    pointer -> next = malloc(sizeof *pointer -> next);
    if (!pointer -> next) {
    l_free(head);
    return NULL;
    }
    pointer -> next -> prev = pointer;
    pointer = pointer -> next;
    }
    pointer -> next = NULL;
    }
    return head;
    }

    long unsigned l_init(struct node *pointer, long unsigned seed)
    {
    while (pointer) {
    pointer -> y = 3.14159265 * LU_RAND(seed);
    pointer = pointer -> next;
    }
    return seed;
    }

    void l_print(struct node *pointer)
    {
    while (pointer) {
    printf("pointer -> y is %f\n", pointer -> y);
    pointer = pointer -> next;
    }
    putchar('\n');
    }

    struct node *l_sort(struct node *base, const size_t count)
    {
    size_t half, source;
    struct {
    struct node *head;
    struct node *pointer;
    } list[2];

    if (2 > count) {
    return base;
    }
    list[0].pointer = list[0].head = base;
    half = count / 2;
    while (--half != 0) {
    list[0].pointer = list[0].pointer -> next;
    }
    list[1].head = list[0].pointer -> next;
    list[1].head -> prev = list[0].pointer -> next = NULL;
    half = count / 2;
    list[0].head = l_sort(list[0].head, half);
    list[1].head = l_sort(list[1].head, count - half);
    list[0].pointer = list[0].head;
    list[1].pointer = list[1].head;
    source = GTE(list[1].head, list[0].head) ? 0 : 1;
    base = list[source].head;
    outer_loop_top:
    while (list[source].pointer -> next) {
    list[source].pointer = list[source].pointer -> next;
    while(GTE(list[source ^ 1].pointer, list[source].pointer)){
    goto outer_loop_top;
    }
    list[source].pointer->prev->next = list[source ^ 1].pointer;
    list[source ^ 1].pointer->prev = list[source].pointer->prev;
    source ^= 1;
    }
    list[source].pointer -> next = list[source ^ 1].pointer;
    list[source ^ 1].pointer -> prev = list[source].pointer;
    return base;
    }

    /* END node.c */
    pete, Oct 23, 2003
    #6
  7. Kent

    pete Guest

    pete wrote:

    > struct node *l_sort(struct node *base, const size_t count)


    /* BEGIN l_sort.c */

    #include <stddef.h>

    #define GTE(A, B) ((A) -> y >= (B) -> y)

    #define PREV prev
    #define NEXT next
    #define N_TYPE struct node
    typedef N_TYPE n_type;

    struct node {
    double x, y;
    struct enemy *enemydata;
    struct node *prev;
    struct node *next;
    };

    n_type *l_sort(n_type *, const size_t);

    n_type *l_sort(n_type *base, const size_t count)
    {
    size_t half, source;
    n_type *pointer[2];

    if (count > 1) {
    pointer[0] = base;
    half = source = count / 2;
    while (--half != 0) {
    base = base -> NEXT;
    }
    pointer[1] = base -> NEXT;
    pointer[1] -> PREV = base -> NEXT = 0;
    half = source;
    pointer[0] = l_sort(pointer[0], half);
    pointer[1] = l_sort(pointer[1], count - half);
    source = GTE(pointer[1], pointer[0]) ? 0 : 1;
    base = pointer[source];
    loop_top:
    while (pointer[source] -> NEXT) {
    pointer[source] = pointer[source] -> NEXT;
    if (GTE(pointer[source ^ 1], pointer[source])) {
    goto loop_top;
    }
    pointer[source] -> PREV -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source] -> PREV;
    source ^= 1;
    }
    pointer[source] -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source];
    }
    return base;
    }

    /* END l_sort.c */



    --
    pete
    pete, Oct 23, 2003
    #7
  8. Kent

    pete Guest

    pete wrote:

    > base = pointer[source];
    > loop_top:
    > while (pointer[source] -> NEXT) {
    > pointer[source] = pointer[source] -> NEXT;
    > if (GTE(pointer[source ^ 1], pointer[source])) {
    > goto loop_top;
    > }


    "continue" is the word I was looking for.

    n_type *l_sort(n_type *base, const size_t count)
    {
    size_t half, source;
    n_type *pointer[2];

    if (count > 1) {
    pointer[0] = base;
    source = half = count / 2;
    while (--source != 0) {
    base = base -> NEXT;
    }
    pointer[1] = base -> NEXT;
    pointer[1] -> PREV = base -> NEXT = 0;
    pointer[0] = l_sort(pointer[0], half);
    pointer[1] = l_sort(pointer[1], count - half);
    source = GTE(pointer[1], pointer[0]) ? 0 : 1;
    base = pointer[source];
    while (pointer[source] -> NEXT) {
    pointer[source] = pointer[source] -> NEXT;
    if (GTE(pointer[source ^ 1], pointer[source])) {
    continue;
    }
    pointer[source] -> PREV -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source] -> PREV;
    source ^= 1;
    }
    pointer[source] -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source];
    }
    return base;
    }

    --
    pete
    pete, Oct 23, 2003
    #8
  9. Kent

    pete Guest

    pete wrote:
    >
    > pete wrote:
    >
    > > base = pointer[source];
    > > loop_top:
    > > while (pointer[source] -> NEXT) {
    > > pointer[source] = pointer[source] -> NEXT;
    > > if (GTE(pointer[source ^ 1], pointer[source])) {
    > > goto loop_top;
    > > }

    >
    > "continue" is the word I was looking for.


    No, it wasn't.

    n_type *l_sort(n_type *base, const size_t count)
    {
    size_t half, source;
    n_type *pointer[2];

    if (count > 1) {
    pointer[0] = base;
    source = half = count / 2;
    while (--source != 0) {
    base = base -> NEXT;
    }
    pointer[1] = base -> NEXT;
    pointer[1] -> PREV = base -> NEXT = 0;
    pointer[0] = l_sort(pointer[0], half);
    pointer[1] = l_sort(pointer[1], count - half);
    source = GTE(pointer[1], pointer[0]) ? 0 : 1;
    base = pointer[source];
    while (pointer[source] -> NEXT) {
    pointer[source] = pointer[source] -> NEXT;
    if (GT(pointer[source], pointer[source ^ 1])) {
    pointer[source] -> PREV -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source] -> PREV;
    source ^= 1;
    }
    }
    pointer[source] -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source];
    }
    return base;
    }

    --
    pete
    pete, Oct 23, 2003
    #9
  10. Kent

    pete Guest

    /*
    Kent wrote:

    Hi!

    I want to store data (of enemys in a game) as a linked list,
    each node will look something like the following:

    struct node
    {
    double x,y; // x and y position coordinates
    struct enemy *enemydata;
    // Holds information about an enemy (in a game)
    // Its a double linked list node
    struct node *prev;

    struct node *next;
    };

    Each node´s x and y coordinates in the linked list changes
    for every frame. Before drawing the enemys to the game screen
    (graphic memory) i would like to sort the entire list
    based on the y coordinate (lowest first).

    My question is, wich sort algorithm do you recommend?
    As it is a linked list, i thought that insertion sort would work
    as a linked list is insertion sort´s "best case".
    But i would like some opinions from more experienced programmers
    about this.
    */

    /*
    ** Mergesort is extremely well suited to sorting linked lists.
    */

    /* BEGIN listsort.c */
    /*
    ** l_sort() is a nonstable sorting function for doubley linked lists.
    ** ls_sort() is a nonstable sorting function for singley linked lists.
    ** sl_sort() is a stable sorting function for doubley linked lists.
    ** sls_sort() is a stable sorting function for singley linked lists.
    ** If the parameters "list" and "count" don't correspond to
    ** the head of the list, and the tail of the list,
    ** then the sorting functions won't work right.
    */
    #include <stdio.h>
    #include <stdlib.h>

    #define NODES 16
    /*
    ** These next 4 macros, are the ones that you would change,
    ** to use l_sort() on a double linked list of any other type node.
    ** l_init() and l_print(), as written, will only work with a
    ** node which has a member named y, of type double.
    */
    #define N_TYPE \
    struct node {double x, y; struct node *prev; struct node *next;}
    #define GT(A, B) ((A) -> y > (B) -> y)
    #define PREV prev
    #define NEXT next

    typedef N_TYPE n_type;

    #define LU_RAND_SEED 123456789LU
    #define LU_RAND(S) ((S) = (S) * 69069 + 362437 & 0xffffffff)

    void l_free (n_type *);
    n_type *l_make (size_t);
    long unsigned l_init (n_type *, long unsigned);
    void l_print(n_type *);
    n_type *l_sort (n_type *, size_t);
    n_type *sl_sort (n_type *, size_t);
    n_type *ls_sort (n_type *, size_t);
    n_type *sls_sort (n_type *, size_t);

    int main(void)
    {
    n_type *list;

    list = l_make(NODES);
    if (list) {
    l_init(list, LU_RAND_SEED);
    puts("Random order");
    l_print(list);
    list = l_sort(list, NODES);
    puts("Sorted order");
    l_print(list);
    l_free(list);
    } else {
    puts("The list was not allocated.");
    }
    return 0;
    }

    void l_free(n_type *pointer)
    {
    while (pointer) {
    n_type *NEXT = pointer -> NEXT;

    free(pointer);
    pointer = NEXT;
    }
    }

    n_type *l_make(size_t nodes)
    {
    n_type *pointer, *list;

    list = nodes ? malloc(sizeof *list) : NULL;
    if (list) {
    pointer = list;
    pointer -> PREV = NULL;
    while (--nodes != 0) {
    pointer -> NEXT = malloc(sizeof *pointer -> NEXT);
    if (!pointer -> NEXT) {
    l_free(list);
    return NULL;
    }
    pointer -> NEXT -> PREV = pointer;
    pointer = pointer -> NEXT;
    }
    pointer -> NEXT = NULL;
    }
    return list;
    }

    long unsigned l_init(n_type *pointer, long unsigned seed)
    {
    while (pointer) {
    pointer -> y = 3.14159265 * LU_RAND(seed);
    pointer = pointer -> NEXT;
    }
    return seed;
    }

    void l_print(n_type *pointer)
    {
    while (pointer) {
    printf("pointer -> y is %f\n", pointer -> y);
    pointer = pointer -> NEXT;
    }
    putchar('\n');
    }

    n_type *l_sort(n_type *list, size_t count)
    {
    size_t half, source;
    n_type *pointer[2];

    if (count > 1) {
    source = half = count / 2;
    pointer[1] = list;
    do {
    pointer[1] = pointer[1] -> NEXT;
    } while (--source != 0);
    pointer[1] -> PREV -> NEXT = 0;
    pointer[1] -> PREV = 0;
    pointer[1] = l_sort(pointer[1], count - half);
    pointer[0] = l_sort(list, half);
    if (GT(pointer[0], pointer[1])) {
    source = 1;
    }
    list = pointer[source];
    while (pointer[source] -> NEXT) {
    pointer[source] = pointer[source] -> NEXT;
    if (GT(pointer[source], pointer[source ^ 1])) {
    pointer[source] -> PREV -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source] -> PREV;
    source ^= 1;
    }
    }
    pointer[source ] -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source ];
    }
    return list;
    }

    n_type *sl_sort(n_type *list, size_t count)
    {
    size_t half, source;
    n_type *pointer[2];

    if (count > 1) {
    source = half = count / 2;
    pointer[1] = list;
    do {
    pointer[1] = pointer[1] -> NEXT;
    } while (--source != 0);
    pointer[1] -> PREV -> NEXT = 0;
    pointer[1] -> PREV = 0;
    pointer[1] = l_sort(pointer[1], count - half);
    pointer[0] = l_sort(list, half);
    if (GT(pointer[0], pointer[1])) {
    source = 1;
    }
    list = pointer[source];
    while (pointer[source] -> NEXT) {
    pointer[source] = pointer[source] -> NEXT;
    if (!source) {
    if (GT(pointer[0], pointer[1])) {
    pointer[0] -> PREV -> NEXT = pointer[1];
    pointer[1] -> PREV = pointer[0] -> PREV;
    source ^= 1;
    }
    } else {
    if (!GT(pointer[0], pointer[1])) {
    pointer[1] -> PREV -> NEXT = pointer[0];
    pointer[0] -> PREV = pointer[1] -> PREV;
    source ^= 1;
    }
    }
    }
    pointer[source ] -> NEXT = pointer[source ^ 1];
    pointer[source ^ 1] -> PREV = pointer[source ];
    }
    return list;
    }

    n_type *ls_sort(n_type *list, size_t count)
    {
    size_t half, source;
    n_type *pointer[2], *prev_pointer;

    if (count > 1) {
    source = half = count / 2;
    prev_pointer = list;
    while (--source != 0) {
    prev_pointer = prev_pointer -> NEXT;
    }
    pointer[1] = prev_pointer -> NEXT;
    prev_pointer -> NEXT = 0;
    pointer[1] = l_sort(pointer[1], count - half);
    pointer[0] = l_sort(list, half);
    if (GT(pointer[0], pointer[1])) {
    source = 1;
    }
    list = pointer[source];
    while (pointer[source] -> NEXT) {
    prev_pointer = pointer[source];
    pointer[source] = pointer[source] -> NEXT;
    if (GT(pointer[source], pointer[source ^ 1])) {
    prev_pointer -> NEXT = pointer[source ^ 1];
    source ^= 1;
    }
    }
    pointer[source] -> NEXT = pointer[source ^ 1];
    }
    return list;
    }

    n_type *sls_sort(n_type *list, size_t count)
    {
    size_t half, source;
    n_type *pointer[2], *prev_pointer;

    if (count > 1) {
    source = half = count / 2;
    prev_pointer = list;
    while (--source != 0) {
    prev_pointer = prev_pointer -> NEXT;
    }
    pointer[1] = prev_pointer -> NEXT;
    prev_pointer -> NEXT = 0;
    pointer[1] = l_sort(pointer[1], count - half);
    pointer[0] = l_sort(list, half);
    if (GT(pointer[0], pointer[1])) {
    source = 1;
    }
    list = pointer[source];
    while (pointer[source] -> NEXT) {
    prev_pointer = pointer[source];
    pointer[source] = pointer[source] -> NEXT;
    if (!source) {
    if (GT(pointer[0], pointer[1])) {
    prev_pointer -> NEXT = pointer[1];
    source ^= 1;
    }
    } else {
    if (!GT(pointer[0], pointer[1])) {
    prev_pointer -> NEXT = pointer[0];
    source ^= 1;
    }
    }
    }
    pointer[source] -> NEXT = pointer[source ^ 1];
    }
    return list;
    }
    /* END listsort.c */

    --
    pete
    pete, Oct 24, 2003
    #10
  11. Kent

    Tom St Denis Guest

    "pete" <> wrote in message
    news:...
    > Each node´s x and y coordinates in the linked list changes
    > for every frame. Before drawing the enemys to the game screen
    > (graphic memory) i would like to sort the entire list
    > based on the y coordinate (lowest first).


    Well there are several solutions. You could just make an array of lists for
    each possible y. If there are too many you could try a heap instead which
    has the benefit of using fixed storage [cuz generally for games you want to
    use a fixed amount of heap lest you run into a "out of memory" message half
    way through a hard level... stupid jedi knights].

    If you really want to use a linked list you could indecies into the list for
    the start of each y. That way you can insert in O(1) time [best case] and
    near O(n) worst case [which won't happen often]. So say you want to insert
    you look into the index for the given y. If it's found you insert and
    adjust the neighbours next/prev pointers. If it isn't found you move up the
    index table until you find a non NULL and then step through the list until
    you find the last value.

    e.g. say you want y=6 but there are no index markings for y=6. You then
    look at y=5. Say there is a y=5. You step through the list until you find
    an entry with y != 5 and you wedge your item between.

    Eventually all of the indecies likely to be used will have hits and inserts
    will take O(1) time.

    So along as you have a feasible number of y values [say 768 or whatever] the
    index will be small [e.g. <4KB]

    Tom
    Tom St Denis, Oct 24, 2003
    #11
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Chris Ritchey
    Replies:
    7
    Views:
    476
    emerth
    Jul 10, 2003
  2. Chris Ritchey

    Generating a char* from a linked list of linked lists

    Chris Ritchey, Jul 9, 2003, in forum: C Programming
    Replies:
    7
    Views:
    463
    emerth
    Jul 10, 2003
  3. fool
    Replies:
    14
    Views:
    502
    Barry Schwarz
    Jul 3, 2006
  4. joshd
    Replies:
    12
    Views:
    665
    John Carson
    Oct 2, 2006
  5. Navin
    Replies:
    1
    Views:
    685
    Ken Schaefer
    Sep 9, 2003
Loading...

Share This Page