I have a hard time translating QuickSort with Hoare partitioning into C code, and can't find out why. The code I'm using is shown below:
void QuickSort(int a[],int start,int end) {
int q=HoarePartition(a,start,end);
if (end<=start) return;
QuickSort(a,q+1,end);
QuickSort(a,start,q);
}
int HoarePartition (int a[],int p, int r) {
int x=a[p],i=p-1,j=r;
while (1) {
do j--; while (a[j] > x);
do i++; while (a[i] < x);
if (i < j)
swap(&a[i],&a[j]);
else
return j;
}
}
Also, I don't really get why HoarePartition works. Can someone explain why it works, or at least link me to an article that does?
I have seen a step-by-step work-through of the partitioning algorithm, but I don't have an intuitive feel for it. In my code, it doesn't even seem to work. For example, given the array
13 19 9 5 12 8 7 4 11 2 6 21
It will use pivot 13, but end up with the array
6 2 9 5 12 8 7 4 11 19 13 21
And will return j which is a[j] = 11. I thought it was supposed to be true that the array starting at that point and going forward should have values that are all larger than the pivot, but that isn't true here because 11 < 13.
Here's pseudocode for Hoare partitioning (from CLRS, second edition), in case this is useful:
Hoare-Partition (A, p, r)
x ← A[p]
i ← p − 1
j ← r + 1
while TRUE
repeat j ← j − 1
until A[j] ≤ x
repeat i ← i + 1
until A[i] ≥ x
if i < j
exchange A[i] ↔ A[j]
else return j
Thanks!
EDIT:
The right C code for this problem will end up being:
void QuickSort(int a[],int start,int end) {
int q;
if (end-start<2) return;
q=HoarePartition(a,start,end);
QuickSort(a,start,q);
QuickSort(a,q,end);
}
int HoarePartition (int a[],int p, int r) {
int x=a[p],i=p-1,j=r;
while (1) {
do j--; while (a[j] > x);
do i++; while (a[i] < x);
if (i < j)
swap(&a[i],&a[j]);
else
return j+1;
}
}
To answer the question of "Why does Hoare partitioning work?":
Let's simplify the values in the array to just three kinds: L values (those less than the pivot value), E values (those equal to the pivot value), and G value (those larger than the pivot value).
We'll also give a special name to one location in the array; we'll call this location s, and it's the location where the j pointer is when the procedure finishes. Do we know ahead of time which location s is? No, but we know that some location will meet that description.
With these terms, we can express the goal of the partitioning procedure in slightly different terms: it is to split a single array into two smaller sub-arrays which are not mis-sorted with respect to each other. That "not mis-sorted" requirement is satisfied if the following conditions are true:
The "low" sub-array, that goes from the left end of the array up to and includes s, contains no G values.
The "high" sub-array, that starts immediately after s and continues to the right end, contains no L values.
That's really all we need to do. We don't even need to worry where the E values wind up on any given pass. As long as each pass gets the sub-arrays right with respect to each other, later passes will take care of any disorder that exists inside any sub-array.
So now let's address the question from the other side: how does the partitioning procedure ensure that there are no G values in s or to the left of it, and no L values to the right of s?
Well, "the set of values to the right of s" is the same as "the set of cells the j pointer moves over before it reaches s". And "the set of values to the left of and including s" is the same as "the set of values that the i pointer moves over before j reaches s".
That means that any values which are misplaced will, on some iteration of the loop, be under one of our two pointers. (For convenience, let's say it's the j pointer pointing at a L value, though it works exactly the same for the i pointer pointing at a G value.) Where will the i pointer be, when the j pointer is on a misplaced value? We know it will be:
at a location in the "low" subarray, where the L value can go with no problems;
pointing at a value that's either an E or a G value, which can easily replace the L value under the j pointer. (If it wasn't on an E or a G value, it wouldn't have stopped there.)
Note that sometimes the i and j pointer will actually both stop on E values. When this happens, the values will be switched, even though there's no need for it. This doesn't do any harm, though; we said before that the placement of the E values can't cause mis-sorting between the sub-arrays.
So, to sum up, Hoare partitioning works because:
It separates an array into smaller sub-arrays which are not mis-sorted relative to each other;
If you keep doing that and recursively sorting the sub-arrays, eventually there will be nothing left of the array that's unsorted.
I believe that there are two problems with this code. For starters, in your Quicksort function, I think you want to reorder the lines
int q=HoarePartition(a,start,end);
if (end<=start) return;
so that you have them like this:
if (end<=start) return;
int q=HoarePartition(a,start,end);
However, you should do even more than this; in particular this should read
if (end - start < 2) return;
int q=HoarePartition(a,start,end);
The reason for this is that the Hoare partition fails to work correctly if the range you're trying to partition has size zero or one. In my edition of CLRS this isn't mentioned anywhere; I had to go to the book's errata page to find this. This is almost certainly the cause of the problem you encountered with the "access out of range" error, since with that invariant broken you might run right off the array!
As for an analysis of Hoare partitioning, I would suggest starting off by just tracing through it by hand. There's also a more detailed analysis here. Intuitively, it works by growing two ranges from the ends of the range toward one another - one on the left-hand side containing elements smaller than the pivot and one on the right-hand side containing elements larger than the pivot. This can be slightly modified to produce the Bentley-McIlroy partitioning algorithm (referenced in the link) that scales nicely to handle equal keys.
Hope this helps!
Your final code is wrong, since the initial value of j should be r + 1 instead of r. Otherwise your partition function always ignore the last value.
Actually, HoarePartition works because for any array A[p...r] which contains at least 2 elements(i.e. p < r), every element of A[p...j] is <= every element of A[j+1...r] when it terminates.
So the next two segments that the main algorithm recurs on are [start...q] and [q+1...end]
So the right C code is as follows:
void QuickSort(int a[],int start,int end) {
if (end <= start) return;
int q=HoarePartition(a,start,end);
QuickSort(a,start,q);
QuickSort(a,q + 1,end);
}
int HoarePartition (int a[],int p, int r) {
int x=a[p],i=p-1,j=r+1;
while (1) {
do j--; while (a[j] > x);
do i++; while (a[i] < x);
if (i < j)
swap(&a[i],&a[j]);
else
return j;
}
}
More clarifications:
partition part is just the translation of the pseudocode. (Note the return value is j)
for the recursive part, note that the base case checking (end <= start instead of end <= start + 1 otherwise you will skip the [2 1] subarray )
First of all u misunderstood the Hoare's partition algorithm,which can be see from the translated code in c,
Since u considered pivot as leftmost element of subarray.
Ill explain u considering the leftmost element as pivot.
int HoarePartition (int a[],int p, int r)
Here p and r represents the lower and upper bound of array which can be part of a larger array also(subarray) to be partitioned.
so we start with the pointers(marker) initially pointing to before and after end points of array(simply bcoz using do while loop).Therefore,
i=p-1,
j=r+1; //here u made mistake
Now as per partitioning we want every element to the left of pivot to be less than or equal to pivot and greater than on right side of pivot.
So we will move 'i' marker untill we get element which is greaterthan or equal to pivot. And similarly 'j' marker untill we find element less than or equal to pivot.
Now if i < j we swap the elements bcoz both the elements are in wrong part of array. So code will be
do j--; while (a[j] <= x); //look at inequality sign
do i++; while (a[i] >= x);
if (i < j)
swap(&a[i],&a[j]);
Now if 'i' is not less than 'j',that means now there is no element in between to swap so we return 'j' position.
So now the array after partitioned lower half is from 'start to j'
upper half is from 'j+1 to end'
so code will look like
void QuickSort(int a[],int start,int end) {
int q=HoarePartition(a,start,end);
if (end<=start) return;
QuickSort(a,start,q);
QuickSort(a,q+1,end);
}
Straightforward implementation in java.
public class QuickSortWithHoarePartition {
public static void sort(int[] array) {
sortHelper(array, 0, array.length - 1);
}
private static void sortHelper(int[] array, int p, int r) {
if (p < r) {
int q = doHoarePartitioning(array, p, r);
sortHelper(array, p, q);
sortHelper(array, q + 1, r);
}
}
private static int doHoarePartitioning(int[] array, int p, int r) {
int pivot = array[p];
int i = p - 1;
int j = r + 1;
while (true) {
do {
i++;
}
while (array[i] < pivot);
do {
j--;
}
while (array[j] > pivot);
if (i < j) {
swap(array, i, j);
} else {
return j;
}
}
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
You last C code works. But it's not intuitive.
And now I'm studying CLRS luckily.
In my opinion, The pseudocode of CLRS is wrong.(At 2e)
At last, I find that it would be right if changing a place.
Hoare-Partition (A, p, r)
x ← A[p]
i ← p − 1
j ← r + 1
while TRUE
repeat j ← j − 1
until A[j] ≤ x
repeat i ← i + 1
until A[i] ≥ x
if i < j
exchange A[i] ↔ A[j]
else
exchnage A[r] ↔ A[i]
return i
Yes, Add a exchange A[r] ↔ A[i] can make it works.
Why?
Because A[i] is now bigger than A[r] OR i == r.
So We must exchange to guarantee the feature of a partition.
move pivot to first. (eg, use median of three. switch to insertion sort for small input size.)
partition,
repetitively swap currently leftmost 1 with currently rightmost 0.
0 -- cmp(val, pivot) == true, 1 -- cmp(val, pivot) == false.
stop if not left < right.
after that, swap pivot with rightmost 0.
Related
I have been studying quick sort for a few hours and am confused about choosing a pivot value. Does the pivot value need to exist in the array?
For example if the array is 1,2,5,6 , can we use the value 3 or 4 as a pivot?
We use the position of pivot for dividing the array into sub-arrays but I am a little confused about what will be the pivot position after we move values < 5 to left of the array and values > 5 to right?
7,1,5,3,3,5,8,9,2,1
I dry runned the algo with the pivot 5 and came with the following result:
1,1,5,3,3,5,8,9,2,7
1,1,5,3,3,5,8,9,2,7
1,1,5,3,3,5,8,2,9,7
We can see that the value 2 is still not in the correct position. What am i doing wrong? Sorry if it's a silly question.
I came up with the following code but it's only working when pivot = left, I can't use a random pivot.
template <class T>
void quickSort(vector <T> & arr, int p, int r, bool piv_flag) {
if (p < r) {
int q, piv(p); counter++;
//piv = ((p + r) / 2); doesn't work
q = partition(arr, p, r, piv);
quickSort(arr, p, q - 1, piv_flag); //Sort left half
quickSort(arr, q + 1, r, piv_flag); //Sort right half
}
return;
}
int partition(vector <T> & arr, int left, int right, int piv) {
int i{ left - 0 }, j{ right + 0 }, pivot{ arr[piv] };
while (i < j) {
while (arr[i] <= pivot) { i++; }
while (arr[j] > pivot) { j--; }
if (i < j) (swap(arr[i], arr[j]));
else {
swap(arr[j], arr[piv]);
return j;
}
}
}
Thank you.
In many applications the pivot is chosen as some element in the array, but it can also be any value you may use to separate the numbers in the array into two. If pivot value you choose is a specific element in the array you need to place it between those two groups after you partition the array into two. If not, you can just proceed with the recursive sorting process by calling the indices properly. (i.e. keeping in mind that there is no pivot element in the array, but just the two groups of values)
See this response to a similar question for a concise explanation of some widely-used alternatives for selecting a pivot.
The most important function of the pivot as to serve as a boundary between the groups we are trying to create during the partitioning phase of quicksort. The goal/challenge here is to create those groups in such a way that they are equal or almost equal in size so that quicksort can work efficiently. That challenge is the reason so many pivot selection methods are conceived. (i.e. so that at least in most cases the numbers will be separated into groups of similar size)
As to the second part of your question regarding how the position of the pivot will change once the partitioning is done, see below for a sample partitioning phase.
Say we have an array A with elements [4,1,5,3,3,5,8,9,2,1] and we chose pivot to be the first element, namely 4. The letter E used below indicates the end of the elements that are smaller than the pivot. (i.e. the last element that is smaller than the pivot)
E
[4,1,5,3,3,5,8,9,2,1]
E
[4,1,3,5,3,5,8,9,2,1]
E
[4,1,3,3,5,5,8,9,2,1]
E
[4,1,3,3,2,5,8,9,5,1]
E
[4,1,3,3,2,1,8,9,5,5]
[1,1,3,3,2,4,8,9,5,5] // swap pivot with the rightmost element that is smaller than its value
After this partitioning, the elements are still not sorted, obviously. But all the elements that is to the left of 4 are smaller than 4, and all the ones to its right are larger than 4. To sort them, we recursively use Quicksort on those groups.
Based on your code, below is a sample partitioning code based on the procedure I described above. You may also observe its execution here.
template <class T>
int partition(vector<T>& arr, int left, int right, int piv) {
int leftmostSmallerThanPivot = left;
if(piv != left)
swap(arr[piv], arr[left]);
for(int i=left+1; i <= right; ++i) {
if(arr[i] < arr[left])
swap(arr[++leftmostSmallerThanPivot], arr[i]);
}
swap(arr[left], arr[leftmostSmallerThanPivot]);
return leftmostSmallerThanPivot;
}
template <class T>
void quickSort(vector<T>& arr, int p, int r) {
if (p < r) {
int q, piv(p);
piv = ((p + r) / 2); // works
q = partition(arr, p, r, piv);
quickSort(arr, p, q - 1); //Sort left half
quickSort(arr, q + 1, r); //Sort right half
}
}
I find it hard to understand Skiena's quick sort. Specifically, what he is doing with the partition function, especially the firsthigh parameter?
quicksort(item_type s[], int l, int h) {
int p; /* index of partition */
if ((h - l) > 0) {
p = partition(s, l, h);
quicksort(s, l, p-1);
quicksort(s, p+1, h);
}
}
We can partition the array in one linear scan for a particular pivot element by maintaining three sections of the array: less than the pivot (to the left of firsthigh), greater than or equal to the pivot (between firsthigh and i), and unexplored (to the right of i), as implemented below:
int partition(item_type s[], int l, int h) {

int i; /* counter */
int p; /* pivot element index */
int firsthigh; /* divider position for pivot element */
p = h;
firsthigh = l;
for (i = l; i <h; i++) {
if (s[i] < s[p]) {
swap(&s[i],&s[firsthigh]);
firsthigh ++;
}
swap(&s[p],&s[firsthigh]);
return(firsthigh);
}
I recommend following the reasoning with pencil and paper while reading through this answer and its considered example case
Some parenthesis are missing from the snippet:
int partition(item_type s[], int l, int h)
{
int i;/* counter */
int p;/* pivot element index */
int firsthigh;/* divider position for pivot element */
p = h;
firsthigh = l;
for (i = l; i < h; i++) {
if (s[i] < s[p]) {
swap(s[i], s[firsthigh]);
firsthigh++;
}
}
swap(s[p], s[firsthigh]);
return(firsthigh);
}
void quicksort(item_type s[], int l, int h)
{
int p; /* index of partition */
if ((h - l)>0) {
p = partition(s, l, h);
quicksort(s, l, p - 1);
quicksort(s, p + 1, h);
}
}
Anyway the partition function works as follows: suppose we have the array { 2,4,5,1,3 } of size 5. The algorithm grabs the last element 3 as the pivot and starts exploring the items iteratively:
2 is first encountered.. since 2 is less than the pivot element 3, it is swapped with the position 0 pointed by firsthigh. This has no effect since 2 is already at position 0
2,4,5,1,3
^
firsthigh is incremented since 2 is now a stable value at that position.
Then 4 is encountered. This time 4 is greater than 3 (than the pivot) so no swap is necessary. Notice that firsthigh continues pointing to 4. The same happens for 5.
When 1 is encountered, this value should be put after 2, therefore it is swapped with the position pointed by firsthigh, i.e. with 4's position
2,4,5,1,3
^ ^ swap
2,1,5,4,3
^ now firsthigh points here
When the elements end, the pivot element is swapped with firsthigh's position and therefore we get
2,1,| 3,| 4,5
notice how the values less than the pivot are put on the left while the values greater than the pivot remain on the right. Exactly what is expected by a partition function.
The position of the pivot element is returned and the process is repeated on the subarrays on the left and right of the pivot until a group of 0 elements is encountered (the if condition is the bottom of the recursion).
Therefore firsthigh means: the first element greater than the pivot that I know of. In the example above firsthigh is put on the first element since we still don't know if that element is greater or less than the pivot
2,4,5,1,3
^
as soon as we realize 2 is not the first element greater than the pivot or we swap a less-than-the-pivot element in that position, we try to keep our invariant valid: ok, advance firsthigh and consider 4 as the first element greater than the pivot. This gives us the three sections cited in the textbook.
At all times, everything strictly to the left of firstHigh is known to be less than the pivot (notice that there are initially no elements in this set), and everything at or to the right of it is either unknown, or known to be >= the pivot. Think of firstHigh as the next place where we can put a value lower than the pivot.
This algorithm is very similar to the in-place algorithm you would use to delete all items that are >= the pivot while "compacting" the remaining items as far to the left as possible. For the latter, you would maintain two indices l and firstHigh (which you could think of as from and to, respectively) that both start at 0, and walk l through the array; whenever you encounter an s[l] that should not be removed, you shunt it as far left as possible: i.e., you copy it to s[firstHigh] and then you increment firstHigh. This is safe because we always have firstHigh <= l. The only difference here is that we can't afford to overwrite the deleted (possibly->=-to-pivot) item currently residing at s[firstHigh], so we swap the two items instead.
I could not understand the optimised chained Matrix multiplication(using DP) code example given in my algorithm's book.
int MatrixChainOrder(int p[], int n)
{
/* For simplicity of the program, one extra row and one extra column are
allocated in m[][]. 0th row and 0th column of m[][] are not used */
int m[n][n];
int i, j, k, L, q;
/* m[i,j] = Minimum number of scalar multiplications needed to compute
the matrix A[i]A[i+1]...A[j] = A[i..j] where dimention of A[i] is
p[i-1] x p[i] */
// cost is zero when multiplying one matrix.
for (i = 1; i < n; i++)
m[i][i] = 0;
// L is chain length.
for (L=2; L<n; L++)
{
for (i=1; i<=n-L+1; i++)
{
j = i+L-1;
m[i][j] = INT_MAX;
for (k=i; k<=j-1; k++)
{
// q = cost/scalar multiplications
q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if (q < m[i][j])
m[i][j] = q;
}
}
}
return m[1][n-1];
}
Why does the first loop starts from 2 ?
Why is j set to i+L-1 and i to n-L+1 ?
I understood the recurrence relation, but could not understand why loops are set like this ?
EDIT:
What is the way to get the parenthesis order after DP ?
In bottom up, that is DP we try to solve the smallest possible case first(we solve each smallest case). Now when we look at the recurrence (m[i,j] represents cost to parenthise from i , j..)
We can see that the smallest possible solution(which will be needed by any other larger sub problem) is of a smaller length than that we need to solve... For P(n) .We need all the costs of parenthising the expression with length lessser than n. This leads us to solve the problem lengthwise... (Note l in the outer loop represents length of the segment whose cost we are trying to optimise)
Now first we solve all the sub problems of length 1 i.e. 0 always (No multiplication required)...
Now your question L=2 -> L=n
we are varying length from 2 to n just to solve the sub problems in order...
i is the starting point of all the sub intervals such that they can be the begining of an interval of length l..
Naturally j represents the end of sub interval -> i+l-1 is the end of sub interval (just because we know the starting point and length we can figure out the end of subinterval)
L iterates the length of a chain. Clearly, a chain cannot be 1 piece long. i iterates the beginning of the chain. If the first piece is i, then the last piece will be i+L-1, which is j. (Try to imagine a chain and count). The condition in the cycle makes sure that for any value of i, the last piece is not greater than the maximum Length n.
Shortly, those are limitations to keep the values in the given boundaries.
I have a problem understanding quicksort algorithm (the simplified version without pointers) from K&R. There is already a thorough explanation provided by Dave Gamble here explanation.
However I noticed that by starting with a slightly changed string we can obtain no swaps during many loops of the for loop.
Firstly the code:
void qsort(int v[], int left, int right)
{
int i, last;
void swap(int v[], int i, int j);
if (left >= right) /* do nothing if array contains */
return; /* fewer than two elements */
swap(v, left, (left + right)/2); /* move partition elem */
last = left; /* to v[0] */
for (i = left + 1; i <= right; i++) /* partition */
if (v[i] < v[left])
swap(v, ++last, i);
swap(v, left, last); /* restore partition elem */
qsort(v, left, last-1);
qsort(v, last+1, right);
}
Walkthrough in my opinion:
we start with CADBE; left=0; right=4; D is the pivot
so according to algorithm we swap D with C obtaining DACBE
last = left =0
i = 1 if ( v1 < v[0] ) it is true so we swap v1 (because last is incremented before operation) with v1 so nothing changes, last = 1, still having DACBE;
now i = 2 if ( v[2] < v[0] ) -> true so we swap v[2] with v[2] nothing changed again; last = 2
now i = 3 if ( v[3] < v[0] ) -> true so we swap v[3] with v[3] nothing changed AGAIN (!), last = 3
So apparently something is wrong, algorithm does nothing.
Your opinions appreciated very much. I must be wrong, authors are better than me ;D
Thanks in advance!
The loop goes from left + 1 up to and including right. When i=4, the test fails and last does not get incremented.
Then the recursive calls sort BACDE with left=0,right=2 and left=4,right=4. (Which is correct when D is the pivot.)
Well, it just so happened that your input sub-array ACBE is already partitioned by D (ACB is smaller than D and E is bigger than D), so it is not surprising the partitioning cycle does not physically swap any values.
In reality, it is not correct to say that it "does nothing". It does not reorder anything in the cycle, since your input data need no extra reordering. But it still does one thing: it finds the value of last that says where smaller elements end and bigger elements begin, i.e. it separates ACBE into ACB and E parts. The cycle ends with last == 3, which is the partitioning point for further recursive steps.
If n numbers are given, how would I find the total number of possible triangles? Is there any method that does this in less than O(n^3) time?
I am considering a+b>c, b+c>a and a+c>b conditions for being a triangle.
Assume there is no equal numbers in given n and it's allowed to use one number more than once. For example, we given a numbers {1,2,3}, so we can create 7 triangles:
1 1 1
1 2 2
1 3 3
2 2 2
2 2 3
2 3 3
3 3 3
If any of those assumptions isn't true, it's easy to modify algorithm.
Here I present algorithm which takes O(n^2) time in worst case:
Sort numbers (ascending order).
We will take triples ai <= aj <= ak, such that i <= j <= k.
For each i, j you need to find largest k that satisfy ak <= ai + aj. Then all triples (ai,aj,al) j <= l <= k is triangle (because ak >= aj >= ai we can only violate ak < a i+ aj).
Consider two pairs (i, j1) and (i, j2) j1 <= j2. It's easy to see that k2 (found on step 2 for (i, j2)) >= k1 (found one step 2 for (i, j1)). It means that if you iterate for j, and you only need to check numbers starting from previous k. So it gives you O(n) time complexity for each particular i, which implies O(n^2) for whole algorithm.
C++ source code:
int Solve(int* a, int n)
{
int answer = 0;
std::sort(a, a + n);
for (int i = 0; i < n; ++i)
{
int k = i;
for (int j = i; j < n; ++j)
{
while (n > k && a[i] + a[j] > a[k])
++k;
answer += k - j;
}
}
return answer;
}
Update for downvoters:
This definitely is O(n^2)! Please read carefully "An Introduction of Algorithms" by Thomas H. Cormen chapter about Amortized Analysis (17.2 in second edition).
Finding complexity by counting nested loops is completely wrong sometimes.
Here I try to explain it as simple as I could. Let's fix i variable. Then for that i we must iterate j from i to n (it means O(n) operation) and internal while loop iterate k from i to n (it also means O(n) operation). Note: I don't start while loop from the beginning for each j. We also need to do it for each i from 0 to n. So it gives us n * (O(n) + O(n)) = O(n^2).
There is a simple algorithm in O(n^2*logn).
Assume you want all triangles as triples (a, b, c) where a <= b <= c.
There are 3 triangle inequalities but only a + b > c suffices (others then hold trivially).
And now:
Sort the sequence in O(n * logn), e.g. by merge-sort.
For each pair (a, b), a <= b the remaining value c needs to be at least b and less than a + b.
So you need to count the number of items in the interval [b, a+b).
This can be simply done by binary-searching a+b (O(logn)) and counting the number of items in [b,a+b) for every possibility which is b-a.
All together O(n * logn + n^2 * logn) which is O(n^2 * logn). Hope this helps.
If you use a binary sort, that's O(n-log(n)), right? Keep your binary tree handy, and for each pair (a,b) where a b and c < (a+b).
Let a, b and c be three sides. The below condition must hold for a triangle (Sum of two sides is greater than the third side)
i) a + b > c
ii) b + c > a
iii) a + c > b
Following are steps to count triangle.
Sort the array in non-decreasing order.
Initialize two pointers ‘i’ and ‘j’ to first and second elements respectively, and initialize count of triangles as 0.
Fix ‘i’ and ‘j’ and find the rightmost index ‘k’ (or largest ‘arr[k]‘) such that ‘arr[i] + arr[j] > arr[k]‘. The number of triangles that can be formed with ‘arr[i]‘ and ‘arr[j]‘ as two sides is ‘k – j’. Add ‘k – j’ to count of triangles.
Let us consider ‘arr[i]‘ as ‘a’, ‘arr[j]‘ as b and all elements between ‘arr[j+1]‘ and ‘arr[k]‘ as ‘c’. The above mentioned conditions (ii) and (iii) are satisfied because ‘arr[i] < arr[j] < arr[k]'. And we check for condition (i) when we pick 'k'
4.Increment ‘j’ to fix the second element again.
Note that in step 3, we can use the previous value of ‘k’. The reason is simple, if we know that the value of ‘arr[i] + arr[j-1]‘ is greater than ‘arr[k]‘, then we can say ‘arr[i] + arr[j]‘ will also be greater than ‘arr[k]‘, because the array is sorted in increasing order.
5.If ‘j’ has reached end, then increment ‘i’. Initialize ‘j’ as ‘i + 1′, ‘k’ as ‘i+2′ and repeat the steps 3 and 4.
Time Complexity: O(n^2).
The time complexity looks more because of 3 nested loops. If we take a closer look at the algorithm, we observe that k is initialized only once in the outermost loop. The innermost loop executes at most O(n) time for every iteration of outer most loop, because k starts from i+2 and goes upto n for all values of j. Therefore, the time complexity is O(n^2).
I have worked out an algorithm that runs in O(n^2 lgn) time. I think its correct...
The code is wtitten in C++...
int Search_Closest(A,p,q,n) /*Returns the index of the element closest to n in array
A[p..q]*/
{
if(p<q)
{
int r = (p+q)/2;
if(n==A[r])
return r;
if(p==r)
return r;
if(n<A[r])
Search_Closest(A,p,r,n);
else
Search_Closest(A,r,q,n);
}
else
return p;
}
int no_of_triangles(A,p,q) /*Returns the no of triangles possible in A[p..q]*/
{
int sum = 0;
Quicksort(A,p,q); //Sorts the array A[p..q] in O(nlgn) expected case time
for(int i=p;i<=q;i++)
for(int j =i+1;j<=q;j++)
{
int c = A[i]+A[j];
int k = Search_Closest(A,j,q,c);
/* no of triangles formed with A[i] and A[j] as two sides is (k+1)-2 if A[k] is small or equal to c else its (k+1)-3. As index starts from zero we need to add 1 to the value*/
if(A[k]>c)
sum+=k-2;
else
sum+=k-1;
}
return sum;
}
Hope it helps........
possible answer
Although we can use binary search to find the value of 'k' hence improve time complexity!
N0,N1,N2,...Nn-1
sort
X0,X1,X2,...Xn-1 as X0>=X1>=X2>=...>=Xn-1
choice X0(to Xn-3) and choice form rest two item x1...
choice case of (X0,X1,X2)
check(X0<X1+X2)
OK is find and continue
NG is skip choice rest
It seems there is no algorithm better than O(n^3). In the worst case, the result set itself has O(n^3) elements.
For Example, if n equal numbers are given, the algorithm has to return n*(n-1)*(n-2) results.