Given an array of size N, for example e={'A','B','C','D','E'}
N = 5 , we want to find all possible combinations of k elements in the array . For example, if k = 3, one possible combination is {'A','B','C'}
. Here, we have three different algorithms to find k combinations of the array .
Forward-backward algorithm
Here, we have two arrays and two main indexes r & i :
- Array e is an array of elements.
- Array pointer , the array pointer is used to save the index of the selected element.
- The index i points to the currently selected element in the array e .
- Point to the index r of the current position in the pointer array .
- As long as i & r does not exceed the length of the array, the algorithm will move forward by increasing i & r .
- If r reaches the last position of the pointer array, a combination will be printed.
- If these two indices reach the last poisition of the array it points to, the algorithm will regress by reducing the [R value
r--
, and set the value I usedi = pointer[r]+1
.
public static void combination(Object[] elements, int k){
// get the length of the array
// e.g. for {'A','B','C','D'} => N = 4
int N = elements.length;
if(k > N){
System.out.println("Invalid input, K > N");
return;
}
// init combination index array
int pointers[] = new int[k];
int r = 0; // index for combination array
int i = 0; // index for elements array
while(r >= 0){
// forward step if i < (N + (r-K))
if(i <= (N + (r - k))){
pointers[r] = i;
// if combination array is full print and increment i;
if(r == k-1){
print(pointers, elements);
i++;
}
else{
// if combination is not full yet, select next element
i = pointers[r]+1;
r++;
}
}
// backward step
else{
r--;
if(r >= 0)
i = pointers[r]+1;
}
}
}
Shift algorithm
- This algorithm is more intuitive than the first algorithm.
- We actually divide the elements array into two types of elements: k elements that can be selected and Nk elements that will be ignored.
- In each iteration, we select Nk non-ignorable elements.
- After each iteration, we will ignore the position movement of the element, as shown in the figure below.
public static void combination(Object[] e, int k){
int[] ignore = new int[e.length-k]; // --> [0][0]
int[] combination = new int[k]; // --> [][][]
// set initial ignored elements
//(last k elements will be ignored)
for(int w = 0; w < ignore.length; w++)
ignore[w] = e.length - k +(w+1);
int i = 0, r = 0, g = 0;
boolean terminate = false;
while(!terminate){
// selecting N-k non-ignored elements
while(i < e.length && r < k){
if(i != ignore[g]){
combination[r] = i;
r++; i++;
}
else{
if(g != ignore.length-1)
g++;
i++;
}
}
print(combination, e);
i = 0; r = 0; g = 0;
terminate = true;
// shifting ignored indices
for(int w = 0 ; w < ignore.length; w++){
if(ignore[w] > w){
ignore[w]--;
if(w > 0)
ignore[w-1] = ignore[w]-1;
terminate = false;
break;
}
}
}
}
Recursive algorithm
- The recursive algorithm has shorter steps.
- In each call to the function, we pass a list of elements, k and cumulative combination.
- Then we have four conditions:
- If
elements.length < k
then stop - If
k == 1
then add each element to the cumulative combination - If it is,
elements.length == k
all elements are added to the cumulative combination. - If it is, a recursive call
elements.length > k
is made to each elemente
to pass a sublist of the element list,k-1
and then the element is addede
to the accumulated combination.
- If
- As shown below
public static void combination(List<String> e, int k, String accumulated){
// 1. stop
if(e.size() < k)
return;
// 2. add each element in e to accumulated
if(k == 1)
for(String s:e)
print(accumulated+s);
// 3. add all elements in e to accumulated
else if(e.size() == k){
for(String s:e)
accumulated+=s;
print(accumulated);
}
// 4. for each element, call combination
else if(e.size() > k)
for(int i = 0 ; i < e.size() ; i++)
combination(e.subList(i+1, e.size()), k-1, accumulated+e.get(i));
}