-1

I have two list : list A containing a set of random numbers and list B empty. I have to sort the list A.

I can make limited operations on these two lists like :

  • move the first element of list A or B at the beginning of the list B or A
  • swap the two first elements of list A or B like 32 41 8 9 become 41 32 8 9
  • make the last element of list A or B become the first in this list (rotation) like 32 41 8 9 become 9 32 41 8
  • make the first element of list A or B become the last one in this list (rotation) like 32 41 8 9 become 41 8 9 32

I have already set up an algorithm to sort the list A using the list B as the stack and the set of allowed operations, but it takes time when the list becomes large (more than 1000 elements) and is therefore not performing at all.

I have also try to set up a merge sort algorithm with this set of operations but with 10k of numbers it's take too many time (over 10 seconds).

Anyone have an idea of an efficient algorithm to perform this sort quickly ? I'm also using linked list and do my program in C to optimize the efficiency.

  • What makes you think a linked list is the most efficient approach? – Scott Hunter Nov 27 '16 at 00:19
  • Because I don't care about indexes of any elements, I can only use the end or the begin of the list. – Hector Hansen Nov 27 '16 at 00:22
  • What makes you think such a list *can* be sorted efficiently, given the limited operations? – Scott Hunter Nov 27 '16 at 00:24
  • @HectorHansen Them cache misses though... – gowrath Nov 27 '16 at 00:25
  • @HectorHansen Also could you provide the function header and/or some test cases? Maybe one small one and one difficult one so that we can test it out. – gowrath Nov 27 '16 at 00:27
  • 2
    Are you at Epitech ? Do your homework alone ! – Stargateur Nov 27 '16 at 00:29
  • @Hector: I would presume a `deque` would be the ideal data structure for a double-ended queue. Although if you're rolling your own, a bounded circular circular queue would probably work even better. –  Nov 27 '16 at 00:30
  • Also, rather than just look at the runtime, also count the number of operations you do. If the number of operations is not growing with a reasonable complexity (e.g. you may very well have a quadratic algorithm or worse!), then it doesn't matter how efficient you can make those operations, your algorithm simply can't be made to run fast. –  Nov 27 '16 at 00:34
  • @Hurkyl I can't conceivably see this running faster than O(n^2) but I may be wrong. Maybe even n^2logn – gowrath Nov 27 '16 at 00:40
  • If you already have a linked list, then you have plenty of operations available beyond the ones you listed. Use a merge sort. It is well-suited to linked lists. – Raymond Chen Nov 27 '16 at 01:08
  • 2
    except for very small lists, linked list is generally a bad idea. That's what the C++ creator suggests: [Bjarne Stroustrup: Why you should avoid Linked Lists](https://youtu.be/YQs6IC-vgmo), [Are lists evil?—Bjarne Stroustrup](https://isocpp.org/blog/2014/06/stroustrup-lists), [Number crunching: Why you should never, ever, EVER use linked-list in your code again](https://www.codeproject.com/articles/340797/number-crunching-why-you-should-never-ever-ever-us), [Strategy: Stop Using Linked-Lists](http://highscalability.com/blog/2013/5/22/strategy-stop-using-linked-lists.html)... – phuclv Nov 27 '16 at 02:01
  • If your B is there just for sorting A then use insertion sort, it's [very fast on sorted list](http://stackoverflow.com/q/736920/995714) [Why is insertion sort faster than quick sort on a sorted array](http://stackoverflow.com/q/10268046/995714). Just take each element in A and insert to the sorted list in B. [C++ benchmark – std::vector VS std::list VS std::deque](http://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html) – phuclv Nov 27 '16 at 02:05
  • http://stackoverflow.com/q/33704858/905902 (almost) duplicate – wildplasser Nov 27 '16 at 15:50

1 Answers1

1

Missing from the problem statement is the ability to compare the first element of A with the first element of B.

Using a linked list will allow a fast rotate. Using a merge sort should be fairly fast.

Use rotate first to last to treat A and B as queues. Treat each queue as two queues, an input (front) queue and an output (back) queue. Use counts to keep track of the boundary between input and output part of each queue. A remaining run count will be needed for each input queue for the merge logic.

For the initial pass, separate the elements into two queues, get an element from A, append to B, get another element from A, append to A. Once this is done, then A and B contain runs of size 1.

Merge runs from A and B, again alternating merged run output between A and B so when done with a merge pass, the queues are ready for the next pass.

Eventually all the elements end up on one list and the sort is done.

update - I wrote a test program using C++ std::queue for the queues, and it takes less than 0.4 seconds to sort 1 million elements (32 bit mode, 32 bit integers) on my system (Intel 3770k 3.5 ghz). A C program using linked lists would involve less overhead than std::queue, so a bit faster.

Example C++ program for proof of concept, it's using two std::queues, using front / pop / push to effectively do a rotate. Pointers aren't needed for the input structures (qs0, qs1), but it makes the input and output references consistent. The output pointer to structure (pqso) alternates between the two queue structures (qs0, qs1). There are two goto's used to branch to common "cleanup" code for the inner loop, which copies the rest of the "other" run and breaks out of the inner loop. This example was based on an old merge sort program, and since this is a "proof of concept", I didn't bother changing it.

#include <iostream>
#include <queue>
#include <cstdlib>
#include <ctime>

typedef unsigned int uint32_t;

#define min(a, b)  (((a) < (b)) ? (a) : (b))

typedef struct{
    std::queue <uint32_t> *pq;              // ptr to queue
    size_t fcnt;                            // front count
    size_t bcnt;                            // back count
    size_t rcnt;                            // run count
}QS;

void msort2q(std::queue <uint32_t> &q0, std::queue <uint32_t> &q1, size_t n)
{
QS qs0;                                     // queue 0
QS qs1;                                     // queue 1
QS *pqsi0 = &qs0;                           // input 0
QS *pqsi1 = &qs1;                           // input 1
QS *pqso;                                   // output
size_t s;                                   // run size

    if(n < 2)
        return;
    pqsi0->pq = &q0;
    pqsi0->fcnt = n;
    pqsi0->bcnt = 0;
    pqsi1->pq = &q1;
    pqsi1->fcnt = 0;
    pqsi1->bcnt = 0;
    pqso = pqsi1;
    while(1){                               // split q0
        pqso->pq->push(pqsi0->pq->front());
        pqsi0->pq->pop();
        pqso->bcnt++;
        pqsi0->fcnt--;
        if(pqsi0->fcnt == 0)
            break;
        pqso = (pqso == pqsi0) ? pqsi1 : pqsi0;
    }
    pqsi0->fcnt = pqsi0->bcnt;
    pqsi0->bcnt = 0;
    pqsi1->fcnt = pqsi1->bcnt;
    pqsi1->bcnt = 0;
    s = 1;
    while(s < n){                       // merge sort
        while(1){                       // merge a pair of runs
            pqsi0->rcnt = min(s, pqsi0->fcnt);
            pqsi0->fcnt -= pqsi0->rcnt;
            pqsi1->rcnt = min(s, pqsi1->fcnt);
            pqsi1->fcnt -= pqsi1->rcnt;
            if(pqsi0->rcnt == 0)        // if end run 0
                goto copy1;             //   copy rest of 1 and break
            if(pqsi1->rcnt == 0)        // if end run 1
                goto copy0;             //   copy rest of 0 and break
            while(1){                   // merge an element
                if(pqsi0->pq->front() <= pqsi1->pq->front()){   // if q0 <= q1
                    pqso->pq->push(pqsi0->pq->front());         //   move q0
                    pqso->bcnt++;
                    pqsi0->pq->pop();
                    pqsi0->rcnt--;
                    if(pqsi0->rcnt != 0)    // if not end run 0
                        continue;           //   continue back to while
copy1:              do{                     //  else copy rest of run 1 and break
                        pqso->pq->push(pqsi1->pq->front());
                        pqso->bcnt++;
                        pqsi1->pq->pop();
                        pqsi1->rcnt--;
                    }while (pqsi1->rcnt);
                    break;
                } else {                                        // else q1 < q0
                    pqso->pq->push(pqsi1->pq->front());         //   move q1
                    pqso->bcnt++;
                    pqsi1->pq->pop();
                    pqsi1->rcnt--;
                    if(pqsi1->rcnt != 0)    // if not end run 1
                        continue;           //   continue back to while
copy0:              do{                     //  else copy rest of run 0 and break
                        pqso->pq->push(pqsi0->pq->front());
                        pqso->bcnt++;
                        pqsi0->pq->pop();
                        pqsi0->rcnt--;
                    }while (pqsi0->rcnt);
                    break;
                }
            }
            pqso = (pqso == pqsi0) ? pqsi1 : pqsi0;     // setup for next pair
            if(pqsi0->fcnt == 0 && pqsi1->fcnt == 0)    // if end pass break
                break;
        }
        pqsi0->fcnt = pqsi0->bcnt;      // setup for next pass
        pqsi0->bcnt = 0;
        pqsi1->fcnt = pqsi1->bcnt;
        pqsi1->bcnt = 0;
        s *= 2;
    }
    if(pqsi0->fcnt == 0)                // swap if needed
        std::swap(q0, q1);
}

#define NUMELEM (1024*1024)
int main()
{
clock_t dwTimeStart;                    // clock values
clock_t dwTimeStop;
uint32_t r;
size_t i;

    std::queue <uint32_t> q0;
    std::queue <uint32_t> q1;

//      generate data

    for(i = 0; i < NUMELEM; i++ )
    {
        r  = (((uint32_t)((rand()>>4) & 0xff))<< 0);
        r += (((uint32_t)((rand()>>4) & 0xff))<< 8);
        r += (((uint32_t)((rand()>>4) & 0xff))<<16);
        r += (((uint32_t)((rand()>>4) & 0xff))<<24);
        q0.push(r) ;
    }

//      sort data
    dwTimeStart = clock();
    msort2q(q0, q1, NUMELEM);
    dwTimeStop = clock();
    std::cout << "Number of ticks " << (dwTimeStop-dwTimeStart) << std::endl;

//      check data
    while(1){
        r = q0.front();
        q0.pop();
        if(q0.size() == 0)
            break;
        if(r > q0.front()){
            std::cout << "error" << std::endl;
            break;
        }
    }
    return 0;
}
rcgldr
  • 27,407
  • 3
  • 36
  • 61
  • If anyone is curious, it takes approximately 30 seconds on 10,000 ints with a stupid quicksort implementation on python. – gowrath Nov 27 '16 at 01:39
  • @gowrath: that's very slow, sorting 10000 ints on my measly Macbook takes about **1 millisecond** with `mergesort` and `qsort` and just above **100 microseconds** with `radixsort` in C – chqrlie Nov 28 '16 at 00:15
  • @chqrlie read: "a stupid implementation". If you'd like to share the qsort implementation please do in an answer, Id love to see it. – gowrath Nov 28 '16 at 01:48
  • @rcgldr No worries. I should have been clearer. Could you possibly post your mergesort solution? I was planning on implementing it but haven't quite gotten time to yet. – gowrath Nov 28 '16 at 15:54
  • @gowrath - It's been long enough since the question was asked, so I added example code.to my answer. – rcgldr Nov 28 '16 at 20:00
  • @rcgldr Thou commits [sin](https://xkcd.com/292/) :p. Nice solution though (y) – gowrath Nov 29 '16 at 10:39
  • @gowrath - removed duplicate code in the split q0 part (not sure how that happened), and at the end of msort2q(), changed the check for swap needed to use the front count. – rcgldr Nov 30 '16 at 23:29