簡體   English   中英

查找少於查詢的元素數量的高效算法

[英]Efficient algorithm to find number of elements less than a query

我有兩個未排序的數組ab 對於每個元素a[i]我需要找到元素b[j]的數量,使得b[j] < a[i] 另外, b可能包含不應計入的重復項。 兩個陣列都可能非常大。

我嘗試了(對於單個查詢x

public static void main(String arg[]) {
    int x = 5;
    int b[] = {2, 4, 3, 4, 11, 13, 17};
    Arrays.sort(b);
    int count = 0;
    for(int i = 0; i < b.length; ++i) {
        if(b[i] < x) {
            if(i == 0)
                ++count;
            else {
                // this check avoids counting duplicates
                if(b[i - 1] != b[i])
                    ++count;
            }
        } else {
            break;
        }
    }
    System.out.println(count);
}

我的問題是,查詢中的所有元素時,這種不執行不夠好a迭代。 我該怎么做才能加快速度?

編輯:考慮到以后的評論,我剛開始就提出了一些更新; 將我的第一句話留在底部。

因此,這里的核心方面是:

  1. 您來到這里時遇到了問題X,但進一步詢問告訴我們,您實際上有一些問題要解決。 那是應該避免的事情:來到這里(或自己解決問題!)...那么您應該能夠清楚地描述您已經解決或打算解決的問題。 我不是在這里指指點點; 只是表示您應該努力確保您了解真正的問題是什么。
  2. 正在詢問我們如何處理數據中的重復數字這一事實也可以看出這一點。 先生,先生:這是您的問題。 我們不知道您為什么要計算這些數字; 我們不知道您的數據來自哪里; 以及最終解決方案應如何處理重復的條目。 從這個意義上講,我只是改寫第一段: 必須澄清您的要求。 我們不能用這部分的所有幫助。 您會看到:您僅在第二個數組中提到重復項。 那第一個呢?!

好,關於您的問題。 事實是:實際上,這只是“工作”。 那里沒有魔術。 由於您有兩個非常大的數組,因此對未排序的數據進行操作絕對是不行的。

因此,首先對兩個數組進行排序。

然后,您遍歷第一個數組,在執行此操作的同時,還要查看第二個數組:

int indexWithinB = 0;
int counterForCurrentA = 0; // and actually ALL values from a before
for (int i=0; i<a.length; i++) {
  int currentA = a[i];     
  while (b[indexWithinB] < currentA) {
    if (indexWithinB > 0) { // check required to avoid using 0-1
      if (b[indexWithinB-1] != b[indexWithinB] { // avoid counting duplicates!
        counterForCurrentA++;
      }
    }
    indexWithinB++;
  }
  // while loop ended, this means: b[indexWithinB] == or > currentA
  // this also means: counterForCurrentA ... should have the correct value
}

上面顯然是偽代碼。 它旨在使您繼續前進; 那里很可能有細微的錯誤。 例如,正如安德里亞斯(Andreas)所指出的:還需要對上述內容進行增強以檢查b.length。 但這留給讀者練習。

這就是我所說的“正常工作”的意思:您只需要坐下來,編寫測試用例並完善我的算法草稿,直到它為您完成工作即可。 如果您發現很難一開始就編寫程序,則拿一張紙,放下兩個帶有數字的數組...,然后手動進行計數。

最后提示:我建議編寫大量的單元測試來測試您的算法(這類內容非常適合單元測試); 並確保您在此類測試中擁有所有重要案例。 您想要在使用10 ^ 5元素數組之前100%確保算法正確!

和這里一樣,原始的答案:

簡單地說:迭代和計數是解決此問題的最有效方法。 因此,在上述情況下,不進行排序可能會縮短整體執行時間。

那里的邏輯真的很簡單:為了知道小於x的數字計數,您必須查看所有這些數字。 因此,您必須迭代整個數組(當該數組未排序時)。

因此,給定您的初始聲明,沒有其他事情了:迭代並計數。

當然,如果您必須多次進行計數...可能值得一開始對數據進行排序。 因為這樣您就可以使用二進制搜索 ,並且獲得該計數就可以在不迭代所有數據的情況下尋找工作。

並且:是什么讓您認為迭代具有10 ^ 5個元素的數組是一個問題? 換句話說:您只是擔心潛在的性能問題,還是真正的性能問題? 您會看到,有時可能必須創建填充該數組。 當然,這比簡單的for循環對條目進行計數要花費更多的時間(和資源)。 老實說:除非我們使用的是小型嵌入式設備... 10 ^ 5個元素...甚至在使用稍微陳舊的硬件時也幾乎沒有

最后:當您擔心運行時時 ,簡單的答案是:對輸入數據進行切片,並使用2,4、8 ...線程並行計算每個切片! 但是如前所述:在編寫該代碼之前,我將進行一些性能分析,以確保您確實必須為此花費寶貴的開發時間。 不要解決假設的性能問題; 專注於對您或您的用戶真正重要的內容!

將數組中的每個項目與x共同映射將花費O(n)時間。 對數組進行排序將得到O(n log n),然后可以使用二進制搜索,即O(log n),則總數為O(n log n)。 因此,最有效的方法也是簡單的方法-只需遍歷數組並將每個項目與x進行比較。

public static void main(String arg[] ){
    int b[]={2, 4, 3, 4, 11, 13, 17};
    int x=5;
     int count=0;
     for(int i=0;i<b.length;i++){
         if(b[i]<x){          
             count++;
         }
     }
     System.out.println(count);
}

我建議您考慮使用以下方法,但是僅當b數組具有非負數時,該方法才有效。 即使未對輸入數組( ab )進行排序,該算法也有效。

偽碼

  1. 獲取數組bmax元素。
  2. 創建一個大小為max + 1的新數組c ,並將1放在位置c[b[i]]
  3. 創建一個大小為max + 1的新數組d ,並將其填充如下:

    d[0]=0;
    d[i]=d[i-1] + c[i];

  4. 創建一個大小為n的新數組e ,並將其填充如下:

    if(a[i] > max) then e[i] = last(d)
    otherwise e[i]=d[a[i]-1];

e數組表示解決方案:它在第i個位置包含b數組的編號計數器,其數量低於數組a的第i個元素。 此示例應比偽代碼更具解釋性:

a = [5, 1, 4, 8, 17, 12, 22]
b = [2, 4, 3, 4, 11, 13, 17]
c = [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1]
d = [0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, 5, 5, 6]
e = [3, 0, 2, 3, 5, 4, 6]

復雜

Steps 1, 2 and 4 are O(n).
Step 3 is O(max(b))

如果輸入數組b僅包含“短”數字(max(b)的大小為n的相同順序),則算法以O(n)執行。 可以對算法進行優化,以創建大小為max-min+1的數組,並為小於min(b) a數組的所有元素考慮計數器0

一個簡單的java實現:

int a[] = {5, 1, 4, 8, 17, 12, 22};
int b[] = {2, 4, 3, 4, 11, 13, 17};
int max = Arrays.stream(b).max().getAsInt();
int c[] = new int[max+1];
int d[] = new int[max+1];
int e[] = new int[a.length];
for(int i=0;i<b.length;i++){
    c[b[i]]=1;
}
for(int i=1;i<c.length;i++){
    d[i] = d[i-1] + c[i];
}
for (int i = 0; i<a.length;i++){
    e[i]=(a[i]>max)?d[d.length-1]:d[a[i]-1];
}
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
System.out.println(Arrays.toString(c));
System.out.println(Arrays.toString(d));
System.out.println(Arrays.toString(e));

對於更大的排序集,我們需要使用分而治之原理來加快搜索速度。這是我的解決方案,具有O(logn)時間復雜度和O(n)空間復雜度。

public static void main(String arg[]) {
    int x = 5;
    int b[] = {2, 4, 3, 4, 11, 13, 17};
    int high = b.length - 1;
    int low = 0;

    while (high >= low) {
      int mid = (high + low) / 2;
        if (b[mid] < x)
          low = mid + 1;
        else
          high = mid - 1;
    }
  System.out.println(low);

}

這應該是一個可能的解決方案。 “昂貴”的任務是對列表進行排序。 Bost列表必須在for循環之前排序。 確保使用快速機制執行排序。 解釋說,對數組/數組列表進行排序是一項非常昂貴的操作,尤其是當您必須對許多值進行排序時。

public static void main(String[] args) throws IOException {
    // int x = 5;
    int a[] = { 1, 2, 3, 4, 5 };
    int b[] = { 2, 4, 3, 4, 11, 13, 17 };
    List<Integer> listA = new LinkedList<>();
    for (int i : a) {
        listA.add(i);
    }
    List<Integer> listB = new LinkedList<>();
    for (int i : b) {
        listB.add(i);
    }
    Collections.sort(listA);
    Collections.sort(listB);
    int smallerValues = 0;
    int lastValue = 0;
    Iterator<Integer> iterator = listB.iterator();
    int nextValue = iterator.next();
    for (Integer x : listA) {
        while (nextValue < x && iterator.hasNext()) {
            lastValue = nextValue;
            nextValue = iterator.next();
            if (nextValue > lastValue) {
                smallerValues++;
            }
        }
        System.out.println(x + " - " + smallerValues);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM