Python 二分法取两组有序数列中位数

Python 二分法取两组有序数列中位数

  • : 取两组有序数列中位数,并且要求算法的时间复杂度为 O(log(m + n))。
  • 现有两组有序数列 (Python 起始 index 为 0)
    A n = { a 0 , a 1 , a 2 , ⋯   , a n − 1 } ,    ( a 0 ≤ a 1 ≤ ⋯ ≤ a n − 1 ) A_n = \big\{a_0,a_1, a_2, \cdots, a_{n-1}\big\}, \; (a_0 \leq a_1 \leq \cdots \leq a_{n-1}) An={a0,a1,a2,,an1},(a0a1an1)
    B m = { b 0 , b 1 , b 2 , ⋯   , b m − 1 } ,    ( b 0 ≤ b 1 ≤ ⋯ ≤ b m − 1 ) B_m = \big\{b_0,b_1, b_2, \cdots, b_{m-1}\big\}, \; (b_0 \leq b_1 \leq \cdots \leq b_{m-1}) Bm={b0,b1,b2,,bm1},(b0b1bm1)
  • 为了方便表示 n ≤ m n\leq m nm
# 例如  
A_4 = [1,3,5,7]
B_7 = [2,4,6,8,10,12,14]

C n + m = { A n , B m } C_{n+m} = \{A_n,B_m\} Cn+m={An,Bm}
C n + m = { c 0 , c 1 , c 2 , ⋯   , c n + m − 1 } C_{n+m} = \big\{c_0,c_1, c_2, \cdots, c_{n+m-1}\big\} Cn+m={c0,c1,c2,,cn+m1}

  • 中位数:
    • 两数值的中位数 为两数组合并的有序数列 C n + m C_{n+m} Cn+m 的中间数
      • 若为奇,为中间序号为 ( m + n ) / / 2 (m+n)//2 (m+n)//2 的那一个数。
      • 若为偶,为中间序号为 ( m + n ) / / 2 − 1 , ( m + n ) / / 2 (m+n)//2-1,(m+n)//2 (m+n)//21(m+n)//2 的两个数的平均数。
C_11 = A_4+B_7
C_11.sort()
n, m = len(A_4), len(B_7)
print('C_11 = A_4 + B_7: ', C_11)
print(f'中位数: {C_11[(n+m)//2]}, index: {(n+m)//2}')
C_11 = A_4 + B_7:  [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14]
中位数: 6, index: 5
D_18 = C_11+B_7
D_18.sort()
n, m = len(C_11), len(B_7)
print('D_18 = C_11 + B_7: ', D_18)
print(f'中位数: {(D_18[(n+m)//2-1]+D_18[(n+m)//2])/2}, index: {(n+m)//2-1,(n+m)//2}')
D_18 = C_11 + B_7:  [1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 10, 10, 12, 12, 14, 14]
中位数: 6.5, index: (8, 9)
  • 一般计算是给所有数列排序,再进行查找。

  • 为了更快速的计算,需要使用特殊算法来优化它。

  • 主要目的:

    1. 若为奇: 找到 第 k k k 那个数。 (令 k = ( m + n ) / / 2 k = (m+n)//2 k=(m+n)//2)
    2. 若为偶: 找到 第 k , k − 1 k,k-1 kk1 那两个数。
  • 查找 c k c_k ck , 我们可以使用二分法

  • 一般是 把 C C C 分成两份 C 右 , C 左 C_右, C_左 C,C
    C = { C → : { c k , ⋯   , c n + m − 1 } C ← : { c 0 , c 1 , ⋯   , c k − 1 } \begin{aligned}C = \begin{cases}C_{\rightarrow} : \{c_{k}, &\cdots, &c_{n+m-1} \} \\C_{\leftarrow}: \{c_0,c_1, &\cdots, &c_{k-1} \} \end{cases}\end{aligned} C={C:{ck,C:{c0,c1,,,cn+m1}ck1}

    • 然后查找 c k c_{k} ck c k − 1 c_{k-1} ck1
  • 在本题中, 由于 C = A + B C = A + B C=A+B ,虽然 A A A B B B有序数组,但 C C C 在相加后并不是。

print('C_11: ',A_4+B_7)
C_11:  [1, 3, 5, 7, 2, 4, 6, 8, 10, 12, 14]
  • 因为先整合再排序,计算步骤会增多,所以 需要从 A A A B B B 中直接提取 做比较

  • k k k 解题逻辑:

    1. 从两组数组 各取 第 < k / / 2 <k//2 <k//2 的值进行比较
    2. 排除掉较小一边的值
    3. 递归至排除所有 C ← C_\leftarrow C 找到第 k k k
A_4 = [1,3,5,7]
B_7 = [2,4,6,8,10,12,14]
n, m = len(A_4), len(B_7)
k = (n+m)//2
print('寻找 k = ', k)
寻找 k =  5
i, j = (k-1)//2, (k-1)//2
print(f'A_4: {A_4}[i={i}]: {A_4[i]}')
print(f'B_7: {B_7}[j={j}]: {B_7[j]}')
A_4: [1, 3, 5, 7][i=2]: 5
B_7: [2, 4, 6, 8, 10, 12, 14][j=2]: 6
  • a k / / 2 → a i ,    b k / / 2 → b j a_{k//2}\to a_i,\; b_{k//2}\to b_j ak//2ai,bk//2bj
  • a i < b j a_i<b_j ai<bj
  • 可知 a i ∈ C ← a_i \in C_\leftarrow aiC
  • 排除掉 a n ≤ i a_{n\leq i} ani C n e w = { A n [ i + 1 : ] , B m } C_{new} = \{A_{n}[i+1:],B_m\} Cnew={An[i+1:],Bm}
  • 重新查找 第 k − ( i + 1 ) k-(i+1) k(i+1) 个数 在 C n e w = { C i + 1 , C i + 2 , ⋯   , C n + m } 中 C_{new} = \{C_{i+1},C_{i+2},\cdots, C_{n+m}\}中 Cnew={Ci+1,Ci+2,,Cn+m}
ai = i + 1 # A 起始位
k_new = k - ai
print('new k: ', k_new)
new k:  2
i, j = (k_new-1)//2, (k_new-1)//2
print(f'A_4: {A_4}[i={ai+i}]: {A_4[ai+i]}')
print(f'B_7: {B_7}[j={j}]: {B_7[j]}')
A_4: [1, 3, 5, 7][i=3]: 7
B_7: [2, 4, 6, 8, 10, 12, 14][j=0]: 2
bj = j+1
k_new = k-ai-bj
print('new k: ', k_new)
new k:  1
i, j = (k_new-1)//2, (k_new-1)//2
print(f'A_4: {A_4}[i={ai+i}]: {A_4[ai+i]}')
print(f'B_7: {B_7}[j={bj+j}]: {B_7[bj+j]}')
A_4: [1, 3, 5, 7][i=3]: 7
B_7: [2, 4, 6, 8, 10, 12, 14][j=1]: 4
bj = bj+j+ 1
k_new = k-ai-bj
print('new k: ', k_new)
new k:  0
print('Find ', min(A_4[ai],B_7[bj]))
Find  6
  • 输出条件:

    • k = 0 k = 0 k=0 时: 取 较小值
    • A A A B B B 为空时( a i > n a_i>n ai>n): 取不空的k 值
  • 开始编写

def takeKthItem(A:list, B:list,k,ai:int=0, bj:int=0):
    n,m = len(A),len(B)
    k_new = k-ai-bj
    if (n-ai)>(m-bj): # 默认 n<m, 若不是 交换  
        return takeKthItem(B,A,k,bj,ai)
    if ai>=n: # 为空时
        return B[bj+k_new]
    if k_new == 0:
        return min(A[ai],B[bj])
    i,j = min((k_new-1)//2,n-ai-1),min((k_new-1)//2,m-bj-1) # index 不能超过list长度
    if A[ai+i]<B[bj+j]:
        return takeKthItem(A, B, k,ai+i+1,bj)
    else:
        return takeKthItem(A, B, k,ai,bj+j+1)
A = [1,2,3,4,5]
B = [2,4,6,8,10,12,14]
print('take 10th item: ',takeKthItem(A,B,10))
A + B =  [1, 2, 2, 3, 4, 4, 5, 6, 8, 10, 12, 14]
take 10th item:  12
  • 完成
def findMedianSortedList(A:list,B:list)->float:
    def takeKthItem(A:list, B:list,k,ai:int=0, bj:int=0):
        n,m = len(A),len(B)
        k_new = k-ai-bj
        if (n-ai)>(m-bj): # 默认 n<m, 若不是 交换  
            return takeKthItem(B,A,k,bj,ai)
        if ai>=n: # 为空时
            return B[bj+k_new]
        if k_new == 0:
            return min(A[ai],B[bj])
        i,j = min((k_new-1)//2,n-ai-1),min((k_new-1)//2,m-bj-1) # index 不能超过list长度
        if A[ai+i]<B[bj+j]:
            return takeKthItem(A, B, k,ai+i+1,bj)
        else:
            return takeKthItem(A, B, k,ai,bj+j+1)
    n,m = len(A), len(B)
    right,left = (𝑚+𝑛)//2, (𝑚+𝑛)//2-1
    if (n+m)%2: # 如果奇数
        return takeKthItem(A, B, right)
    else: # 如果偶数  
        return (takeKthItem(A, B, right)+takeKthItem(A, B, left))/2
findMedianSortedList(A,B)
4.5
findMedianSortedList([2,3],[1,4,5])
3
A = [i for i in range(1,1000000,2)]
B = [i for i in range(5000,3000000,3)]
def normal(A,B):
    C = A+B
    C.sort()
    length = len(C)
    if length%2:
        return C[length//2]
    else: 
        return (C[length//2]+C[length//2-1])/2
%time normal(A,B)
Wall time: 32.9 ms
901000.0
  • 普通的方法,计算复杂的两组数列需要 32.9 ms。
%time findMedianSortedList(A,B)
Wall time: 0 ns
901000.0
  • 优化过后,二分法计算复杂的两组数列需要 < 1 < 1 <1 ns。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值