標籤:style class blog code http color
題目:輸入n個整數,找出其中最小的K個數
方法一:直接std::sort,T(n) = O(nlgn)
方法二:直接std::nth_element T(n) = O(n) 但是修改了原數組
void MinKth(std::vector<int>& num, int kth, std::vector<int>& result){ if (num.size() == 0 || || kth <= 0 || kth > num.size()) { throw std::invalid_argument("invalid_argument"); } std::nth_element(num.begin(), num.begin() + kth - 1, num.end()); for (int i = 0; i < kth; ++i) { result.at(i) = num.at(i); }}
自己實現 nth_element:
一次劃分:
int Partition(std::vector<int>& num, int first, int last){ if (first < 0 || last >= num.size() || first > last) { throw std::invalid_argument("invalid_argument"); } int index = rand() % (last - first + 1) + first; int key = num.at(index); std::swap(num.at(first), key); int midIndex = first; for (int i = first + 1; i <= last; ++i) { if (num.at(i) < num.at(first)) { std::swap(num.at(++midIndex), num.at(i)); } } std::swap(num.at(midIndex), num.at(first)); return midIndex;}
void GetKth(std::vector<int>& num, int kth, std::vector<int>& result){ if (num.size() == 0 || kth > num.size()) { throw std::invalid_argument("invalid_argument"); } int first = 0; int last = num.size() - 1; int index = Partition(num, first, last); while (index != kth - 1) { if (index > kth - 1) { last = index - 1; index = Partition(num, first, last); } else { first = index + 1; index = Partition(num, first, last); } } for (int i = 0; i < kth; ++i) { result.at(i) = num.at(i); }}
方法三:O(nlgk)解法
先建立一個大小為K的資料容器來儲存最小的k個數,然後順序遍曆原數組:
如果容器中已有數字少於K個,則直接將原數組元素讀入到容器中
如果容器中已有K個數字,表示已滿,這時 我們找出容器中K個數的最大值,將這個最大值與待插入元素比較:
如果待插入元素比最大值還要大,那麼直接忽略
如果待插入元素比最大值要小,那麼就用這個元素替換掉最大值
我們該選取什麼資料結構來表示這個資料容器呢?
如果是vector數組,那麼每次找最大值需要O(k)時間,總時間為O(nk)
注意到我們每次僅僅需要的是最大值,很自然地想到最大堆,每次調整堆需要O(lgk),總時間為O(nlgk)
(1) 資料容器使用vector
void GetKth(const std::vector<int>& num, int kth, std::vector<int>& result){ if (num.size() == 0 || kth <= 0 || kth > num.size()) { throw std::invalid_argument("invalid_argument"); } for (int i = 0; i < num.size(); ++i) { if (result.size() < kth) { result.push_back(num.at(i)); } else { auto maxIter = std::max_element(result.begin(), result.end()); if (num.at(i) < *maxIter) { *maxIter = num.at(i); } } }}
(2)資料容器使用 堆
首先 堆是一棵完全二叉樹,根據完全二叉樹的性質,我們可以非常方便的由根節點定位到左子節點和右子節點
一般使用數組來表示完全二叉樹,結點之間的關係依靠 結點編號來維持
假設父節點的編號為 index (1<= index <= num.size() ) 其左子節點編號為 2*index, 其右子節點編號為 2*index + 1
(註:圖片來自於:http://www.cnblogs.com/Anker/archive/2013/01/23/2873422.html)
(1) 堆調整
堆調整操作是對編號為index的結點進行“下降”,使以index為根的子樹成為最大堆
(註:圖片來自於:http://www.cnblogs.com/Anker/archive/2013/01/23/2873422.html)
void MaxHeapAdjust(std::vector<int>& num, int index) { int leftIndex = 2 * index; int rightIndex = 2 * index + 1; int largest = 0; if (leftIndex <= num.size()) { if (num.at(leftIndex - 1) > num.at(index - 1)) { largest = leftIndex; } else { largest = index; } } if (rightIndex <= num.size()) { if (num.at(rightIndex - 1) > num.at(largest - 1)) { largest = rightIndex; } } if (largest == 0) return; if (largest != index) { std::swap(num.at(index - 1), num.at(largest - 1)); MaxHeapAdjust(num, largest); }}
(2) 建堆
建立最大堆的過程是自底向上地調用最大堆調整程式將一個數組A[1.....N]變成一個最大堆。將數組視為一顆完全二叉樹,從其最後一個非葉子節點(n/2)開始進行堆調整
for (int i = num.size() / 2; i >= 1 ; --i) { MaxHeapAdjust(num, i);}
堆調整和建堆操作都已經說清楚了,再回到本題中來
我們首先取K個數建堆,然後對其後的數字不斷與堆頂元素(即最大值)做比較,如果比堆頂元素要小,即更新堆頂元素,並以堆頂元素為根結點進行堆調整操作
void GetKth(const std::vector<int>& num, int kth, std::vector<int>& heap) { if (num.size() == 0 || kth <= 0 || kth > num.size()) { throw std::invalid_argument("invalid_argument"); } for (int i = 0; i < kth; ++i) { heap.push_back(num.at(i)); } for (int i = heap.size() / 2; i >= 1 ; --i) { MaxHeapAdjust(heap, i); } for (int i = kth; i < num.size(); ++i) { if (num.at(i) < heap.at(0)) { heap.at(0) = num.at(i); MaxHeapAdjust(heap, 1); } }}
最後上一張圖總結一下兩種方法的優缺點: