http://blog.csdn.net/yanzi1225627/article/details/8111806 、http://www.eyeandroid.com/thread-9629-1-1.html這裡討論了求第二大的思路。現在研究如果是第i大怎麼辦?
先清晰一下概念,如果有個數組a[6] = 2 60 10 32 84 6; 那麼第1大的數是最大值84, 第2大是60, 第3大是32, 第1小是2, 第二小是6., 第三小是10, 第4小是32. 也即如果讓找第i小, 等價於找第n-i+1大。 如果讓找第i大, 等效於找第n+1-i小,兩者可以互換。
思路還是快速排序,為了方便我們假設找第i小的數。
先看快速排序的partition函數:
int partition(int a[], int i, int j){int pivot = a[i];while(i<j){while(i<j && a[j] >= pivot)j--;if(i<j)a[i++] = a[j];while(i<j && a[i] <= pivot)i++;if(i<j)a[j--] = a[i];}a[i] = pivot;return i;}
下面就是找第i小的數:
int find_n_min(int a[], int low, int high, int n_min){int p = partition(a, low, high);if(p-low+1 == n_min)return a[p];if(p-low+1 > n_min) return find_n_min(a, low, p-1, n_min);else return find_n_min(a, p+1, high, n_min-(p-low+1));}
解釋下思路,先找到劃分的基準索引p,p左邊的都是小於a[p]的, 那麼可以得到a[p]就是第p-low+1小的數。 為了方便舉個例子, 如果經過一次partition函數之後,數組為 5 3 6 12 50 21,p = 3,也就是a【3】 = 12是劃分的基準,那麼a【3】肯定是3-0+1 = 4,第4小的數。
如果p-low+1 >n_min, 比如,p-low+1 = 5, a[p]是第5小的數,而程式讓找第2小的數,這個時候需要遞迴到 【low, p-1】區間去找第2小的數,這個舉了例子應該不難理解了。
對應的如果小於n_min的話,就要從p+1開始到high區間進行調用搜尋第n_min - (p - low +1)小的數。
這裡需要注意兩個事,
第一,遞迴的時候前面不要忘了return
第二,遞迴的時候新的區間一定是【low, p-1】,這個減一一定不能少!為什嗎?這是我們這裡採用的a[low]作為基準值,而http://blog.csdn.net/yanzi1225627/article/details/8109035這裡採用的是(high
+ low)/2作為基準值,所以遞迴的時候,臨界區間的一邊沒有減一。
為了清晰說明這個問題,我們舉個例子,假設初始數組a[] = 12 3 6 5 50 21,我們要找第3小的數。
第一次進到partition函數後,以12作為基準,partition函數返回的p = 3,數組這個時候更新成5 3 6 12 50 21,因為p-0+1 = 4大於3,所以索引從0到2搜尋即可!如果上面臨界區間沒有減1,也就是從0到3搜尋的話(數組為5 3 6 12),我們看看會有啥後果?
第二次進到partion,基準為5,函數返回的p = 1,數組更新成3 5 6 12,由於p-0+1 = 2,因此需要進到第三次partition,臨界區間不加1的話(正確的應該從p+1開始),就是從p(也就是1)到3,數組為 5 6 12,問題轉換為在5 6 12中找第1小的數
第三次進到partion,壞就壞在這,因為是用的第一個數5作為基準的,所以這個partition返回的p = 1. 也就是5,所以最終函數的找到的第三小的數也就是5.這顯然是錯誤的!因此,在這裡用a[low]作基準的話,臨界區間必須減1。如果像這裡http://blog.csdn.net/yanzi1225627/article/details/8109035用mid
= (low +high)/2坐的基準,更新後的區間則不用減一。
至此,在數組中找第i小的問題解決了,如果要找第i大的數只需將參數轉化下就中,這在開頭交代過。或者將上面函數裡的p-low+1 改成high-p+1,a[p]為第i大的數,簡單調整下即可。