簡體   English   中英

在這個特定示例中,如何將 O(n^2) 算法轉換為 O(n)?

[英]How do I convert an O(n^2) algorithm to O(n) in this particular example?

我正在嘗試解決我為練習技能而進行的編碼練習。 它涉及為籃球運動員提取一些.JSON 數據。 我的程序必須找到所有可能的玩家對,它們的總和等於給定的 integer 輸入。

這是我設計的代碼:

import json
import requests

def to_number(A):
    
    B = int(A)
    
    return B
        

def search(Number):
    response = requests.get("https://mach-eight.uc.r.appspot.com/")

    data = json.loads(response.text)
    PLAYERS = data["values"]

    answer_list = []

    for player1 in PLAYERS:
    
        raw_height = player1['h_in']
        height_1 = to_number(raw_height)
    
        PLAYERS.remove(player1)
        for player2 in PLAYERS:
        
            raw_height = player2['h_in']
            height_2 = to_number(raw_height)
        
            result = height_1 + height_2
        
            if result == Number:
                par = (player1['first_name'] + ' ' + player1['last_name'] + ' & ' + 
                       player2['first_name'] + ' ' + player2['last_name'])
            
                answer_list.append(par)
        
    return answer_list

def executer():
    Number = int(input("insert your integer: "))
    result = search(Number)
    
    return result
    
if __name__=="__main__":
    
    result = executer()
    stop_here = len(result)
    
    while stop_here == 0:
    
        print("No matches found, please try with a new number \n\n")
    
        result = executer()
        stop_here = len(result)
        
    print(result)

到目前為止,它確實完成了查找對的目標,但是以嵌套 for 循環為代價,我需要想出一種減少計算時間的方法,例如,作為 O(n) 算法。

到目前為止,我嘗試使用 itertools package 和排列 function 來制作沒有循環的配對,但我很快意識到這只會讓它變慢。

另一方面,我考慮將玩家的每個高度減去 integer 輸入,這將返回與初始高度配對的唯一可能高度:

圖形示例

這種方法會直接引導我找到唯一可能的配對,對吧? 但是我在這之后該怎么做有問題。 我不確定如何僅通過一個循環來確定與操作結果高度相對應的玩家。

如果你能幫助我解開這個難題,我將非常感激。 同樣,如果您能想到另一種方法,我全是耳朵和眼睛。

首先:因為所需的 output 必須包含與所需高度總和匹配的所有對,所以任何算法的最壞情況復雜度至少為 O(²)。 以所有玩家的身高為 70 的情況為例,並且 function 的參數是 140,那么很明顯您必須 output 所有可能的對。 其中有 (-1)/2 個,即 O(²)。 由於算法必須產生那么多對,因此它至少要執行那么多步驟,因此它至少是 O(²)。 我在這里忽略了人名中的字符數。 我將假設這些名稱最多有 100 個字符,因此這不會影響時間復雜度。

但是,在查看平均最佳情況時間復雜度時,您的算法並不是最優的,因為您的算法仍然是 O(²),而可以在 O() 的最佳情況時間復雜度下完成:

您可以使用以高度為鍵的字典,並將具有相同高度的人員列表(他們的全名)作為值。

這是您的 function 的外觀:

def search(total):
    response = requests.get("https://mach-eight.uc.r.appspot.com/")

    data = json.loads(response.text)
    players = data["values"]

    d = defaultdict(list)
    for player in players:
        d[int(player['h_in'])].append(player['first_name'] + " " + player['last_name'])

    return [player + " & " + other
        for height in d
            if total - height in d
                for other in d[total - height]
                    for player in d[height]
                        if player < other
    ]

因此,例如,如果輸入沒有高度相同的玩家,那么這個算法將在線性時間內完成這項工作。

我將忽略您帖子中所有多余的代碼; 問題是,給定一個整數列表,如何找到添加到給定目標總和的對。

執行此操作的直接方法是對列表進行排序,即O(N log N)時間。 為了說明,讓我們考慮列表s = [5, 6, 8, 13, 14, 15]和目標總和 21。讓lo, hi = 0, len(s)指向結束元素的指針。

現在我們檢查總和...

total = s[lo] + s[hi]
if total == target:
    # print a found pair; move both pointers in one spot.
elif total < target:
    # sum is too small
    lo += 1
else:
    hi -= 1

lo < hi時重復此操作


還有另一種更好的方法:將高度放入一個集合中,然后簡單地使用in

s_set = set(s)
for height in s_set:
    if (target - height) in s_set:
        # print a pair

現在這將找到每對兩次; 我將把過濾留給你。 這也假設高度是唯一的; 如果您需要識別所有成對的玩家,而不僅僅是高度,那么您應該使用以高度為鍵的字典,其值作為玩家列表 - 但這不再是O(N) ,除非您有一個常數限制任何給定身高的球員數量。

為什么不創建一組“互補高度”,例如new Set(player1.map(height => target - height))

一旦你有了這個集合,你就可以檢查 player2 的高度,如果你找到一個條目,這意味着你剛剛找到了一對總和達到目標的對。

你可以看看這個問題https://leetcode.com/problems/two-sum/

我會做什么:

Sort the input list by height

for each player, 
   searchValue = player.height - requestedTotalHeight
   Binary search: find all matches in the sorted list with height searchValue
   add match to the output

這應該給你一個 N log N 運行時,不計算排序。 但是,由於本世紀 CPU 的工作方式(分支預測),即使使用當前的 N^2 方法,首先對列表進行排序也會給您更快的響應。

或者,您可以在導入數據時跳過排序並構建搜索樹。 如果數據足夠隨機,則樹不應該太不平衡。

一些提示:

首先,您必須擺脫嵌套循環。 這本質上是 O(n²)。 是的,您可以使用描述的聰明的 O(nlogn) 搜索/排序算法來提高運行時間。

實現 O(n) 的解決方案是使用支持您的數據結構。 hashmap 可以幫助您在 O(1) 內訪問。 使用 O(n) 填充它。 在 python 中,hashmap 是一個字典。 用它:

my_list = [ 2, 3, 5, 2, 3, 6]
my_target = 5

print(f"Find pairs in {my_list} that sum up to {my_target}")

d = {}  # use a dictionary

result = []

for i in range(len(my_list)):

    if my_target - my_list[i] in d.keys():
        result.append((my_list[d[my_target - my_list[i]]], my_list[i]))
    d[my_list[i]] = i

print(f"results: {result}")

研究它,理解它,讓它適應你的問題。

更新

我花時間尋找樂趣和利潤,並在 O(n) 中為您編寫了解決方案。 它基於上面顯示的算法。 它利用了 O(1) 中的 hashmap 訪問這一事實。

import json
import requests


def search(target_sum):
    response = requests.get("https://mach-eight.uc.r.appspot.com/")
    data = json.loads(response.text)
    players = data["values"]

    idx_map = {}  # will hold the height as key and it's index in the player list as value
    answer_list = []

    for idx, p in enumerate(players):
        h_in = int(p['h_in'])
        if target_sum - h_in in idx_map.keys():  # the complementary value is in the idx_map!
            p1 = players[idx_map[target_sum - h_in]]  # the according player is resolved
            p2 = players[idx]
            answer_list.append((p1, p2))  # save 'em as a fine tuple

        idx_map[h_in] = idx  # save the idx of the player at the

    return answer_list


def get_person_string(player):
    return f"{player['first_name']} {player['first_name']} ({player['h_in']})"


def main():
    while 1:
        try:
            target_sum = int(input("Insert target sum: "))
            result = search(target_sum)
            for pair in result:
                print(f"Player {get_person_string(pair[0])} and Player {get_person_string(pair[1])} "
                      f"together are {target_sum} inches tall.")
        except ValueError:
            break


if __name__ == "__main__":
    main()

Output:

Insert target sum: 90
Insert target sum: 130
Insert target sum: 140
Player Speedy Speedy (71) and Player Nate Nate (69) together are 140 inches tall.
Player Brevin Brevin (70) and Player Mike Mike (70) together are 140 inches tall.
Insert target sum: 180
Insert target sum: 176
Player Roy Roy (86) and Player Yao Yao (90) together are 176 inches tall.
Insert target sum: 175
Player Robert Robert (85) and Player Yao Yao (90) together are 175 inches tall.
Insert target sum: 174
Player Jason Jason (84) and Player Yao Yao (90) together are 174 inches tall.
Player Yao Yao (90) and Player Yi Yi (84) together are 174 inches tall.
Insert target sum: x

** 更新 **

這里的算法不適用於所有情況。 邊緣情況:如果每個玩家的身高都相同,並且我們正在尋找身高*2,那么我們將需要玩家集合的笛卡爾積,比如 P。

所以P² = P x P = { (a, a') | a, a' in P } P² = P x P = { (a, a') | a, a' in P }中。

我的算法只在那種邊緣情況下獲得連續的玩家元素。 可能有一種方法可以通過使用索引的字典作為索引 map 的值來改善這一點,但同樣的問題仍然存在:在最壞的情況下(主要是關於什么是漸近分析),您將需要遍歷所有元素對於所有元素,導致 O(n²) 最壞情況時間復雜度。 感謝 trincot 將我帶到這里。

首先,您的代碼中有一個錯誤。 您在 PLAYERS 上有一個 for 循環,並且在使用.remove()方法時正在修改您正在循環的列表。 這會導致意想不到的結果(基本上,它會在不應該的情況下跳過原始列表中的一些條目)。 此 StackOverflow 帖子中的更多詳細信息。

現在回答你的問題。 正如其他人所提到的,以高度值作為鍵的字典是 go 的好方法。 但是,我相當確定時間復雜度仍然是 O(n^2)。 就像@trincot 說的那樣,如果您需要 output 匹配對列表,則無法改進。 這歸結為采用兩個列表的笛卡爾積(即下面代碼中的itertools.product調用),其復雜度為 O(n^2)。 請參閱此帖子以獲取更多說明。

擊敗 O(n^2) 的唯一方法是嘗試返回不同的值。 例如,如果您只需要計算有多少對總和為 N 而不是返回對列表,那么該算法的運行速度可能比 O(n^2) 快。

無論如何,這是字典方法的實現。

import json
import requests
import itertools


def get_player_data() -> list:
    response = requests.get("https://mach-eight.uc.r.appspot.com/")
    return json.loads(response.text)["values"]


def build_height_index(player_data: list) -> dict:
    height_index = {}
    for player in player_data:
        name = f"{player['first_name']} {player['last_name']}"
        h = int(player["h_in"])
        height_index.setdefault(h, []).append(name)
    return height_index


def search(player_data: list, n: int) -> list:
    height_index = build_height_index(player_data)
    pairs = []
    for h in height_index:
        # avoid double counting pairs
        if h > n / 2:
            continue

        player_list = height_index.get(h, [])
        if h == n / 2:
            # the players in this list are pairs with each other
            new_pairs = [*itertools.combinations(player_list, r=2)]

        else:
            # find the list of players with the matching height
            matching_height = n - h
            matching_list = height_index.get(matching_height, [])
            new_pairs = [*itertools.product(player_list, matching_list)]

        pairs += new_pairs

    return pairs


def format_output_pairs(pairs: list) -> list:
    """
    This will format the output in a string like you had it, but
    otherwise it's unnecessary.
    """
    return [" & ".join(sorted(x)) for x in pairs]


def find_pairs(n: int) -> list:
    player_data = get_player_data()
    pairs = search(player_data, n)
    return format_output_pairs(pairs)


### Example output:
# > find_pairs(140)
# ['Brevin Knight & Mike Wilks',
#  'Chucky Atkins & Nate Robinson',
#  'Nate Robinson & Speedy Claxton']

暫無
暫無

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

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