问题
I used an algorithm that I've used in the past for arrays. This always picks the first element as the pivot. Here's the code:
void quickSort(int a[],int l,int r,int *count)
{
if(l<r-1)
{
*count=*count+r-l-1;
int q=partition(a,l,r); //finding the pivot position in sorted array
quickSort(a,l,q-1,count); //recursive calling before pivot sub array
quickSort(a,q+1,r,count); //recursive calling after pivot sub array
}
}
//partition function definition
int partition(int a[],int l,int r)
{
int j,temp,i=l+1;
for(j=l+1;j<r;j++)
{
//swap values if a[j]<=a[r](i.e. pivot)
if(a[j]<=a[l])
{
temp=a[j];
a[j]=a[i];
a[i]=temp;
i++;
}
}
//place pivot at its position by swapping
temp=a[i-1];
a[i-1]=a[l];
a[l]=temp;
return i;
}
Now here is when I try to implement this to a doubly linked list. "head" represents the head to the linked list
void recQuick(void* head, node* s, node* e, int (*comparator)(void*,void*)) {
//s = (node*) head;
if ( e != NULL && s != e && s != e->next ) { //we want to cycle through the linked list
node* pivot = (node*) realQuickSorter(head,s,e,(comparator));
recQuick(head,s,pivot->prev, (comparator));
recQuick(head,pivot->next,e, (comparator));
}
//return 1;
}
node* realQuickSorter ( void* head, node* s, node* e, int (*comparator)(void*,void*)) {
char* piv = s->str; //will always be the first element
node* leader = s->next;
//node* ii = s->next;
node* follower = leader;
for ( follower = leader; follower != e ; follower = follower->next ) {
if ( ((comparator)(follower->str,s->str)) == 1 ) { //pivot is bigger, we need to swap
swap(&(follower->str),&(leader->str));
leader = (leader == NULL) ? s : leader->next;
//leader = leader->next;
}
}
swap(&(s->str),&(leader->prev->str));
return leader->prev;
}
Functions like swap, bringMeEnd are correct
The linked list version only seems to swap the first two when out of order, leaving the rest the same
回答1:
Assuming that e is a pointer to the last node in a sub-list, the for loop in realQuickSorter() stops before comparing the last node with the pivot. There may be other issues.
It would help if compare and swap functions were included, as well as the code that generates the test list and calls recQuick().
Here is example code based on the original question. The fixes are noted in comments. I changed variable names to match some old code I had. The names follower
and leader
were backwards from the way they are used. In my example code, I switched to using pi
and pj
, as pointer to node equivalents to indexes i
and j
as used for arrays. I reversed the sense of the compare function to be the same as strcmp (assuming the goal is to sort from lowest to highest string value).
recQuick was missing a check for lo (s
) == NULL, which can happen if the pivot ends up in the first node, where pivot->prev == NULL. realQuickSorter needed two fixes: include the last node (hi or e
) when comparing versus pivot. If the pivot ends up in the last node, then pi (leader
) may end up as NULL (if hi->next == NULL), so a check is made and pi is set to hi in this case, else it's set to pi->prev.
void recQuick(node* lo, node* hi, int (*comparator)(void*,void*))
{
node* pv;
if(lo == NULL || hi == NULL || lo == hi || lo == hi->next) /* fix */
return;
pv = (node*) realQuickSorter(lo,hi,(comparator));
recQuick(lo, pv->prev, (comparator));
recQuick(pv->next, hi, (comparator));
}
node* realQuickSorter(node* lo, node* hi, int (*comparator)(void*, void*))
{
node* pi = lo->next;
node* pj;
for(pj = pi; pj != hi->next ; pj = pj->next ){ /* fix */
if(((comparator)(pj->str, lo->str)) <= 0 ){ /* changed comparator */
swap(&(pj->str),&(pi->str));
pi = pi->next; /* fix */
}
}
if(pi == hi->next) /* fix (hi->next could be NULL) */
pi = hi; /* fix */
else /* fix */
pi = pi->prev; /* fix */
swap(&(lo->str),&(pi->str)); /* fix */
return pi; /* fix */
}
This is an inefficient way to sort a linked list. M Oehm's answer should be a bit better, but a bottom up merge sort for linked list would be faster:
https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation_using_lists
On a large list with scattered nodes, despite what algorithm is used, every node access could involve a cache miss. Assuming enough memory, it would be faster to copy the list data into an array, sort the array, and create a new linked list.
回答2:
You try to implement quicksort for linked lists based on an already existing implementation for arrays. You do so by swapping the nodes' values. In my opinion, that's not ideal. Linked lists are not arrays. You should swap the nodes instead.
(There's probably a reason your data exists as linked list. If the nodes' payload is large, swapping the data is ineffecient. If you have outside pointers to the nodes, swapping will invalidate them.)
How does quicksort work?
- Pick a pivot and remove it from the list.
- Partition the remaining list into
- elements that are less than the pivot (le),
- elements that are equal to the pivot and
- elements that are greater than the pivot (gt) by some metric.
- Run quicksort on the partitions so that you list now looks like this:
sorted le partition | pivot | sorted gt partition
In the array implementation, you achieve that by swapping the elements around and by moving the pivot. That's a good strategy, because that way you need extra space.
In a linked list, you can create the partitions by extracting the current list into two partition lists. Call quicksort on those and then stitch them together again with the pivot in the middle.
If you have lists with many elements that have the same value, i.e. that compare equal, you can make the single-node pivot a third list, eq.
Here's some code that does that. The functions
pop
, which pops the first node off a list;append
which appends a node at the end of a list andjoin
, which appends the second list to the first
are not shown, but the algorithm itself should be clear. It is pretty straightforward. Each node has a next
and ´prevpointer as well as some
data; a list has
headand
tail` pointers.
Anyway, here goes:
void quicksort(List *list)
{
if (list->head != list->tail) {
List eq = {NULL, NULL};
List lt = {NULL, NULL};
List gt = {NULL, NULL};
append(&eq, pop(list));
while (list->head) {
Node *node = pop(list);
int cmp = compare(node->data, eq.head->data);
if (cmp < 0) {
append(<, node);
} else if (cmp > 0) {
append(>, node);
} else {
append(&eq, node);
}
}
quicksort(<);
quicksort(>);
join(list, <);
join(list, &eq);
join(list, >);
}
}
This sorting agoritm is stable: Elements with the same value have the same order in the sorted and in the original list. A complete example program that includes the functions pop
, join
and extract
is here on ideone.
回答3:
I don't think you can do that in a for loop
来源:https://stackoverflow.com/questions/60466837/quicksort-on-a-doubly-linked-list-not-working-as-it-should