C++經典算法實現系列2
上回我們說道,牛逼的C++可以實現很多牛逼的算法。我們繼續之前的記錄。
Algorithm 4. 二分查找
我們將實現一個二分查找的算法。
首先必須明白,二分法查找數畢竟滿足:這堆數是保存在數組中,而且這堆數必須是有序排列,也就是需要要求有一定的順序。之所以要求實在數組中,是因為,如果存在鏈表中,鏈表的存儲不是連片的,
而數組是連片的,這樣數組就可以非常輕而易舉的通過下標index每個元素。其實我感覺C++里面好像都是用的數組吧。直接上代碼吧:
int binary_search(int a[], int low, int high, int target) {
// binary search, search x in a
if (low > high) {
return -1;
}
int mid = (low + high) / 2;
if (a[mid] > target) {
return binary_search(a, low, mid - 1, target);
}
if (a[mid] < target) {
return binary_search(a, mid + 1, high, target);
}
return mid;
}
這是比較簡單的二分查找的遞歸實現。但是請注意,二分法其實是有要求的,那就是,查找的數組必須是升序。我感覺這樣查找就沒有啥意思了,要是有一個牛逼的查找算法可以在一個混亂的數組中定位一個元素那就比較牛逼了。
二分查找的局限性就發生在它的前置條件上,需要數組是有序的,然而大部分數組都是無序的,如果將數組構建成為有序數組,那么又有第二個問題,必須是數組,而數組在構建有序過程中其實是非常低效的,因為數組是連片存儲,在移動和 插入過程中會有很大的開銷。因此讓我們接下來來實現一個二叉查找樹算法。據說該算法既可以構建有序的集合又可以高效率的搜尋目標。
Algorithm 5. 尋找最大的K個元素
這個問題應該被歸結為top k問題,而不是排序問題。這個問題有很多種解法,其中最簡單的當然是先排序,然后再選取k個最大的數,還有一種解法是,使用快速排序的思想,具體如下:
1. 隨機選擇一個元素X,將數組S分為兩部分,一部分全部大于X,一部分全部小于X,其實這就是快速排序的第一步;
2. 如果第一部分元素個數大于K,在繼續在該部分查找K,重復1,直到元素個數等于K;
3. 如果第二部分元素小于K,則在第二部分繼續分開,查找剩下的K-T個元素;
這個算法簡單易行,但是請注意,這個top K是無序的,時間復雜度為O(nlogK),這里我實現一個牛逼的基于模板的實現,事實上用模板更簡單易懂一些:
#include <iostream>
#include <vector>
using namespace std;
// find top k implement by STL
vector<int>::iterator find_k(vector<int>::iterator begin, vector<int>::iterator end, int k) {
vector<int>::difference_type n = k;
if (end - begin <= k) {
return end;
}
auto left = begin;
auto right = end - 1;
srand(time(NULL));
int index = (int) rand() % n;
iter_swap(begin, begin + index);
while (left < right) {
// traverse right than left
while (*right <= *left && left < right) {right--;}
if (left < right) {iter_swap(left, right);}
while (*left > *right && left < right) { left++; }
if (left < right) {iter_swap(left, right);}
}
n = left - begin;
if (n + 1 >= k ) {
// if left element more than k, find from left
// TODO: why left + 1?
return find_k(begin, left + 1, k);
} else {
// if left element less than k, find the rest k- n
return find_k(left + 1, end, k - n - 1);
}
}
int main()
{
vector<int> a = {3, 56, 7, 89, 34, 12, 56, 39};
auto it_r = find_k(a.begin(), a.end(), 5);
for (auto it = a.begin(); it < it_r; it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
當然,這個問題還有一個清晰的解法,我把它叫做最小堆方法,它的思想是:
1. 首先隨機選擇K個數,然后從原數組中依次拿出一個數,和這里的每一個數進行比較,如果都小于,則pass該數,如果比某個元素大,就替換掉它,直到所有元素比完為止;
大家可以看到,這個算法非常簡單,只需要一步,而且時間復雜度是:O(n*K),不僅如此,這個算法的空間復雜度也很低,只需要把K個元素裝入內存即可,其他元素只需要讀取。這個算法我就不寫出實際實現額。相反還有一個有意思的算法,也就是二分法來實現它。
二分法之前業實現過。二分法的思路是:
1. 首先我們要知道整個數組的最大值和最小值以及數組長度,然后先從中值開始,如果mid~max個數大于K,則在mid~max繼續尋找,mid變成min;
2. 如果mid~max個數小于K則在min~mid繼續尋找,mid變成max,不斷的縮短區間,知道max-min=1;
二分法實現的思想也很簡單,但是實際上,在實際問題運用中不好用。首先我覺得你必須要知道最大值最小值有時候并不太現實。這個算法復雜度也是O(n*K).