給定陣列
a,大小為
n,陣列元素為1到
n的數字,不過有的數字出現了多次,有的數字沒有出現。請給出演算法和程式,統計哪些數字沒有出現,哪些數字出現了多少次。能夠在
o(n)
的時間複雜度,
o(1)
的空間複雜度要求下完成麼?
,其中x。
在此我們提出一種另一種方法,比較貼近我們最初的思維方式:交換元素法。在
o(1)
的空間複雜度要求下,我們只能操作原陣列,不能進行新的大於
o(1)
的空間分配。對原陣列進行操作,我們最容易想到的操作即是交換元素,因為元素的交換出現在了所有的原地排序演算法中。因為原陣列的元素範圍為1到
n,而且陣列長度也為
n,所以乙個很自然的想法是將元素
i放置到位置
i,然後我們就可以統計每個元素出現的次數。根據這個思想我們按照如下方式交換元素:遍歷
n個位置,如果位置
i處不滿足
a[i]=i
,則交換
a[a[i]]
和a[i]
兩個元素。例如,
a[3]=5
,則滿足
a[i] !=i
,我們就將
a[3]
和a[5]
交換,交換的結果是
a[5]=5
,有乙個元素在其應該在的位置。
我們希望通過遍歷
n個位置,通過交換元素的方式使陣列達到如下狀態:
如果元素
i出現一次,它就在位置
i處;如果元素
j出現多次,它除了在位置
j處之外,還佔據某些其他未出現的元素的位置
。例如序列: 7
6535
14我們希望在完成交換之後,達到如下狀態: 1
5345
67 元素
1、3、
4、6、
7都在其正確位置,元素
5除了在位置
5以外,還佔據了元素
2應該出現的位置。
但是不幸的是,我們很快發現上述的遍歷方法不能達到上述目的。按照如上方式遍歷完之後的序列為: 4
1355
67其中,元素4和
1沒有在正確位置。
通過研究發現,出現上述問題的原因在於:經過一次交換之後,雖然
a[i]
處的元素在正確位置,但是
i處的元素可能還不在正確位置。解決的方法很簡單,只要i
處的元素始終能執行交換操作就一直交換,直到不能交換元素為止
。此時的偽**如下:
for i=1:n
while canswap(i) do swap(i);
while
迴圈結束的條件是不能交換,不能交換就說明:要麼
a[i]=i
,要麼a[a[i]]=a[i]
,兩個條件的含義是元素
i已經在正確位置,或者元素
i沒有在正確位置,但是其正確位置處已經放置了元素
i,這時說明
i出現多次。通過將每個位置遍歷多次的方式,我們就可以使陣列元素達到前述的狀態。
正確性證明:我們通過證明按照上述方式交換之後,不存在元素不滿足前述狀態。首先,如果要交換元素,則每一次交換都會將乙個元素放入正確位置,同時不會將之前在正確位置的元素替換掉。這是因為我們在交換元素時必須滿足兩個條件:
a[i]!=i
且a[a[i]]!=a[i]
,第乙個條件使我們不交換已經在正確位置的元素,第二個條件使我們在交換的時候滿足要交換到的位置尚未放入正確的元素。所以只要滿足交換條件,則必定將乙個元素放入正確位置,同時不會破壞之前已經在正確位置的元素。由於我們要將
n個位置遍歷一次,所以會將每個位置不滿足前述狀態的元素都進行交換,所以遍歷完
n個位置之後,所有的元素都滿足前述狀態。
複雜度證明:上述交換操作的複雜度為
o(n)
。雖然是兩層迴圈,但是該操作的複雜度是
o(n)
。事實上,上述操作和
kmp演算法的流程非常相似,所以我們也可以通過平攤分析來證明內層
while
迴圈的平攤代價是
o(1)
。我們通過平攤分析中的聚集分析來證明:如果滿足交換條件,則每次都會使乙個元素處在正確位置,因為總共有
n個元素,所以至多需要
n-1次交換(交換完
n-1個元素,第
n個元素自動滿足)即可使所有的元素處在正確位置,也即
while
迴圈至多執行
o(n)
次,每次的平攤代價是
o(1)
。所以上述交換操作的複雜度為
o(n)。
完成交換之後,下一步的操作是統計每個元素出現的次數,我們可以這樣完成:遍歷一遍陣列,將在正確位置的元素變為
-1,其他位置不變;然後再次遍歷陣列,將大於
0的元素所在的正確位置減
1,同時該位置置零。最後每個位置的絕對值即表示了該位置的元素個數。也即統計元素個數也可以通過
o(n)
的複雜度完成,因而整個演算法的複雜度為
o(n)
。通過**優化,可以在一次遍歷過程中統計每個元素的次數。
我們將兩種不能思路的方式都實現並測試效能,效能差異如下:
圖1 不同的統計方法和排序效能對比(左:小資料;右:大資料)
基於交換的方法我們稱其為
swap
,基於整除的方法我們稱其為
division
,同時為了對比時間,我們將
c++的
sort
方法執行時間也列在圖上。通過對比我們發現兩種不同的統計方法效能接近,而且滿足線性關係,但是基於整除的方法效能稍好,但是它們都比
sort
的效能高。
測試**如下:
#include #include #include #include #include #include using namespace std;
#define max 100000000
int a[max];
int b[max];
int c[max];
void gen_data(int n)
}}void num_count2(int n)
{ for(int i=0;i
動態XtraReports的另一種方法
昨晚一下忘了,昨天在一籌莫展的情況下,有一位好心 小土豆 遠端協助,實操指導了另外一種動態xtrareports方法。因為是他幫我寫的 我就不貼了。簡單說一下方法思路。在xtrareports資料欄內加入乙個gridcontrol1,將其dock設為fill。然後還是在xtrareports的建構函...
SQL 擷取的另一種方法
有時候我們需要將如下結果集中的ids進行分割 ids 1,12,123 1 行受影響 希望得到這樣的結果 112 123 3 行受影響 之前一直用表值函式來擷取,這裡借助master.dbo.spt values表來實現 準備資料 if object id tempdb.a is notnull d...
使索引失效的另一種方法
使索引失效的另一種方法 我們可以使用hints使索引失效,但有時候我們可以使用另外一種方法來更靈活的限制索引的使用。假設我們有表test id number,name varchar 20 在表上建立了索引ix test id,ix test name。在表上的資料有 sql select from...