1

Consider a situation like the following:

The array amounts keeps track of some statistical amounts:

int[] amounts = { 1, 2, 5, 7, 2, 4, 8, 6 };

It is known that the array will always have a fixed size (in this case 8). What I need to find are the indexes of a given number k of smallest elements:

int   k = 5;
int[] smallest = { 1, 2, 2, 4, 5 };
int[] smallestIndices = { 0, 1, 4, 5, 2 };

I figured I could make use of the Selection Algorithm, but I realized that this re-orders the array, thus returning an incorrect index.

Another idea I came up with was to create an array of tuples of index and value, and sort that array by the tuple value. Then I could extract the indexes of the first k tuples in the sorted array. However, this seems like a wasteful solution that can probably done more easily.

Is there an algorithm that allows me to find the indexes of the k smallest numbers in an array of size n?

  • Note that I am not looking for the smallest k numbers, which is already answered by this question. I am looking for their indexes. Thus, this question should not flagged as a duplicate.
Community
  • 1
  • 1
Clashsoft
  • 11,553
  • 5
  • 40
  • 79
  • 1
    "Is there an algorithm..." Sure. Look for the smallest, look for the next smallest, look for the next smallest... k times. If you are only working on small inputs, quadratic algorithms can be the best choice. – Andy Turner Nov 19 '15 at 22:43
  • 1
    http://stackoverflow.com/questions/5380568/algorithm-to-find-k-smallest-numbers-in-array-of-n-items – Ashraful Islam Nov 19 '15 at 22:46
  • http://www.geeksforgeeks.org/k-largestor-smallest-elements-in-an-array/ – UglyCode Nov 19 '15 at 23:32
  • This is not a duplicate because I'm looking for the indexes, not the `k` smallest numbers. I found that question before posting this one, and it failed to help me because the Selection Algorithm doesn't work for me. – Clashsoft Nov 20 '15 at 14:34
  • This question is a demonstration of why Java is really bad as a teaching language. To fully explain why the answer in the other question is appropriate requires concepts not available in the language. I'm voting to re-open because it will be necessary to post something too big for a comment for this guy to get it. – Joshua Dec 15 '15 at 17:16
  • The correct answer was already given (and it actually helped), but it seemed unjustified to mark it as a duplicate while it was asking for something different. In fact, I now how sorting and the Selection Algorithm and the like work, but I was looking for a quick and concise chunk of code to do it (which has been provided by @BorisTheSpider in the accepted answer). – Clashsoft Dec 15 '15 at 18:59

6 Answers6

4

Create an array of indexes, i.e. an array [0,n), the sort that by the element located at that index in the primary array. Now all you need to do is take the top elements from the array of indexes.

There is no need use pairs.

Concrete example:

//imports
import static java.util.Comparator.comparing;

//method
public static int[] bottomN(final int[] input, final int n) {
    return IntStream.range(0, input.length)
            .boxed()
            .sorted(comparing(i -> input[i]))
            .mapToInt(i -> i)
            .limit(n)
            .toArray();
}

Usage:

public static void main(String[] args) throws Exception {
    int[] amounts = {1, 2, 5, 7, 2, 4, 8, 6};
    System.out.println(Arrays.toString(bottomN(amounts, 5)));
}

Output:

 [0, 1, 4, 5, 2]

So all we do is take the array [0,1,2,3,4,5,6,8] and then sort it by the value of the element in the input array at the index equal to the current array element. This gives use an array of indexes sorted by their value in the input.

Finally we trim the top n and return it.

Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
1

If the array size is always small, you can get away with a simpler algorithm, but this should work fast for larger arrays as well. The idea is to put index-value pairs into a heap, then take out k pairs with the smallest values.

public class SmallestIndicesTest {
    public static void main(String[] args) {
        // main method here as an example use case
        int[] amounts = { 1, 2, 5, 7, 2, 4, 8, 6 };
        int k = 5;
        ArrayList<Integer> smallestIndices = getNsmallestIndices(amounts, k);
        for (int i : smallestIndices) {
            System.out.println("Index i=" + i + " contains value " + amounts[i]);
        }
    }

    private static ArrayList<Integer> getNsmallestIndices(int[] t, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        PriorityQueue<Pair> queue = new PriorityQueue<>();
        for (int i=0; i<t.length; i++) {
            queue.add(new Pair(t[i], i));
        }
        for (int i=0; i<k; i++) {
            Pair nextSmallest = queue.poll();
            list.add(nextSmallest.index);
        }
        return list;
    }
}


class Pair implements Comparable<Pair> {
    public int value;
    public int index;

    public Pair(int value, int index) {
        this.value = value;
        this.index = index;
    }

    @Override
    public int compareTo(Pair o) {
        return (this.value - o.value);
    }
}
Atte Juvonen
  • 4,922
  • 7
  • 46
  • 89
  • Rather than inserting all elements in the heap, which uses O(n log n) time, one could use the Heapify algorithm, which works in O(n). – anumi Nov 19 '15 at 23:01
  • Could you please present the O(n) solution? – Atte Juvonen Nov 19 '15 at 23:06
  • @anumi well, `O(n)` to heapify followed by `O(k log n)` (where `k` is the number of smallest elements required) to take the smallest `k`. I'm not sure naive complexity analysis tells us much in this situation as the constant factors are likely much bigger than the asymptotic ones. – Boris the Spider Nov 19 '15 at 23:08
1

It depends on the ratio of number of elements to the amount of elements you want to find. You will be able to sort n elements as tuples in in O(n*log(n)). However if the amount of elements (m) you want to find is < log(n), you might consider simply looping through the list and gathering the m smallest elements. Then you will be guaranteed a complexity of O(n*log(n)). If you have a constant amount of elements you want to find, your complexity with the latter method will be O(n).

Clashsoft
  • 11,553
  • 5
  • 40
  • 79
Neitsch
  • 81
  • 3
  • This is a perfect demonstration of why complexity analysis is fundamentally flawed in this case. – Boris the Spider Nov 19 '15 at 23:10
  • We would need more information to argue either way. However I see your point, that it does not make that much sense to say we are at O(n) just because k < log(n). Let's assume though, that we always want to pick the 5 lowest numbers with an unknown number of entries. Then we would always go for the O(n) algorithm. If on the other hand we want to find the lower half of the items this would definitely ask for Quicksort. Still, you are totally right, that there is a major flaw in reality associated with my answer. – Neitsch Nov 20 '15 at 04:50
0

What you need to do is to keep the index of the elements. For the example you gave,

int[] amounts = { 1, 2, 5, 7, 2, 4, 8, 6 }

Use an array of pair to re-store the array, then you get,

pair<int,int>[] pairs = {{0, 1},{1, 2}, {2, 5}, {3, 7}, {4, 2}, {5, 4}, {6, 8}, {7, 6}}

then apply the selection algorithm on the array of pairs. Note that you should use the second value of the pair as the primary key to compare a pair. You can return the k most smallest values as well as their indexes. The complexity is the same as the selection algorithm.

notbad
  • 2,797
  • 3
  • 23
  • 34
0

I am not sure I got your question. You want to get multiple indexes of elements from an array? If so you can try to create a new array and add indexes there, therefore the first element of smallest array is at the index of first element of indexes array. Something like

public static void main(String[] args) {
    int[] amounts = { 1, 2, 5, 7, 2, 4, 8, 6 };
    int   k = 5;
    int[] smallest = { 1, 2, 2, 4, 5 };
    int[] indexes = new int[k];
    for (int i = 0; i<k; i++) {
        int searchable = smallest[i];
        for (int j = 0; j<amounts.length; j++) {
            if (searchable == amounts[j]) {
                indexes[i] = j;
            }
        }
        }           
    for (int n = 0; n<indexes.length; n++) {
            System.out.println("Number " + smallest[n] + " index is " + indexes[n]);
    }
}
Taavi Kivimaa
  • 242
  • 1
  • 7
  • My problem is that a need to find the `smallest` first while preserving the order of the initial array. – Clashsoft Nov 19 '15 at 23:22
0

You can do this in pretty close to linear time (technically it's O(nk) but based on your description n is small) for any size of n and k, no need to be fancy.

If you are dealing with long lists and worried about cache coherency then you can create an object containing two integers for index and value (you could just use a tuple but an object is cleaner). Create an ArrayList of capacity n initially empty. If not worried about jumping around in memory then just an array of n integers each holding the index and initialized to -1 will suffice. Just look up the value from the index each time.

Loop through the elements of k. For each element

  • Check if the value is lower than the last value in the list, if not move to next element and no processing is needed at all on this one.
  • If the list length is n then delete the last item from the list to make room for the new one.
  • Create a new object with the index and value set. (Only needed if using an object)
  • Scan through the ArrayList until you find a smaller object or the start of the list
  • Insert the new object (or index) at the located point

At the end of a single scan through the main list the ArrayList will contain a sorted list of the smallest n values.

Tim B
  • 40,716
  • 16
  • 83
  • 128
  • Note that insert into a `List` at an arbitrary index is not `O(1)`. A ringbuffer would be more appropriate here. But I don't think the sledgehammer of complexity analysis can be applied meaningfully to this little problem. – Boris the Spider Nov 19 '15 at 23:13
  • @BoristheSpider Yep, lots of optimizations possible here but they are all almost certainly overkill since n is small. Not sure a ring buffer would help since insertion can happen at arbitrary points. – Tim B Nov 19 '15 at 23:15