views:

833

answers:

6

I have a circular, statically allocated buffer in C, which I'm using as a queue for a depth breadth first search. I'd like have the top N elements in the queue sorted. It would be easy to just use a regular qsort() - except it's a circular buffer, and the top N elements might wrap around. I could, of course, write my own sorting implementation that uses modular arithmetic and knows how to wrap around the array, but I've always thought that writing sorting functions is a good exercise, but something better left to libraries.

I thought of several approaches:

  1. Use a separate linear buffer - first copy the elements from the circular buffer, then apply qsort, then copy them back. Using an additional buffer means an additional O(N) space requirement, which brings me to
  2. Sort the "top" and "bottom" halve using qsort, and then merge them using the additional buffer
  3. Same as 2. but do the final merge in-place (I haven't found much on in-place merging, but the implementations I've seen don't seem worth the reduced space complexity)

On the other hand, spending an hour contemplating how to elegantly avoid writing my own quicksort, instead of adding those 25 (or so) lines might not be the most productive either...

Correction: Made a stupid mistake of switching DFS and BFS (I prefer writing a DFS, but in this particular case I have to use a BFS), sorry for the confusion.

Further description of the original problem:

I'm implementing a breadth first search (for something not unlike the fifteen puzzle, just more complicated, with about O(n^2) possible expansions in each state, instead of 4). The "bruteforce" algorithm is done, but it's "stupid" - at each point, it expands all valid states, in a hard-coded order. The queue is implemented as a circular buffer (unsigned queue[MAXLENGTH]), and it stores integer indices into a table of states. Apart from two simple functions to queue and dequeue an index, it has no encapsulation - it's just a simple, statically allocated array of unsigned's.

Now I want to add some heuristics. The first thing I want to try is to sort the expanded child states after expansion ("expand them in a better order") - just like I would if I were programming a simple best-first DFS. For this, I want to take part of the queue (representing the most recent expanded states), and sort them using some kind of heuristic. I could also expand the states in a different order (so in this case, it's not really important if I break the FIFO properties of the queue).

My goal is not to implement A*, or a depth first search based algorithm (I can't afford to expand all states, but if I don't, I'll start having problems with infinite cycles in the state space, so I'd have to use something like iterative deepening).

+5  A: 

I think you need to take a big step back from the problem and try to solve it as a whole - chances are good that the semi-sorted circular buffer is not the best way to store your data. If it is, then you're already committed and you will have to write the buffer to sort the elements - whether that means performing an occasional sort with an outside library, or doing it when elements are inserted I don't know. But at the end of the day it's going to be ugly because a FIFO and sorted buffer are fundamentally different.


Previous answer, which assumes your sort library has a robust and feature filled API (as requested in your question, this does not require you to write your own mod sort or anything - it depends on the library supporting arbitrary located data, usually through a callback function. If your sort doesn't support linked lists, it can't handle this):

The circular buffer has already solved this problem using % (mod) arithmetic. QSort, etc don't care about the locations in memory - they just need a scheme to address the data in a linear manner.

They work as well for linked lists (which are not linear in memory) as they do for 'real' linear non circular arrays.

So if you have a circular array with 100 entries, and you find you need to sort the top 10, and the top ten happen to wrap in half at the top, then you feed the sort the following two bits of information:

  • The function to locate an array item is (x % 100)
  • The items to be sorted are at locations 95 to 105

The function will convert the addresses the sort uses into an index used in the real array, and the fact that the array wraps around is hidden, although it may look weird to sort an array past its bounds, a circular array, by definition, has no bounds. The % operator handles that for you, and you might as well be referring to the part of the array as 1295 to 1305 for all it cares.

Bonus points for having an array with 2^n elements.


Additional points of consideration:

It sounds to me that you're using a sorting library which is incapable of sorting anything other than a linear array - so it can't sort linked lists, or arrays with anything other than simple ordering. You really only have three choices:

  • You can re-write the library to be more flexible (ie, when you call it you give it a set of function pointers for comparison operations, and data access operations)
  • You can re-write your array so it somehow fits your existing libraries
  • You can write custom sorts for your particular solution.

Now, for my part I'd re-write the sort code so it was more flexible (or duplicate it and edit the new copy so you have sorts which are fast for linear arrays, and sorts which are flexible for non-linear arrays)

But the reality is that right now your sort library is so simple you can't even tell it how to access data that is non linearly stored.

If it's that simple, there should be no hesitation to adapting the library itself to your particular needs, or adapting your buffer to the library.

Trying an ugly kludge, like somehow turning your buffer into a linear array, sorting it, and then putting it back in is just that - an ugly kludge that you're going to have to understand and maintain later. You're going to 'break' into your FIFO and fiddle with the innards.

Adam Davis
I know - that's why I wrote that I could "write my own sorting implementation that uses modular arithmetic" - but this is the one thing that I specifically want to avoid. Also, you have to be very careful implementing comparisons (which % doesn't help you with, you have to use differences).
Kim Sullivan
In that case you need to describe the library implementation of the sorting algorithm. What is the API/interface to the sort you already have? If it doesn't support arbitrary mapping then you can't even sort linked lists with it and it's a fairly limited library.
Adam Davis
I'm using standard C (not C++ or C#), with no additional libraries. I'll edit my original question to go into more detail as to what I'm trying to do.
Kim Sullivan
You still haven't answered my question. You claim you want to leave the sorting algorithm in the library - but you don't tell us which library. The C standard does not include sorting, so we're kind of left at a loss here - what is the sorting API you're trying to shoehorn into your program?
Adam Davis
http://en.wikipedia.org/wiki/Qsort_%28C_Standard_Library%29
Kim Sullivan
+1  A: 

Perhaps a priority queue could be adapted to solve your issue.'

EvilTeach
A simple priority queue has O(n) insertion time, which leads to O(n^2) in my case (same as insertion sort). I'm not sure about using more complicated heap based queues - could be problematic because I still need the whole buffer as a queue, not just the top N sorted elements (an array of queues?)
Kim Sullivan
Well, sure, if you use a linked list with linear search, you'll get O(n) insertion time. Don't do that -- use a *real* priority queue. There are actually some very simple ones, especially if you're interested in amortized performance instead of worst case.
comingstorm
You seem fine with reordering your items in-place; there's no reason why you can't iterate through the priority queue structure to access everything. If N is small, you might consider combining a sorted array for the N top elements with a heap for the remaining elements.
comingstorm
+2  A: 

You could rotate the circular queue until the subset in question no longer wraps around. Then just pass that subset to qsort like normal. This might be expensive if you need to sort frequently or if the array element size is very large. But if your array elements are just pointers to other objects then rotating the queue may be fast enough. And in fact if they are just pointers then your first approach might also be fast enough: making a separate linear copy of a subset, sorting it, and writing the results back.

Brian Ensink
This is a O(N) operation for N the size of the queue. And it comes up in roughly n/N cases for n the size of the region to be sorted. The average is then O(n), which is equivalent to Kim's option 1, if you can stand the possibly large in the bad cases. Not bad.
dmckee
I agree, it's an interesting idea. Also, I didn't consider the fact that the "special" sort only happens in n/N rounds (the buffer is large enough to hold a lot of regions).
Kim Sullivan
+1  A: 

I'm not seeing exactly the solution you asked for in c. You might consider one of these ideas:

  • If you have access to the source for your libc's qsort(), you might copy it and simply replace all the array access and indexing code with appropriately generalized equivalents. This gives you some modest assurance that the underling sort is efficient and has few bugs. No help with the risk of introducing your own bugs, of course. Big O like the system qsort, but possibly with a worse multiplier.

  • If the region to be sorted is small compared to the size of the buffer, you could use the straight ahead linear sort, guarding the call with a test-for-wrap and doing the copy-to-linear-buffer-sort-then-copy-back routine only if needed. Introduces an extra O(n) operation in the cases that trip the guard (for n the size of the region to be sorted), which makes the average O(n^2/N) < O(n).


I see that C++ is not an option for you. ::sigh:: I will leave this here in case someone else can use it.

  • If C++ is an option you could (subclass the buffer if needed and) overload the [] operator to make the standard sort algorithms work. Again, should work like the standard sort with a multiplier penalty.
dmckee
+1  A: 

Do you know about the rules regarding optimization? You can google them (you'll find a few versions, but they all say pretty much the same thing, DON'T).

It sounds like you are optimizing without testing. That's a huge no-no. On the other hand, you're using straight C, so you are probably on a restricted platform that requires some level of attention to speed, so I expect you need to skip the first two rules because I assume you have no choice:

Rules of optimization:

  1. Don't optimize.

  2. If you know what you are doing, see rule #1

You can go to the more advanced rules:

Rules of optimization (cont):

  1. If you have a spec that requires a certain level of performance, write the code unoptimized and write a test to see if it meets that spec. If it meets it, you're done. NEVER write code taking performance into consideration until you have reached this point.

  2. If you complete step 3 and your code does not meet the specs, recode it leaving your original "most obvious" code in there as comments and retest. If it does not meet the requirements, throw it away and use the unoptimized code.

  3. If your improvements made the tests pass, ensure that the tests remain in the codebase and are re-run, and that your original code remains in there as comments.

Note: that should be 3. 4. 5. Something is screwed up--I'm not even using any markup tags.

Okay, so finally--I'm not saying this because I read it somewhere. I've spent DAYS trying to untangle some god-awful messes that other people coded because it was "Optimized"--and the really funny part is that 9 times out of 10, the compiler could have optimized it better than they did.

I realize that there are times when you will NEED to optimize, all I'm saying is write it unoptimized, test and recode it. It really won't take you much longer--might even make writing the optimized code easier.

The only reason I'm posting this is because almost every line you've written concerns performance, and I'm worried that the next person to see your code is going to be some poor sap like me.

Bill K
Yes, you're probably right - but I'm mostly considering big-oh "performance". Just because a bubble sort is easy to code it doesn't mean an "optimized" algorithm (like quicksort, merge- or heapsort) shouldn't be used "prematurely".
Kim Sullivan
I know what you mean, but does the performance matter? For that matter, do you need to be using a circular buffer when a linked list would work? Is it small enough that an array would actually work as well? Have you written tests to prove this?
Bill K
My point is that every choice you make is making your future decisions harder and more complicated. You might reconsider how you design everything. I work on cable boxes with ARM cpus, and we still use Java--maybe 50% less efficient off the top, but well worth it for the improved readability alone!
Bill K
Changing the algorithm to do less work is not optimising in the sense that you are critiquing. Low level optimisation should be left until last, but transforming the algorithm from O(n^2) to O(n log n) or similar should be done asap.
Tom Leys
I agree that using a circular buffer sounds like optimization at the expense of simplicity for 'no good reason'
Tom Leys
Tom, in general I somewhat agree with your statement about orders of magnitude, but I can't say it's absolute. A very large portion of the time, how long a segment of code takes to run is absolutely meaningless, but clear code is always valuable, so code it twice.
Bill K
A: 

How about somthing like this example here. This example easely sorts a part or whatever you want without having to redefine a lot of extra memory. It takes inly two pointers a status bit and a counter for the for loop.

#define _PRINT_PROGRESS
#define N 10
BYTE buff[N]={4,5,2,1,3,5,8,6,4,3};
BYTE *a = buff;
BYTE *b = buff;
BYTE changed = 0;
int main(void)
{
    BYTE n=0;
    do
    {
     b++;
     changed = 0;
     for(n=0;n<(N-1);n++)
     {
      if(*a > *b)
      {
       *a ^= *b;
       *b ^= *a;
       *a ^= *b;
       changed = 1;
      }
      a++;
      b++;
     }
     a = buff;
     b = buff;
#ifdef _PRINT_PROGRESS
     for(n=0;n<N;n++)
      printf("%d",buff[n]);
     printf("\n");
    }
#endif
    while(changed);
    system( "pause" );
}
eaanon01