【算法】树状数组

树状数组

树状数组也是高级数据结构之一

树状数组,利用数的二进制特征进行检索的一种树状结构

是一种真正的高级数据结构:二分思想(O(logn)))、二叉树、位运算、前缀和

树状数组的基本应用:

  1. 修改元素add(k,x): 把a[k]加上x。

  2. 求和:

    sum(x) = a1+ … +ax

    区间和ai+…+aj=sum(j)-sum(i-1)

求区间和(只查询)

image-20230308143052014

代码:

A = [0,4,5,6,7,8,9,10,11,12,13]
sum = [0]*20
sum[1] = a[1]
for i in range(2,11):      #计算前缀和
    sum[i]=a[i]+sum[i-1]
print(sum)
for i in range(1,11):      #用前缀和反推计算数组a[]:
    print(sum[i]-sum[i-1],end=' ')
    
print("[5,8]=",sum[8]-sum[4]) #查询区间和,例如查询[5,8]

那如果数列是动态的呢?

修改元素效率不发生变化

但对于区间和: sum(i)-sum(i-1) 复杂度: O(n)

效率很低

那么就可以引入树状数组的概念

定义

动态修改、求区间和:用树状数组

先展示代码:

def lowbit(x):
    return x&-x
def add(x, d):
    while(x<n):
        tree[x] += d
        x += lowbit(x)
def sum(x):
    ans = 0
    whlie(x>0):
        ans += tree[x]
        x -= lowbit(x)
    return ans

树状数组的结构与二叉树相似

image-20230308144002645

右边的每个点的值等于其子树的值的和

代码中的lowbit的功能:找到x的二进制数的最后一个1

从lowbit推出tree[]数组,所有的计算都基于tree[]

另m=lowbit(x)

定义tree[x]:把a[x]和他前面共m个数 相加

例:lowbit(6)=2,有tree[6] = a[5]+a[6]

用图来表示:

image-20230308152830649

有了tree[],就可以进行基于tree[]的计算

基于tree[]的计算

求和

那以上关系是如何得到的?借助lowbit(x)

image-20230308153100735

数组更新

image-20230308153210159

树状数组处理逆序对问题

对于逆序对问题(对数组中没两个元素,前边的大于后边的就代表是一对逆序对)

题目样式:

image-20230313183518446

老样子,先暴力:

n = int(input())
a = list(map(int,input().split()))  
res=0
for i in range(n):
    for j in range(i+1,n):
        if a[j]<a[i]:
            res +=1
print(res)

模拟:先检查第一个数a1,把后面所有数跟它比较,如果发现有一个比a1小,就是一个逆序对;再检查第二个数,第三个数…;直到最后一个数

复杂度:O(N2)

经观察,暴力法的执行过程和交换排序很想,可以联想到归并排序,是否可以处理?

def merge(L, mid, R):
    global res
    i = L;   j = mid+1;    t=0
    while(i <= mid and j <= R):
        if(a[i] > a[j]):
            b[t] = a[j]; t+=1;  j+=1
            res = res + mid-i+1         #记录逆序对数量          
        else:   b[t] = a[i];  t+=1   i+=1
#一个子序列中的数都处理完了,另一个还没有,把剩下的复制过来
    while i <= mid:  b[t]=a[i];  t+=1;  i+=1
    while j <= R:    b[t]=a[j];  t+=1;  j+=1
    for i in range(t): a[L+i] = b[i]#把排好序的b[]复制回a[]

def merge_sort(L, R):
    if L<R:  
        mid = (L + R) // 2    #平分成两个子序列
        merge_sort(L, mid)
        merge_sort(mid+1, R)
        merge(L, mid, R)

n = int(input())
a = list(map(int,input().split())) 
b = [0]*n
res = 0
merge_sort(0, n-1)
print(res)

相比普通的归并排序,这里只多了一个res = res + mid - i +1 用于记录逆序对数量

以上两种是基础思想,用树状数组解决逆序对问题是树状数组的巧妙应用

image-20230313184550331

倒序:

image-20230313184707737

正序:

image-20230313184720590

n = int(input())
a = list(map(int,input().split()))
b = sorted(a)
for i in range(n):  a[a.index(b[i])] = i+1
tree = [0] * (n+1)
def lowbit(x):
    return x & -x
def update(x, d):
    while(x < n):
        tree[x] += d
        x += lowbit(x)
def sum(x):
    ans = 0
    while(x > 0):
        ans += tree[x]
        x -= lowbit(x)
    return ans
res = 0
a.insert(0,0)  #在最前面加个0
for i in range(len(a)-1, 0, -1):
    update(a[i], 1)
    res += sum(a[i]-1)

print(res)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜小田

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值