As part of a school project, I need to write a function that will take an integer N and return a two-dimensional array of every permutation of the array {0, 1, ..., N-1}. The declaration would look like public static int[][] permutations(int N).
The algorithm described at http://www.usna.edu/Users/math/wdj/book/node156.html is how I've decided to implement this.
I wrestled for quite a while with arrays and arrays of ArrayLists and ArrayLists of ArrayLists, but so far I've been frustrated, especially trying to convert a 2d ArrayList to a 2d array.
So I wrote it in javascript. This works:
function allPermutations(N) {
// base case
if (N == 2) return [[0,1], [1,0]];
else {
// start with all permutations of previous degree
var permutations = allPermutations(N-1);
// copy each permutation N times
for (var i = permutations.length*N-1; i >= 0; i--) {
if (i % N == 0) continue;
permutations.splice(Math.floor(i/N), 0, permutations[Math.floor(i/N)].slice(0));
}
// "weave" next number in
for (var i = 0, j = N-1, d = -1; i < permutations.length; i++) {
// insert number N-1 at index j
permutations[i].splice(j, 0, N-1);
// index j is N-1, N-2, N-3, ... , 1, 0; then 0, 1, 2, ... N-1; then N-1, N-2, etc.
j += d;
// at beginning or end of the row, switch weave direction
if (j < 0 || j >= N) {
d *= -1;
j += d;
}
}
return permutations;
}
}
So what's the best strategy to port that to Java? Can I do it with just primitive arrays? Do I need an array of ArrayLists? Or an ArrayList of ArrayLists? Or is there some other data type that's better? Whatever I use, I need to be able to convert it back into a an array of primitive arrays.
Maybe's there a better algorithm that would simplify this for me...
Thank you in advance for your advice!
As you know the number of permutations beforehand (it's N!) and also you want/have to return an int[][] I would go for an array directly. You can declare it right at the beginning with correct dimensions and return it at the end. Thus you don't have to worry about converting it afterwards at all.
Since you pretty much had it completed on your own in javascript, I'll go ahead and give you the Java code for implementing Steinhaus' permutation algorithm. I basically just ported your code to Java, leaving as much of it the same as I could, including comments.
I tested it up to N = 7. I tried to have it calculate N = 8, but it's been running for almost 10 minutes already on a 2 GHz Intel Core 2 Duo processor, and still going, lol.
I'm sure if you really worked at it you could speed this up significantly, but even then you're probably only going to be able to squeeze maybe a couple more N-values out of it, unless of course you have access to a supercomputer ;-).
Warning - this code is correct, NOT robust. If you need it robust, which you usually don't for homework assignments, then that would be an exercise left to you. I would also recommend implementing it using Java Collections, simply because it would be a great way to learn the in's and out's of the Collections API.
There's several "helper" methods included, including one to print a 2d array. Enjoy!
Update: N = 8 took 25 minutes, 38 seconds.
Edit: Fixed N == 1 and N == 2.
public class Test
{
public static void main (String[] args)
{
printArray (allPermutations (8));
}
public static int[][] allPermutations (int N)
{
// base case
if (N == 2)
{
return new int[][] {{1, 2}, {2, 1}};
}
else if (N > 2)
{
// start with all permutations of previous degree
int[][] permutations = allPermutations (N - 1);
for (int i = 0; i < factorial (N); i += N)
{
// copy each permutation N - 1 times
for (int j = 0; j < N - 1; ++j)
{
// similar to javascript's array.splice
permutations = insertRow (permutations, i, permutations [i]);
}
}
// "weave" next number in
for (int i = 0, j = N - 1, d = -1; i < permutations.length; ++i)
{
// insert number N at index j
// similar to javascript's array.splice
permutations = insertColumn (permutations, i, j, N);
// index j is N-1, N-2, N-3, ... , 1, 0; then 0, 1, 2, ... N-1; then N-1, N-2, etc.
j += d;
// at beginning or end of the row, switch weave direction
if (j < 0 || j > N - 1)
{
d *= -1;
j += d;
}
}
return permutations;
}
else
{
throw new IllegalArgumentException ("N must be >= 2");
}
}
private static void arrayDeepCopy (int[][] src, int srcRow, int[][] dest,
int destRow, int numOfRows)
{
for (int row = 0; row < numOfRows; ++row)
{
System.arraycopy (src [srcRow + row], 0, dest [destRow + row], 0,
src[row].length);
}
}
public static int factorial (int n)
{
return n == 1 ? 1 : n * factorial (n - 1);
}
private static int[][] insertColumn (int[][] src, int rowIndex,
int columnIndex, int columnValue)
{
int[][] dest = new int[src.length][0];
for (int i = 0; i < dest.length; ++i)
{
dest [i] = new int [src[i].length];
}
arrayDeepCopy (src, 0, dest, 0, src.length);
int numOfColumns = src[rowIndex].length;
int[] rowWithExtraColumn = new int [numOfColumns + 1];
System.arraycopy (src [rowIndex], 0, rowWithExtraColumn, 0, columnIndex);
System.arraycopy (src [rowIndex], columnIndex, rowWithExtraColumn,
columnIndex + 1, numOfColumns - columnIndex);
rowWithExtraColumn [columnIndex] = columnValue;
dest [rowIndex] = rowWithExtraColumn;
return dest;
}
private static int[][] insertRow (int[][] src, int rowIndex,
int[] rowElements)
{
int srcRows = src.length;
int srcCols = rowElements.length;
int[][] dest = new int [srcRows + 1][srcCols];
arrayDeepCopy (src, 0, dest, 0, rowIndex);
arrayDeepCopy (src, rowIndex, dest, rowIndex + 1, src.length - rowIndex);
System.arraycopy (rowElements, 0, dest [rowIndex], 0, rowElements.length);
return dest;
}
public static void printArray (int[][] array)
{
for (int row = 0; row < array.length; ++row)
{
for (int col = 0; col < array[row].length; ++col)
{
System.out.print (array [row][col] + " ");
}
System.out.print ("\n");
}
System.out.print ("\n");
}
}
The java arrays are not mutable (in the sense, you cannot change their length). For direct translation of this recursive algorithm you probably want to use List interface (and probably LinkedList implementation as you want put numbers in the middle). That is List<List<Integer>>.
Beware the factorial grows rapidly: for N = 13, there is 13! permutations that is 6 227 020 800. But I guess you need to run it for only small values.
The algorithm above is quite complex, my solution would be:
create List<int[]> to hold all permutations
create one array of size N and fill it with identity ({1,2,3,...,N})
program function that in place creates next permutation in lexicographical ordering
repeat this until you get the identity again:
put a copy of the array at the end of the list
call the method to get next permutation.
If your program just needs to output all permutations, I would avoid to store them and just print them right away.
The algorithm to compute next permutation can be found on internet. Here for example
Use whatever you want, arrays or lists, but don't convert them - it just makes it harder. I can't tell what's better, probably I'd go for ArrayList<int[]>, since the outer List allows me to add the permutation easily and the inner array is good enough. That's just a matter of taste (but normally prefer lists, since they're much more flexible).
As per Howard's advice, I decided I didn't want to use anything but the primitive array type. The algorithm I initially picked was a pain to implement in Java, so thanks to stalker's advice, I went with the lexicographic-ordered algorithm described at Wikipedia. Here's what I ended up with:
public static int[][] generatePermutations(int N) {
int[][] a = new int[factorial(N)][N];
for (int i = 0; i < N; i++) a[0][i] = i;
for (int i = 1; i < a.length; i++) {
a[i] = Arrays.copyOf(a[i-1], N);
int k, l;
for (k = N - 2; a[i][k] >= a[i][k+1]; k--);
for (l = N - 1; a[i][k] >= a[i][l]; l--);
swap(a[i], k, l);
for (int j = 1; k+j < N-j; j++) swap(a[i], k+j, N-j);
}
return a;
}
private static void swap(int[] is, int k, int l) {
int tmp_k = is[k];
int tmp_l = is[l];
is[k] = tmp_l;
is[l] = tmp_k;
}
Related
I have a function public static int countBaad(int[] hs) that takes in an input array and I'm supposed to find how many numbers are smaller than the ones ahead of it.
For instance,
if hs = [7,3,5,4,1] the answer would be 2 because the pairs that violate the order are 3 and 5 and 3 and 4, since 3 is smaller than them and should've been ahead of them.
if hs = [8,5,6,7,2,1] the answer would be 3 because 5 is smaller than 6 and 7, giving us 2, and since 6 is also smaller than 7, we would get a total of 3 wrong pairs
Here is my current code using the merge sort approach:
public static int countBaad(int[] hs){
return mergeSort(hs, hs.length);
}
public static int mergeSort(int[] a, int n) {
if (n < 2) {
return n;
}
int mid = n / 2;
int[] l = new int[mid];
int[] r = new int[n - mid];
for (int i = 0; i < mid; i++) {
l[i] = a[i];
}
for (int i = mid; i < n; i++) {
r[i - mid] = a[i];
}
mergeSort(l, mid);
mergeSort(r, n - mid);
return merge(a, l, r, mid, n - mid);
}
public static int merge(int[] a, int[] l, int[] r, int left, int right) {
int size = 0;
int i = 0, j = 0, k = 0;
while (i < left && j < right) {
if (l[i] <= r[j]) {
a[k++] = l[i++];
size++;
}
else {
a[k++] = r[j++];
size++;
}
}
while (i < left) {
a[k++] = l[i++];
size++;
}
while (j < right) {
a[k++] = r[j++];
size++;
}
return size;
}
This code gives me the incorrect output after I put in arrays
hs = [7,3,5,4,1] returns 5
hs = [8,5,6,7,2,1] returns 6
What am I doing wrong here, can anyone please correct me?
What your code is currently doing is attempting a sort and then simply returning the size of the sorted array (big surprise, given the aptly named size variable).
Basically you are sorting in descending order and your specification calls for the result to be how many numbers were smaller than those appearing later in the array.
However, in merge you are actually adding to size regardless of their values.
Then, you're only returning the 'size' result of the final merge, not that of the sorting steps required inbetween.
And finally, perhaps the elephant in the room, is that you're performing a (unnecessary) sort as a side effect, but ignoring it completely.
Long story short, the code is too complicated and error prone for what it is supposed to do.
Here's a simple double for loop that achieves the desired outcome:
public static int countBaad(int[] hs){
int count = 0;
for(int i = 0; i < hs.length; i++) {
for(int j = i+1; j < hs.length; j++) {
//compare the i'th position with all subsequent positions
int current = hs[i];
int other = hs[j];
if(current < other) {
System.out.println("Found bad number pair: ("+current+","+other+")");
count++;
}
}
}
return count;
}
System.out.println(countBaad(new int[]{7,3,5,4,1}));
//prints:
//Found bad number pair: (3,5)
//Found bad number pair: (3,4)
//2
System.out.println(countBaad(new int[]{8,5,6,7,2,1}));
//prints:
//Found bad number pair: (5,6)
//Found bad number pair: (5,7)
//Found bad number pair: (6,7)
//3
This is much more succinct and free from side effects.
Edit:
Fixing the mergeSort code, with extra sysout logging to illustrate the algorithm:
public static int mergeSort(int[] a, int n) {
if(n==1) {
//No sorting required, so the result should be 0.
return 0;
}
int mid = n / 2;
int[] l = new int[mid];
int[] r = new int[n - mid];
//'splitting the array' loops are just arraycopy, so
// should use the native implementation:
System.arraycopy(a, 0, l, 0, mid);
if(n - mid >= 0) System.arraycopy(a, mid, r, 0, n - mid);
//add the results from all merges, not just the last one
int result = 0;
result += mergeSort(l, mid);
result += mergeSort(r, n - mid);
result += merge(a, l, r); //there is no need to pass in the array lengths
return result;
}
public static int merge(int[] a, int[] l, int[] r) {
System.out.println("Merging "+Arrays.toString(l)+" and "+Arrays.toString(r));
int size = 0;
int lIdx = 0, rIdx = 0, aIdx = 0;
while (lIdx < l.length && rIdx < r.length) {
if (l[lIdx] >= r[rIdx]) {
a[aIdx++] = l[lIdx++];
//size++; //no: left was already bigger than right
}
else {
//take from the right.
//This number is bigger than all the numbers remaining on the left.
for(int tempIdx = lIdx;tempIdx<l.length;tempIdx++) {
//this loop is for illustration only
System.out.println(" Found bad pair: (" + l[tempIdx] + "," + r[rIdx] + ")");
}
size+=l.length-lIdx;
a[aIdx++] = r[rIdx++];
}
}
//while (lIdx < left) { //NOTE that you had this condition incorrectly reversed resulting in bad merge
// a[aIdx++] = l[lIdx++];
// size++; //no, no comparisons are taking place here
//}
//while (rIdx < right) { //NOTE that you had this condition incorrectly reversed, resulting in bad merge
// a[aIdx++] = r[rIdx++];
// size++; //no, no comparisons are taking place here
//}
//we can also replace the above two loops with arraycopy
// which will perform better on large arrays
if(lIdx < left) {
System.arraycopy(l, lIdx, a, aIdx, l.length-lIdx);
}
if(rIdx < right) {
System.arraycopy(r, rIdx, a, aIdx, r.length-rIdx);
}
return size;
}
Since you value performance, you should use System.arraycopy where possible. I have also renamed the loop variables to make the code easier to understand.
System.out.println(countBaad(new int[]{7,3,5,4,1}));
//prints:
//Merging [7] and [3]
//Merging [4] and [1]
//Merging [5] and [4, 1]
//Merging [7, 3] and [5, 4, 1]
// Found bad pair: (3,5)
// Found bad pair: (3,4)
//2
System.out.println(countBaad(new int[]{8,5,6,7,2,1}));
//prints:
//Merging [5] and [6]
// Found bad pair: (5,6)
//Merging [8] and [6, 5]
//Merging [2] and [1]
//Merging [7] and [2, 1]
//Merging [8, 6, 5] and [7, 2, 1]
// Found bad pair: (6,7)
// Found bad pair: (5,7)
//3
Edit #2
To remove the side effects (sort) from this method, the input array can be copied, for example with a simple call to Arrays.copyOf(hs, hs.length); and passing in the result instead of the original.
Working on an addBefore() method that adds a new element to the beginning of an array of ints and then causes the existing elements to increase their index by one.
This is what is showing in the console when trying to run --
java.lang.RuntimeException: Index 1 should have value 11 but instead has 0
at IntArrayListTest.main(IntArrayListTest.java:67)
Below is the code I have so far.
public class IntArrayList {
private int[] a;
private int length;
private int index;
private int count;
public IntArrayList() {
length = 0;
a = new int[4];
}
public int get(int i) {
if (i < 0 || i >= length) {
throw new ArrayIndexOutOfBoundsException(i);
}
return a[i];
}
public int size() {
return length;
}
public void set(int i, int x) {
if (i < 0 || i >= a.length) {
throw new ArrayIndexOutOfBoundsException(i);
}
a[i] = x;
}
public void add(int x) {
if (length >= a.length) {
int[] b = new int[a.length * 2];
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}
a = b;
//count += 1;
}
a[length] = x;
count++;
length = length + 1;
}
public void addBefore(int x) {
int[] b = new int[a.length*2];
for (int i = 0; i < a.length; i++) {
b[i+a.length] = a[i];
}
a = b;
a[index] = x;
length ++;
}
}
Whether you add first or last, you need to only grow the array size if it is already full.
The count field seems to be exactly the same as length, and index seems unused and meaningless as a field, so remove them both.
To rearrange values in an array, use this method:
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
You two "add" methods should then be:
public class IntArrayList {
private int[] a; // Underlying array
private int length; // Number of added elements in a
// other code
public void add(int x) {
if (length == a.length) {
int[] b = new int[a.length * 2];
System.arraycopy(a, 0, b, 0, length);
a = b;
}
a[length++] = x;
}
public void addBefore(int x) {
if (length < a.length) {
System.arraycopy(a, 0, a, 1, length);
} else {
int[] b = new int[a.length * 2];
System.arraycopy(a, 0, b, 1, length);
a = b;
}
a[0] = x;
length++;
}
}
If the answer requires you to do the looping yourself then something like this should work fine (one of a few ways to do this, but is O(n)) :
public void addBefore(int x) {
if(length + 1 >= a.length){
int[] b = new int[a.length*2];
b[0] = x;
for (int i = 0; i < length; i++) {
b[i + 1] = a[i];
}
a = b;
} else {
for (int i = length; i >= 0 ; i--) {
a[i + 1] = a[i];
}
a[0] = x;
}
length++;
}
I noticed this started running a "speed test" - not sure how useful a test like that is, as it would be based on cpu performance, rather than testing complexity of the algorithm ..
you had three problems with your solution:
you increased the length of a every time the method was called. this would quickly create an OutOfMemoryException
when you copied values from a to b, you did b[i+a.length] = a[i]; which means the values would be copied to the middle of b instead of shift just one place
at the end, you put the new value in the end of the array instead of at the beginning.
all this I was able to see because I used a debugger on your code. You need to start using this tool if you want to be able to detect and fix problems in your code.
so fixed solution would do this:
check if a is full (just like it is done with add() method) and if so, create b, and copy everything to it and so on)
move all values one place ahead. the easiest way to do it is to loop backwards from length to 0
assign new value at the beginning of the array
here is a working solution:
public void addBefore(int x) {
// increase length if a is full
if (length >= a.length) {
int[] b = new int[a.length * 2];
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}
a = b;
}
// shift all values one cell ahead
for (int i = length; i > 0; i--) {
a[i] = a[i-1];
}
// add new value as first cell
a[0] = x;
length ++;
}
}
You can use the existing Java methods from the Colt library. Here is a small example that uses a Python syntax (to make the example code small I use Jython):
from cern.colt.list import IntArrayList
a=IntArrayList()
a.add(1); a.add(2) # add two integer numbers
print "size=",a.size(),a
a.beforeInsert(0, 10) # add 10 before index 0
print "size=",a.size(),a
You can use DataMelt program to run this code. The output of the above code is:
size= 2 [1, 2]
size= 3 [10, 1, 2]
As you can see, 10 is inserted before 1 (and the size is increased)
Feel free to change the codding to Java, i.e. importing this class as
import cern.colt.list.IntArrayList
IntArrayList a= new IntArrayList()
You could use an ArrayList instead and then covert it to an Integer[] Array which could simplify your code. Here is an example below:
First create the ArrayList:
ArrayList<Integer> myNums = new ArrayList<Integer>();
Next you can add the values that you want to it, but I chose to just add the numbers 2-5, to illustrate that we can make the number 1 the first index and automatically increment each value by one index. That can simplify your addBefore() method to something such as this:
public static void addBefore(ArrayList<Integer> aList) {
int myInt = 1;
aList.add(0, myInt);
}
Since your ArrayList has ONE memory location in Java, altering the Array within a method will work (this would also work for a regular Array). We can then add any value to the beginning of the ArrayList. You can pass an Integer to this method as the second argument (int x), if you want, but I simply created the myInt primitive to simplify the code. I know that in your code you had the (int x) parameter, and you can add that to this method. You can use the ArrayList.add() method to add the int to index 0 of the Array which will increment each Array element by 1 position. Next you will need to call the method:
addBefore(myNums);//You can add the int x parameter and pass that as an arg if you want here
Next we can use the ArrayList.toArray() method in order to covert the ArrayList to an Integer Array. Here is an example below:
Integer[] integerHolder = new Integer[myNums.size()];
Integer[] numsArray = (Integer[])myNums.toArray(integerHolder);
System.out.println(Arrays.toString(numsArray));
First we create an ArrayHolder that will be the same size as your ArrayList, and then we create the Array that will store the elements of the ArrayList. We cast the myNums.toArray() to an Integer Array. The results will be as follows. The number 1 will be at index 0 and the rest of your elements will have incremented by 1 index:
[1, 2, 3, 4, 5]
You could do the entire process within the addBefore() method by converting the Array to an ArrayList within the method and adding (int x) to the 0 index of the ArrayList before converting it back into an Array. Since an ArrayList can only take a wrapper class object you'll simply need to convert the int primitive Array into the type Integer for this to work, but it simplifies your addBefore() method.
I have an array of integers representing a deck of cards with 52 cards in it, with numbers ranging from 1-52 to represent the cards. I am trying to write a method which will take two positions within the array as parameters to divide the array into three blocks. Block 1 is all the values below the first position, block 2 is all the values lying between the two positions(inclusive of the values at position 1 and 2) and block 3 is all the values lying above the second position. I then want to switch the positions of blocks 1 and 3 within the array.
So for an array with the values of:
1,2,3,4,5,6,7,8,9,10,11,12
Setting positionOne(1), positionTwo(9) would give the array:
11,12,2,3,4,5,6,7,8,9,10,1
Here's what I have so far, which almost works but I think has bugs in it:
public void switchPositions(int pos1, int pos2) {
int[] newCards = new int[cards.length];
int sizeChunkA = 0;
int sizeChunkC = 0;
int sizeChunkB = 0;
int counter = 0;
for(int i = pos2+1; i<cards.length; i++) {
sizeChunkC++;
}
for(int i = 0; i<pos1; i++) {
sizeChunkA++;
}
for(int i = pos1; i<=pos2; i++) {
sizeChunkB++;
}
for(int i = 1; i<=sizeChunkC; i++) {
newCards[counter] = cards[pos2+i];
counter++;
}
for(int i=pos1; i<=pos2; i++) {
newCards[counter] = cards[i];
counter++;
}
for(int i=0; i<sizeChunkA; i++) {
newCards[counter] = cards[i];
counter++;
}
cards = newCards;
}
Is there a better way to do this?
Instead of loops use Arrays.copyOfRange and System.arraycopy:
// make a copy of the first section before overwriting it
int[] copy = Arrays.copyOfRange (inputArray, from, to);
// overwrite the first section with the second section
System.arraysCopy(inputArray, sourcePosition, inputArray, from, copy.length);
// copy the original content of the first section to the second section
System.arraysCopy(copy, 0, inputArray, sourcePosition, copy.length);
You have to change the indices according to your requirements.
I would approach it this way.
Create three arraylists for your three temp blocks.
Iterate through your array starting at 0-> position 1. Moving the values into your first array list.
Iterate through your starting at position 1 and ending at position 2. Moving all values into the sec0nd array list.
Then iterate through your array starting at position 2+1 through to the end.
Combine the three array lists.
There is a bit clearer of a way to do this. If you want to code it yourself rather than just use the existing Arrays.copyOfRange (perhaps if you would like to do it in place rather than generating a new array).
int lengthOfDeck = cards.length;
int[] newCards = new int[lengthOfDeck];
for (int i = 1; i <= lengthOfDeck; i++) {
if (i >=pos1 && i <=pos2) {
newCards[i-1]=i;
}
else if (i < pos1) {
newCards[i-1]= i + pos2;
}
else {
newCards[i-1] = i - pos2;
}
}
What about approach without using additional memory?!
Main idea, is that if you want to shift array to the offs positions right or left you can do it in place with 2 full for loops. I give you example:
Initial array {1,2,3,4,5}; we want to move it 2 positions right (i.e. offs=2), and have result {4,5,1,2,3}
1st loop for reverse all elements: {5,4,3,2,1}
2nd loop for reverse first offs elements: {4,5,3,2,1}
3rd loop for reverse other elements: {4,5,1,2,3}.
That's all! To solve your task, you have to do it twice, but second time you have to reduce array's length to not move last elements.
This is example:
public static void main(String... args) throws IOException {
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
replace(arr, 1, 9);
// arr = [11, 12, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
}
public static void replace(int[] arr, int pos1, int pos2) {
rotate(arr, arr.length, -pos1);
rotate(arr, arr.length - pos1, -pos2);
}
// offs > 0 - to the right; offs < 0 - to the left
private static void rotate(int[] arr, int length, int offs) {
offs = offs >= length ? length % offs : offs;
length = Math.min(arr.length, length);
for (int i = 0, j = length - 1; i < j; i++, j--)
swap(arr, i, j);
for (int i = 0, j = offs > 0 ? offs - 1 : length + offs - 1; i < j; i++, j--)
swap(arr, i, j);
for (int i = offs > 0 ? offs : length + offs, j = length - 1; i < j; i++, j--)
swap(arr, i, j);
}
private static void swap(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
According to the performance. You have 4 full for loops with one swap in each of them. So performance is O(n), without using additional memory.
Thanks to Eran for his tip, here is a more adapted method for your needs:
public int[] splitArray(int[] i, int position1, int position2) {
position2++;
int[] piece1 = Arrays.copyOfRange(i, 0, position1);
int[] piece2 = Arrays.copyOfRange(i, position1, position2);
int[] piece3 = Arrays.copyOfRange(i, position2, i.length);
System.arraycopy(piece3, 0, i, 0, piece3.length);
System.arraycopy(piece2, 0, i, piece3.length, piece2.length);
System.arraycopy(piece1, 0, i, piece3.length+piece2.length, piece1.length);
return i;
}
Im solving the QuickSort assignment at Algorithms class by Stanford and using the median rule to select the pivot element. The input is numbers from 1-10000 and output is the number of comparisons
My function are as follows :
public static int noOfComp = 0;
public static void quick_sort(int[] a, int p, int r){
if(p<r) {
noOfComp+= r-p;
int mid = partition(a, p, r);
quick_sort(a, p, mid-1);
quick_sort(a, mid+1, r);
}
}
public static int median(int a[],int p, int r){
int firstPos = p;
int len = r-p+1;
int lastPos = r;
int midPos = len%2==0 ? p + (len)/2-1: p + (len)/2 ;
int first = a[firstPos];
int middle = a[midPos];
int last = a[lastPos];
if (first <= middle) {
if (middle <= last) {
// first - middle - last
return midPos;
} else if (first <= last) {
// first - last - middle
return lastPos;
}
// last - first - middle
return firstPos;
}
if (first <= last) {
// middle - first - last
return firstPos;
} else if (middle <= last) {
// middle - last - first
return lastPos;
}
// last - middle - first
return midPos;
}
public static int partition(int[] a, int p, int r){
int chosen = median(a,p,r);
swap(a, p, chosen);
int pivot = a[p];
int i = p;
for (int j = p+1; j < a.length; j++) {
if (a[j] < pivot) {
i++;
swap(a, i, j);
}
}
swap(a, i,p);
return i;
}
//main
public static void main(String[] args) throws Throwable{
int i=0;
Scanner in = new Scanner(new File("C:\\Users\\Uzumaki Naruto\\Documents\\QuickSort.txt"));
while(in.hasNext()){
i++;
in.next();
}
int[] a = new int[i];
i=0;
Scanner in2 = new Scanner(new File("C:\\Users\\Uzumaki Naruto\\Documents\\QuickSort.txt"));
while(in2.hasNext()){
a[i++] = in2.nextInt();
}
quick_sort(a, 0, a.length-1);
System.out.println("Number of comparisons : " + noOfComp);
}
The answer to question seems to be around 128k , but my algorithm output it 132k. I've read the code number of times but unable to ascertain the error.
Indeed, I also get an average count of around 132k with your code, executed on randomly shuffled arrays of unique numbers. I did not find any mistake in the algorithm, except for the following one, but it's not influencing your count result, which assumed correct code:
The loop in partition has a bad exit condition:
for (int j = p+1; j < a.length; j++) {
It should be:
for (int j = p+1; j <= r; j++) {
The following is not an error, but you can rewrite
int len = r-p+1;
int midPos = len%2==0 ? p + (len)/2-1: p + (len)/2 ;
as:
int midPos = p + (r-p)/2;
But: You did not count the comparisons made in the function median, and this should normally be done, otherwise an algorithm cannot be fairly compared with another (variant). So that results in 2 or 3 more comparisons per call of partition. This increases the average count to around 148k!
Here it says that:
the expected number of comparisons needed to sort n elements with random pivot selection is 1.386 n.log(n). Median-of-three pivoting brings this down to ≈ 1.188 n.log(n).
The thing is that for n = 10 000, 1.188 n.log(n) ≈ 158k so your algorithm seems to do fewer comparisons than this estimate, at least for this particular case of n.
I do see a way to reduce that number again.
Reducing the number of comparisons
The main idea is to profit from the comparisons you make in the function median by already putting the lowest and highest of the three inspected values in the right partition, so they do not need to be treated further by the loop in the function partition.
To give an example, if you have an array like this:
5, 1, 2, 9, 3
Then median will compare 5, 2 and 3 and choose 3 as pivot value. The function could now be extended to also put the three investigated elements in the right order, without extra comparisons, to get this:
2, 1, 3*, 9, 5
And then the pivot element would not have to be swapped to the start of the array, but to the second slot, because we already have decided that the left most element belongs to the lower partition:
2, 3*, 1, 0, 5
And now the main partition loop can concentrate on this sub-array, because also the last element is known the belong to the upper partition:
2, 3*, [1, 0], 5
At the end of the loop the final swap will be with the second element instead of the first:
2, 0, 1, 3*, 5
This will reduce the number of comparisons in the main loop with 2.
In this variant, the median function will always return the index of the second slot, after making a few swaps in the array:
public static int median(int a[],int p, int r){
int m = p + (r-p)/2;
// actually sort the three elements:
noOfComp++;
if (a[r] < a[m]) {
swap(a, r, m);
}
if (p < m) { // more than 2 elements
noOfComp++;
if (a[m] < a[p]) {
swap(a, m, p);
noOfComp++;
if (a[r] < a[m]) {
swap(a, r, m);
}
}
// put the middle element (pivot) in second slot
swap(a, m, p+1);
}
return p+1;
}
And partition will look like this:
public static int partition(int[] a, int p, int r){
int k = median(a, p, r); // always returns p+1 as pivot's index
int i = k; // (k..i] is lower partition
for (int j = p+2; j < r; j++) { // positions p and r can be excluded
if (a[j] < a[k]) {
i++;
swap(a, i, j);
}
}
swap(a, i, k); // place pivot between partitions
return i;
}
In quick_sort the count of comparisons will be two less:
noOfComp += r-p-2;
With the above adjustments the number of comparisons goes down from 148k to 135k on average.
So I am afraid that although the actual number of comparisons has been reduced this way, it still does not match the 128k.
Other ideas
I tried using insertion sort when the array became small, but it did not yield much of an improvement. Another idea is to improve the search for the median by looking at more elements, but only if the array is not too small, as the cost of looking for one must be small compared to the partitioning effort.
But the assignment may not allow for all this tweaking.
Below, I have designed a function tournamentTreeKSelection which simulates a tree like structure using arrays and returns the largest element in the array. For example, given an input array [10,9,8,7,6,5,4,3,2,1] the following steps are performed to return 10.
[10, 8, 6, 4, 2, -1]
[10, 6, 2, -1]
[10, 2]
[10] //Max element of array found
My goal is to now add a second parameter int k requesting that the function return the k-th largest element such that tournamentTreeKSelection(data, 2) returns 9.
I'm having a lot of difficulty in modifying my algorithm to perform this task because my assumption is that i'm going to have to keep track of all elements that the max element beats ? Any help is appreciated.
import java.util.ArrayList;
import java.util.Arrays;
public class TournamentTree {
public static int tournamentTreeKSelection(int[] data, int k) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
for(int i = 0; i < data.length - 1; i += 2) {
list.add(max(data[i] , data[i + 1]));
}
for(int i = 0; i < data.length - 1; i++) {
list2.add(min(data[i], data[i + 1]));
}
if(list.size() == 1) return list.get(0);
if(list.size() % 2 != 0) list.add(-1);
if(k == 1) return tournamentTreeKSelection(listToArray(list),k);
else return tournamentTreeKSelection(listToArray(list2), --k);
}
public static int max(int a, int b) {
return a > b ? a : b;
}
public static int min(int a, int b) {
return a > b ? b : a;
}
public static int[] listToArray(ArrayList<Integer> arr) {
int[] arr2 = new int[arr.size()];
for(int i = 0; i < arr.size(); i++)
arr2[i] = arr.get(i);
return arr2;
}
}
I have now modified the code but it only works for k = 1 - 8, why does it break down ? tournamentTreeKSelection(data, 9) and tournamentTreeKSelection(data, 10) return 3 when they should be returning 2 and 1 respectively.
First of all, why your code is wrong:
When the size of the list is 2 or 3, your statement list.size() == 1 will be true even if K > 1.
Why do you do min(data[i], data[i + 1]), I have a feeling you just want to remove the maximum element but what with the case
[10,1,9,2,8,3,7,4,6,5], gives after 1 iteration [1,1,2,2,3,3,4,4,5] removing possible outcomes 9, 8, 7 and 6.
Some tips
Don't do useless computations. You are calculating the two lists, while you know in front you are only going to use one of them.
Use builtin methods whenever possible, see Math.max, Math.min
Note that you know the size of the resulting array in front. There is no need to create an ArrayList which causes a lot of overhead for you. Just create an array of the resulting size. For k==1, ((data.length+1)/2) else data.length-1
Still wondering
You say your tournament tree structure is a requirement, but you are looping over it in your code as it is an array. Why? You could determine the max value from the moment K==1 in 1 loop, instead of taking half of the maxes and doing it over and over again.
Alternative approach
As already suggested the sorting approach, or the quick find methods can be used. I was thinking how you could still use your tournament tree approach. And the best I came up with is how merge sort works. I slightly edited because you only need max K elements to return.
public static int find(int[] a, int k) {
int[] max = find(a, 0, a.length - 1, k);
return max[k-1];
}
private static int[] find(int[] a, int lo, int hi, int k) {
if (hi < lo){
return new int[]{};
}
if(lo == hi){
return new int[]{a[lo]};
}
int mid = lo + (hi - lo) / 2;
int[] left = find(a, lo, mid, k);
int[] right = find(a, mid + 1, hi, k);
return merge(left, right, k);
}
private static int[] merge(int[] left, int[] right, int k) {
int[] res = new int[Math.min(k, left.length+right.length)];
int l = 0, r = 0;
for (int i = 0; i<res.length;i++) {
if (l == left.length)
res[i] = right[r++];
else if (r == right.length)
res[i] = left[l++];
else if (left[l] > right[r])
res[i] = left[l++];
else
res[i] = right[r++];
}
return res;
}