這個題目之前在看數據結構和算法分析的時候見過,其實解法也挺多的。
排序后找出前k個元素
快排O(NlogN),然后O(K),總的復雜度為O(NlogN)創建一個長度為k的容器,然后不斷地維護更新,這樣的話,每次都要:
1,將當前的k個元素中的最大者與當前遍歷的元素比較
2,若當前的元素更小,則需要替換這個值
3,若有上一步的話,將新的數組排序
據說用二叉樹的話復雜度為O(logK)。。
然后遍歷n-k的元素
總的復雜度為O(Nlog(k))
- 和快速排序一樣,我們改造一下得到快速選擇
quickSelect
首先大家如果不太熟悉快排,可以去看一下。
這里我們要打印的元素只是前k個,因此我們把快排的算法改進一下
快排:
void quick_sort(int* a, int left, int right)
{
int i, j;
int pivot;
if (left + 3<= right)
{
//pivot = Median3(a, left, right);
pivot = get_pivot(a, left, right);//error1
i = left;
j = right - 1;
for (;;)
{
while (a[++i] < pivot){}
while (a[--j] > pivot){}
if (i < j)
swap(&a[i], &a[j]);
else
break;
}
swap(&a[i], &a[right - 1]);
quick_sort(a, left, i - 1);
quick_sort(a, i + 1, right);
}
else
InsertionSort(a + left, right - left + 1);
}
特別要提到的是
if (left + 3<= right)
這里加上了一個cutoff量
我試了一下,如果這個值是0,1,2的話,會出錯
大家可以宏定義一個cutoff,然后自己設置值,這一點呢,我猜是為了防止越界的時候出現問題,交給插入排序來做
其中pivot的程序:
//不僅是找到中位數,而且為了方便要把pivot移動到最右邊(hide),這樣就不用去處理這個元素了
//技巧:找中位數不要局限在比較大小,這樣很麻煩的,可以通過swap快速達到效果
int get_pivot(int*arr, int beg, int end)
{
int middle = (beg + end) / 2; //change2
if (arr[middle] < arr[beg])
swap(&arr[middle], &arr[beg]);
if (arr[end] < arr[beg])
swap(&arr[beg], &arr[end]);
if (arr[middle]>arr[end])
swap(&arr[middle], &arr[end]);
//hide pivot
swap(&arr[middle], &arr[end - 1]);//we don't need to deal with arr[end],but it's ok if you do
return arr[end - 1];
}
swap:
void swap(int* a, int *b) { int tmp = *a; *a = *b; *b = tmp; }
快排里面,
quick_select(arr,beg,i-1); quick_select(arr,i+1,end);
這一句可以改進而使quickselect更快速,快排的復雜度為O(N*logN),而下面的算法則可以達到O(N)!
.
因為我們只需要保證前k個元素是最小的;而我們每次調用,都保證了pivot前面的元素不大于pivot && pivot后面的元素不小于pivot
我們有一個下標 i 來記錄pivot的位置,所以我們可以判斷k與i的關系來決定對那一部分繼續進行遞歸:
1,如果k小于i,那么只需要對前面的i個進行遞歸調用
2,如果k等于i+1,說明剛好有k個元素,直接打印
3,如果k大于i,那么前i個元素就不需要管理,處理后面的一部分
如下:
//quickSelect:
//attention that Kth value in array is arr[k-1]
if (k < i)
quick_select(arr, beg, i - 1, k);
else
if (k>i + 1)
quick_select(arr, i + 1, end, k);
//如果k比中間要長,那么 i 之前的都不需要管了,只需要看后面的部分
//所以我們看見,quickSelect只需要對一邊進行遞歸,所以,復雜度會低于quicksort
以上是quickSort,接下來是select:
函數的本身并沒有什么變化,只是改動了上面提到的兩句遞歸部分:
/*if (k - 1 == i) //it seems that it dosen't matter
return;
else*/
if (k < i)
quick_select(arr, beg, i - 1, k);
else
if (k>i + 1)
quick_select(arr, i + 1, end, k);
*不過我試了一下,第一種情況k - 1 == i
可以注釋掉。。
void quick_select(int* arr, int beg, int end, int k)
{
int pivot;
int i, j;
if (beg +2 <= end)//<= matters
{
pivot = get_pivot(arr, beg, end);
i = beg;
j = end - 1;//it is okay to be j=end
while (1)
{
while (arr[++i] < pivot);//it doesn't matter wether it is "++i" or "i++"
while (arr[--j] > pivot);
if (i < j)
swap(&arr[i], &arr[j]);
else break;
}
swap(&arr[i], &arr[end - 1]);//since you hide, restore here
//follow the quickSort :
//quick_select(arr, beg, i - 1, k);
//quick_select(arr, i + 1, end, k);
//quickSelect:
//attention that Kth value in array is arr[k-1]
if (k - 1 == i)
return;
else if (k < i)
quick_select(arr, beg, i - 1, k);
else
if (k>i + 1)
quick_select(arr, i + 1, end, k);
//如果k比中間要長,那么 i 之前的都不需要管了,只需要看后面的部分
//所以我們看見,quickSelect只需要對一邊進行遞歸,所以,復雜度會低于quicksort
}
else
InsertionSort(arr + beg, end - beg + 1);
}
我用例子試了一下是沒有問題的:
int a[] = { 5, 3, 8, 99, 10, 2, 4, 7, 1, 22 };
quick_select(a, 0, 9, 6);
//Qsort(a, 0, 9);
//quick_sort(a, 0, 9);
for (int i = 0; i < 10; i++)
std::cout << a[i] << " ";
補充
我在做這道題的時候在網上搜索過,還有一種題目是讓你輸出第k個元素。。在以上程序中稍稍改進,加入返回的值然后輸出
像這樣就可以了:
if (k - 1 == i)
return arr[i];
else if (k < i)
return quick_select_k(arr, beg, i - 1, k);
else
if (k>i + 1)
return quick_select_k(arr, i + 1, end, k);
- 記錄一下我遇到的坑:
在寫get_pivot()
的時候,我最開始是這樣寫的:
//int middle = arr[(beg + end) / 2]; //change2
//if (middle < arr[beg])
// swap(&middle, &arr[beg]);
//if (arr[end] < arr[beg])
// swap(&arr[beg], &arr[end]);
//if (middle>arr[end])
// swap(&middle, &arr[end]);
//swap(&middle, &arr[end - 1]);
//beg<middle<end
第一行就出錯了??!
int middle = arr[(beg + end) / 2];
我tm調試了一兩個小時才找到這個問題啊!一直以為我算法錯了?。?br>
這里在數組里面用除法,似乎是有問題的!
update:
上stackoverflow問了一下發現果然很sb??!
我那種錯誤的寫法根本沒有達到交換的效果,只是把middle這個值賦給了數組里面的一個元素!
學習了!