演算法導論 中位數和順序統計量之選擇演算法

2021-06-27 22:43:41 字數 3762 閱讀 6811

實際生活中,我們經常會遇到這類問題:在乙個集合,誰是最大的元素?誰是最小的元素?或者誰是第二小的元素?。。。。等等。那麼如何在較短的時間內解決這類問題,就是本文要闡述的。

先來熟悉幾個概念:

1、順序統計量:

在乙個由n個元素組成的集合中,第i個順序統計量(order statistic)是該集合中第i小的元素。最小值是第1個順序統計量(i=1),最大值是第n個順序統計量(i=n)。

2、中位數:

乙個中位數是它所屬集合的「中點元素」,當n為奇數時,中位數是唯一的,位於i=(n+1)/2處;當n為偶數時,存在兩個中位數,分別位於i=n/2和i=n/2+1處。

直觀的看,對於乙個包含n個元素的集合中,要求其中最小的元素,需要做多少次比較呢?很容易想到,至少要做n-1次比較;我們只需要遍歷集合來進行比較,每次記錄較小的元素,遍歷結束的時候即可得到最小的元素。對於最大值也可以這樣求。

如果問題是需要同時找到最大值和最小值呢,當然可以進行兩次遍歷,做2*(n-1)次比較,就可以得到最大值和最小值。但這並不是最優的,因為我們並不需要每個數既與最大值進行比較又與最小值進行比較。

以偶數個元素的集合為例,我們可以先比較一下前兩個元素,大的那乙個先設為max,而小的那乙個先設為min,而之後的元素由於是偶數個,所以拆成兩個一組,先在組內進行一次比較,然後再將組內大的去與max比較,如果比max大,則替換max的值,否則不變;組內小的去與min比較,類似的操作直到遍歷完整個元集合。那麼一共進行的比較次數為:1+(n/2-1)*3=3n/2-2次。如果是奇數個元素的集合的話,省略第一次的比較操作,然後直接將max和min都設為第乙個元素即可,後面的操作與偶數的情況相同。那麼對於奇數個元素的集合而言,一共要進行的比較次數為:((n-1)/2)*3次。如果不考慮奇數還是偶數,我們至多需要進行3[n/2](不大於n/2的最大整數)次比較即可得到最大值和最小值,也即演算法的時間複雜度為o(n)。

實現的**也比較簡單:

#include typedef int t;

using namespace std;

/* * 包含結果的結構體,裡面含有最大值和最小值

*/struct result

};result* getminmax(int a, int len);

int main() ;

result* r1 = getminmax(a, 9);

cout << "最大值為:" << r1->max << ",最小值為:" << r1->min << endl;

t b[10] = ;

result* r2 = getminmax(b, 10);

cout << "最大值為:" << r2->max << ",最小值為:" << r2->min << endl;

delete r1;

delete r1;

return 0;

}result* getminmax(t a, int len)

if (len == 1)

if (len == 2)

int max, min;

int i = 0;

if (len % 2 == 0) else

while (i < len)

return re;

}

看起來感覺好像要比求最大值和最小值要複雜許多,而實際上,求乙個互異元素的集合中的第i小的元素,時間複雜度與上面的一樣,也是o(n)。下面關於快速排序的知識,可以參看:演算法導論之一(快速排序)

這裡要採用的思路與快速排序有些類似,快速排序找找到乙個mid位置之後,要繼續對mid兩邊的陣列進行快速遞迴排序。而這裡由於只需要找到第i個順位統計量的位置,所以這個元素要麼位於mid位置,要麼位於mid左邊的陣列,要麼位於mid右邊的陣列,所以只需要考慮一邊即可。而對應到演算法複雜度上,快速排序的期望時間複雜度為o(nlgn),而這裡為o(n)。

具體思路:

1、採用快速排序的分組方式,但是稍加改進,每次分組的時候的key值不再是乙個確定的位置的值,而是先從所有元素中隨機挑選乙個作為key值,然後再將其與末尾的元素交換。這樣的好處在於能夠有效避免每次分組都是極度不平衡的狀態:0:n-1。(從概率上看,隨機選擇key值則使得不存在一種固定的情況讓最壞的情況每次都發生)。

2、在得到隨機分組結果之後,我們先看看得到的那個mid位置的元素與我們要找的第i順位統計量是不是乙個位置,如果是,則直接返回mid位置的元素。如果發現mid位置位於第i順位統計量之前,則我們只需要遞迴的對分組的後面的那部分集合進行上面的操作即可,反之,如果發現mid位於第i順位統計量之後,則我們只需要遞迴的堆分組的前半部分集合進行上面的操作即可。

**如下:

/**

* 意在解決:線性時間內o(n)內完成查詢陣列中第i小的元素

*/#include typedef int t;

using namespace std;

t randomizedselect(t a, int start, int end, int i);

int randomizedpartition(t a, int start, int end);

int partitionarray(t a, int start, int end);

void swap(t* a, t* b);

void printarray(t* a, int len);

void randomizedquicksort(t a, int start, int end);

int main() ;

cout << "排序之前的陣列:"; // << endl;

printarray(a, 10);

int pos = 3;

cout << "第" << pos << "小的元素為:" << randomizedselect(a, 0, 9, pos) << endl;

randomizedquicksort(a, 0, 9);

cout << "排序之後的陣列:"; // << endl;

printarray(a, 10);

return 0;}/*

* 查詢第i順位統計量 即第i小的元素

*/t randomizedselect(t a, int start, int end, int i)

int q = randomizedpartition(a, start, end);

int keypos = q - start + 1; //求出這個元素是第幾小的元素,方便與第i小元素比對

//與i相比較

if (keypos == i) else if (keypos > i) else else

} i++;

swap(a + i, a + j);

return i;}/*

* 交換兩個元素

*/void swap(t* a, t* b)

/* * 列印陣列

*/void printarray(t* a, int len)

cout << endl;

}

關於上面演算法為什麼在元素互異的時候時間複雜度為o(n),在《演算法導論-第三版》p121~p122頁有詳細的數學證明,這裡就不再贅述。我們可以簡單的理解,對於快速排序,期望的時間複雜度為o(nlgn),其遞迴在分組之後要對兩邊的兩個集合都進行遞迴;而對於選擇演算法,只需要對其一邊的集合進行遞迴即可,速度上要優於快排,在元素互異的情況下,時間複雜度為o(n)。

演算法導論 中位數和順序統計量

在乙個由n個元素組成的集合中,第i個順序統計量是該集合中第i小的元素。乙個中位數是它所屬集合的 中點元素 當n為奇數時,中位數是唯一的,位於i n 1 2處 當n為偶數時,存在兩個中位數,分別位於i n 2和i n 2 1處。如果不考慮n的奇偶性,中位數總是出現在i n 1 2 處 下中位數 和i ...

演算法導論之中位數和順序統計量(3)

在乙個由n個元素組成的集合中,第i個順序統計量是該集合中的第i小的元素。本章的演算法正是找出乙個互異的元素集合中的第i小的元素。單個的最小值和最大值 在乙個有n個元素的集合中,我們要確定其中最小的元素,必須要進行n 1次比較,正如minmum a 演算法顯示的這樣 minmun a min a i ...

演算法導論 9 中位數和順序統計量

乙個中位數是它所屬集合的中點元素 9.1.1 在乙個有n個元素的集合中,需要做多少次比較才能確定其最小元素呢 9.1.2 同時找到最小值和最大值 實現 同時求解最大值和最小值 123 4567 891011 1213 1415 1617 1819 2021 2223 2425 26 def mini...