Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Randomly permute N first elements of a singly linked list

I have to permute N first elements of a singly linked list of length n, randomly. Each element is defined as:

typedef struct E_s
{
  struct E_s *next;
}E_t;

I have a root element and I can traverse the whole linked list of size n. What is the most efficient technique to permute only N first elements (starting from root) randomly?

So, given a->b->c->d->e->f->...x->y->z I need to make smth. like f->a->e->c->b->...x->y->z

My specific case:

  • n-N is about 20% relative to n
  • I have limited RAM resources, the best algorithm should make it in place
  • I have to do it in a loop, in many iterations, so the speed does matter
  • The ideal randomness (uniform distribution) is not required, it's Ok if it's "almost" random
  • Before making permutations, I traverse the N elements already (for other needs), so maybe I could use this for permutations as well

UPDATE: I found this paper. It states it presents an algorithm of O(log n) stack space and expected O(n log n) time.

like image 859
psihodelia Avatar asked Dec 12 '10 12:12

psihodelia


People also ask

How do you efficiently generate a random permutation of 1 N?

Approach: Create an array of N elements and initialize the elements as 1, 2, 3, 4, …, N then shuffle the array elements using Fisher-Yates shuffle Algorithm. Fisher–Yates shuffle Algorithm works in O(n) time complexity. The assumption here is, we are given a function rand() that generates a random number in O(1) time.

What is the probability of selecting a random node from a given singly linked list?

Given a singly linked list, select a random node from the linked list (the probability of picking a node should be 1/N if there are N nodes in the list).

How do you randomly shuffle a linked list in C++?

The safest way to shuffle a linked list is copy the data to an array, shuffle the array, and then copy the results of the array back to your list. This is assuming you have functions that goes through your list, and functions to copy data back to your list.

How do you generate random permutations?

A simple algorithm to generate a permutation of n items uniformly at random without retries, known as the Fisher–Yates shuffle, is to start with any permutation (for example, the identity permutation), and then go through the positions 0 through n − 2 (we use a convention where the first element has index 0, and the ...


2 Answers

I've not tried it, but you could use a "randomized merge-sort".

To be more precise, you randomize the merge-routine. You do not merge the two sub-lists systematically, but you do it based on a coin toss (i.e. with probability 0.5 you select the first element of the first sublist, with probability 0.5 you select the first element of the right sublist).

This should run in O(n log n) and use O(1) space (if properly implemented).

Below you find a sample implementation in C you might adapt to your needs. Note that this implementation uses randomisation at two places: In splitList and in merge. However, you might choose just one of these two places. I'm not sure if the distribution is random (I'm almost sure it is not), but some test cases yielded decent results.

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

#define N 40

typedef struct _node{
  int value;
  struct _node *next;
} node;

void splitList(node *x, node **leftList, node **rightList){
  int lr=0; // left-right-list-indicator
  *leftList = 0;
  *rightList = 0;
  while (x){
    node *xx = x->next;
    lr=rand()%2;
    if (lr==0){
      x->next = *leftList;
      *leftList = x;
    }
    else {
      x->next = *rightList;
      *rightList = x;
    }
    x=xx;
    lr=(lr+1)%2;
  }
}

void merge(node *left, node *right, node **result){
  *result = 0;
  while (left || right){
    if (!left){
      node *xx = right;
      while (right->next){
    right = right->next;
      }
      right->next = *result;
      *result = xx;
      return;
    }
    if (!right){
      node *xx = left;
      while (left->next){
    left = left->next;
      }
      left->next = *result;
      *result = xx;
      return;
    }
    if (rand()%2==0){
      node *xx = right->next;
      right->next = *result;
      *result = right;
      right = xx;
    }
    else {
      node *xx = left->next;
      left->next = *result;
      *result = left;
      left = xx;
    }
  }
}

void mergeRandomize(node **x){
  if ((!*x) || !(*x)->next){
    return;
  }
  node *left;
  node *right;
  splitList(*x, &left, &right);
  mergeRandomize(&left);
  mergeRandomize(&right);
  merge(left, right, &*x);
}

int main(int argc, char *argv[]) {
  srand(time(NULL));
  printf("Original Linked List\n");
  int i;
  node *x = (node*)malloc(sizeof(node));;
  node *root=x;
  x->value=0;
  for(i=1; i<N; ++i){
    node *xx;
    xx = (node*)malloc(sizeof(node));
    xx->value=i;
    xx->next=0;
    x->next = xx;
    x = xx;
  }
  x=root;
  do {
    printf ("%d, ", x->value);
    x=x->next;
  } while (x);

  x = root;
  node *left, *right;
  mergeRandomize(&x);
  if (!x){
    printf ("Error.\n");
    return -1;
  }
  printf ("\nNow randomized:\n");
  do {
    printf ("%d, ", x->value);
    x=x->next;
  } while (x);
  printf ("\n");
  return 0;
}
like image 61
phimuemue Avatar answered Oct 18 '22 03:10

phimuemue


Convert to an array, use a Fisher-Yates shuffle, and convert back to a list.

like image 21
Mitch Wheat Avatar answered Oct 18 '22 05:10

Mitch Wheat