有序數組取交集(搜索算法)的算法優化


  有序數組取交集是一個非常常見的問題,也是搜索引擎的核心算法之一,然而,當搜索引擎的數據量很大時,倒排索引會很長,即每個有序數組的長度會很大。按照常見的算法求兩個有序數組交集(同時遍歷兩個數組),時間復雜度為0(n+m)[n和m為兩個數組的長度];如果求多個有序數組的交集,時間復雜度為多個數組長度之和,顯然這樣的算法代價是很高的,下面介紹一種復雜度更低的算法——分治查找算法。

  首先,對於兩個數組求交集:有序數組M(長度為m)和有序數組N(長度為n)

  分治查找算法的最壞情況下時間復雜度為m+n,最優的情況下為logm * logn

一、算法過程描述如下:

  分治的思想,步驟如下:

    ①找到較短的數組(比如M),在N中用二分查找M[mid](mid=m/2)

    ②如果m<=0或者n<=0,算法結束

    ③如果找到M[mid]==N[x]:

      把M[mid]放到交集中,

      另M=M[0,mid-1],N=N[0,x-1],重復該算法;

      另M=M[mid+1,m],N=N[x+1,n],重復該算法;

    ④如果在N中沒有找到M[mid],即存在x,使得N[x]<M[mid]<N[x+1]:

      另M=M[0,mid-1],N=N[0,x],重復該算法;

      另M=M[mid+1,m],N=N[x+1,n],重復該算法;

二、時間復雜度分析

  顯然,該分治查找的算法時間復雜度取決於x的值,由於下一次的算法耗時取決於本次x的值,即

    下一次的耗時 = 在N[0,x]查找M[1M/4]的耗時 + 在N[x+1,n]查找M[3M/4]的耗時

  用數學公式表示一下,即

    time(next) = logx + log(n-x) = log(x(n-x))

  可以得出

    ①當x = n/2時,算法每一步的耗時高,即最差的情況

    ②當x->0或者x->n時,每一步耗時最低,即最好情況

  接下來分別對最差情況和最好情況分別進行計算:

    ①最差情況復雜度整理:

      

      經化簡可得:

      

      因為m<n,所以當m=Xn時(經計算X為某個比較接近1的常數),上式取得最大值

      但上式最大值仍是n的X1倍(經計算,這個X1為比1大一點點的常數),也就是說,在最壞的情況下(每一步都有x=n/2),改算法時間復雜度為m+n

    ②最優情況復雜度整理:

      最優情況為logm * logn,即有logm個logn復雜度的二分查找

    ③平均情況:

      該算法的平均復雜度比較復雜,

      簡單來說,受每一步x的取值影響

      展開來說,受m和n的差值、兩個數組的內數值的取值范圍、兩個數組的數值概率分布影響

      雖然說平均情況比較復雜,但是可以確定的是:

        1)m和n相差越大

        2)兩個數組取值范圍交叉越小

        3)兩個數組的數值概率分布相差越大

      該算法平均復雜度越低

三、推廣到多數組取交集(搜索引擎算法)

  如果是多個有序數組求交集(海量數據的搜索引擎),可以

    ①先用該算法求出其中長度較小的兩個數組的交集A(此時數組A長度應該會更小)

    ②再用小長度的數組A和接下來最小的數組取交集,得到更小的數組A1

    ③另A=A1,重復②,直到所有數組都完成

  用增大m和n的差值的辦法,可以大大減小算法計算量,提高效率

 四、抽樣檢測

  由於難以計算,可以用以下程序來進行抽樣檢測來比較兩種算法的性能,我做過很多次測試,實驗結果符合上述結論

  注:以下測試程序是用的隨機數,隨機數服從均勻分布,所以該測試程序可以改變1)和2)兩個條件,不能改變條件3)
     還有,沒有考慮到數組內數字重復出現的情況,會導致結果又一點點誤差,並不影響整個結論
---------------------------代碼分割線-----------------

#include <iostream>
#include <vector>
#include <time.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

vector<int> ret;//交集數組
int times;

//遞歸二分查找
//nextDivideEnde 為下次分治第一部分的終點
//nextDivideStart 為下次分治第二部分的起點
void BinSearch(const vector<int> Arry, const int startIndex, const int endIndex, const int key, int &nextDivideEnd, int &nextDivideStart)
{
times += 1;
if(startIndex <= endIndex)
{
times += 1;
int mid = startIndex + (endIndex - startIndex) / 2;
if(key == Arry[mid])
{
ret.push_back(key);
nextDivideEnd = mid;
nextDivideStart = mid;
}
else if(key < Arry[mid])
{
BinSearch(Arry, startIndex, mid-1, key, nextDivideEnd, nextDivideStart);
}
else if(key > Arry[mid])
{
BinSearch(Arry, mid+1, endIndex, key, nextDivideEnd, nextDivideStart);
}
}
else
{
nextDivideStart = startIndex;
nextDivideEnd = endIndex;
}
}

int divideSearch(const vector<int> ArryA, const int AstartIndex, const int AendIndex,
const vector<int> ArryB, const int BstartIndex, const int BendIndex)
{
times += 1;
//BinSearch in the longer one to reduce the complexity
if((AendIndex - AstartIndex) <= (BendIndex - BstartIndex))
{
times += 1;
if(AstartIndex <= AendIndex)
{
int mid = AstartIndex + (AendIndex - AstartIndex)/2;
int nextDivideEnd;
int nextDivideStart;
BinSearch(ArryB, BstartIndex, BendIndex, ArryA[mid], nextDivideEnd, nextDivideStart);
divideSearch(ArryA, AstartIndex, mid-1, ArryB, BstartIndex, nextDivideEnd);
divideSearch(ArryA, mid+1, AendIndex, ArryB, nextDivideStart, BendIndex);
}
}
else//arryB is shorter, then BinSearch mid of B in A
{
times += 1;
if(BstartIndex <= BendIndex)
{
int mid = BstartIndex + (BendIndex - BstartIndex)/2;
int nextDivideEnd;
int nextDivideStart;
BinSearch(ArryA, AstartIndex, AendIndex, ArryB[mid], nextDivideEnd, nextDivideStart);
divideSearch(ArryB, BstartIndex, mid-1, ArryA, AstartIndex, nextDivideEnd);
divideSearch(ArryB, mid+1, BendIndex, ArryA, nextDivideStart, AendIndex);
}
}
}
//to produce the random number in thr range of m and n
int Random(const int m, const int n)
{
int pos, dis;
if(m == n)
{
return m;
}
else if(m < n)
{
pos = m;
dis = n - m + 1;
return rand() % dis + pos;
}
else
{
pos = n;
dis = m - n + 1;
return rand() % dis + pos;
}
}
//common search
int commonSearch(const vector<int> &arryA, const int countA, const vector<int> &arryB, const int countB)
{
int i=0,j=0;
while(i < countA && j < countB)//2 judge: countA and countB
{
times += 2;//2 judge, so +2
if(arryA[i] == arryB[j])
{
ret.push_back(arryA[i]);
times++;
i++;
j++;
}
else if(arryA[i] > arryB[j])
{
times++;
j++;
}
else
i++;
}
}

int main(int argc,char* argv[])
{
if(argc != 7)
{
cout << "please input the countA, minA, maxA, countB, minB, maxB!" << endl;
return -1;
}
vector<int> A;
vector<int> B;
int minA, maxA, minB, maxB;
int countA, countB;
countA = atoi(argv[1]);
minA = atoi(argv[2]);
maxA = atoi(argv[3]);
countB = atoi(argv[4]);
minB = atoi(argv[5]);
maxB = atoi(argv[6]);

srand((int)time(NULL));
for(int i = 0; i < countA; i++)
A.push_back(Random(minA, maxA));
for(int j = 0; j < countB; j++)
B.push_back(Random(minB, maxB));
sort(A.begin(), A.end());
sort(B.begin(), B.end());
divideSearch(A, 0, countA, B, 0, countB);
cout << "ret.size(): " << ret.size() << endl;
cout << "divide search use" << times << " times comparision" << endl;

//now for the common search
ret.clear();//clear() 只是清空vector 並不回收內存 perfect
times = 0;
commonSearch(A, countA, B, countB);
cout << "ret.size(): " << ret.size() << endl;
cout << "common search use" << times << " times comparision" << endl;
return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM