美文网首页算法
[数据结构与算法-iOS 实现]二叉堆原理实现及top k 问题

[数据结构与算法-iOS 实现]二叉堆原理实现及top k 问题

作者: 孙掌门 | 来源:发表于2020-06-30 20:48 被阅读0次

二叉堆 demo

二叉堆其实就是一颗完全二叉树,所以又可以叫做完全二叉堆

二叉堆的底层可以用数组来实现

对于二叉堆的索引 i :

1. 如果 i= 0 , 那么为根节点
2. 如果 i > 0 , 那么他的父节点的编号为 floor((i - 1) / 2)
3. 如果 2i + 1 <= n(总节点个数) - 1 ,他的左子节点的编号为 2i + 1
4. 如果 2i+ 1 > n -1 ,他无左子节点
5. 如果 2i+ 2 <= n -1,他的右子节点的编号为 2i + 2
6. 如果2i + 2 > n -1 ,他无右子节点

添加元素

如果是大顶堆,默认先将元素添加到数组的尾部,然后再拿这个元素和自己的父节点作比较,如果自己比父节点大,则交换位置,循环执行此操作,如果当前节点比父节点小,或者没有父节点,则退出循环,这个过程叫做上滤,时间复杂度为logn。

删除元素

1. 将最后一个节点替换根节点
2. 删除最后一个节点
3. 循环执行以下操作,下滤(sift down),事件复杂度O(logn),
  3.1 如果node<子节点,那么就与最大的子节点交换位置
  3.2 如果node>=子节点或者node没有子节点,退出循环

top k 问题

//top k 问题
// 找到一组数中,最大的前几个
- (void)testFindMax{
    NSArray *arr = @[@1,@2,@3,@4,@5,@6,@7,@8,@9,@10];
    int k =5;
    SCXBinaryHeap *heap = [[SCXBinaryHeap alloc] initWithDelegate:self];;

    // 找到最大的前五个,现将前五个额元素,放入小顶堆中,然后之后的每一个元素,如果比堆顶大,那么就执行replace,这样做的目的其实就是,默认先将前五个作为最大,然后如果发现有大的,就一次替换,因为堆顶是最小的,所以到最后,小顶堆的元素一定是最后要找的,时间复杂度为nlogk,k为要寻找的个数
    // 这里是找到最小的前五个数
    for (int i = 0; i < arr.count; i ++) {
        if (heap.size < k) {
            [heap add:arr[i]];
        } else if ([self compareA:heap.getTopObject valueB:arr[i]]){
            [heap replaceTopObject:arr[i]];
        }
    }
    NSLog(@"%@",heap);
}

代码示例


//
//  SCXBinaryHeap.m
//  TestArithmetic
//
//  Created by 孙承秀 on 2020/6/30.
//  Copyright © 2020 孙承秀. All rights reserved.
//

#import "SCXBinaryHeap.h"
#define DefaultCapacity 10
@interface SCXBinaryHeap()
{
    NSInteger _size;
    NSInteger _capacity;
}
@property(nonatomic,weak)id <SCXBinaryHeapDelegate>delegate;
@property(nonatomic,strong)NSMutableArray *array;
@end
@implementation SCXBinaryHeap
-(instancetype)initWithDelegate:(id<SCXBinaryHeapDelegate>)delegate{
    if (self = [super init]) {
        self.delegate = delegate;
        self.array = [NSMutableArray arrayWithCapacity:DefaultCapacity];
        _capacity = DefaultCapacity;
    }
    return self;
}
- (instancetype)initWithArray:(NSArray *)arr delegate:(id<SCXBinaryHeapDelegate>)delegate{
    if (self = [super init]) {
        self.delegate = delegate;
        if (!arr || arr.count == 0) {
            _array = [NSMutableArray arrayWithCapacity:DefaultCapacity];
            _size = 0;
        } else {
            NSInteger count = arr.count;
            _capacity = count;
            _size = count;
            self.array = arr.mutableCopy;
            [self heapify];
        }
    }
    return self;
}
- (void)heapify{
    // 自下而上下滤
    for (NSInteger i = (_size >> 1) - 1; i >= 0; i --) {
        [self siftDown:i];
    }
}
- (void)add:(nonnull id)object {
    if ([self isNULL:object]) {
        return;
    }
    // 动态扩容
    [self ensureCapcity:_size + 1];
    // 将元素放在最后,然后将当前size+1
    _array[_size++] = object;
    // 将当前最后一个元素上滤
    [self siftUp:_size-1];
}
-(id)removeTopObject{
    if ([self isEmpty]) {
        return nil;
    }
    NSInteger lastIndex = --_size;
    id obj = _array[0];
    _array[0] = _array[lastIndex];
    [_array removeObjectAtIndex:lastIndex];
    [self siftDown:0];
    return obj;
}
- (id)replaceTopObject:(id)object{
    if ([self isNULL:object]) {
        return nil;
    }
    id obj = nil;
    if (_size == 0) {
        _size ++;
        _array[0] = object;
    } else {
        obj = _array[0];
        _array[0] = object;
        [self siftDown:0];
    }
    return obj;
}
-(NSInteger)size{
    return _size;
}
-(BOOL)isEmpty{
    return _size == 0;
}

- (void)clear {
    [_array removeAllObjects];
    _size = 0;
}

- (nonnull id)getTopObject {
    return _array.firstObject;
}

// 上滤
- (void)siftDown:(NSInteger)index{
    //第一个叶子节点的所以就是非叶子节点的数量,因为为完全二叉树,所以,要么没有左右子节点,要么只有左节点,不可能出现只有右子节点的情况
    // index < 第一个叶子节点的索引,这样就能保证他能和有子节点的进行交换
    // 必须保证index 位置为非叶子节点,因为这样可以找到左节点,或者左右节点,进行交换
    // 非叶子节点的数量为 二叉树节点数量除以二
    
    id obj = _array[index];
    // 第一个非叶子节点的索引
    NSInteger half = _size >> 1;
    while (index < half) {
        // 要么只有左子节点
        // 要么右左右子节点
        // 左子节点的索引为 2i +1 ,右子节点的索引为 2i+2
        NSInteger leftIndex = (index << 1) + 1;
        id leftObjf = _array[leftIndex];
        NSInteger rightIndex = leftIndex +1;
        id rightObj = _array[rightIndex];
        
        // 选出最大值
        id maxObj = leftObjf;
        NSInteger maxIndex = leftIndex;
        
        if (rightIndex < _size && [self.delegate compareA:rightObj valueB:leftObjf]) {
            // 右节点比左节点大
            maxObj = rightObj;
            maxIndex = rightIndex;
        }
        
        // 选出左右最大的节点和index之后,和当前节点进行比较
        if ([self.delegate compareA:obj valueB:maxObj]) {
            // 如果当前节点比左右子节点中最大的那一个都打大,就退出不用交换了
            break;
        }
        // 如果当前节点比左右节点中的其中一个小,那么将当前位置,赋值为最大值,将最大值一次上滤,然后自己下沉,记住位置
        _array[index] = maxObj;
        index = maxIndex;
    }
    _array[index] = obj;
}
- (void)siftUp:(NSInteger)index{
    // 取出当前 index 的元素
    id currentValue = _array[index];
    // 循环上滤
    while (index > 0) {
        // 父节点的索引
        NSInteger parentIndex = (index -1) >> 1;
        // 父节点的值
        id parentObj = _array[parentIndex];
        // 比较
        if (![self.delegate compareA:currentValue valueB:parentObj] ) {
            // 如果当前的值比父节点的值小,说明符合大顶堆的要求,直接退出
            break;;
        }
        // 如果当前的值比父节点大,那么交换位置
        _array[index] = parentObj;
        index = parentIndex;
    }
    _array[index] = currentValue;
}
/// 动态扩容
- (void)ensureCapcity:(NSInteger)capcity{
    NSInteger oldCapcity = _capacity;
    if (oldCapcity >= capcity) {
        return;
    }
    NSInteger newCapcity = oldCapcity + (oldCapcity >> 1);
    [self resMemory:newCapcity];
}
- (void)resMemory:(NSInteger)newCapcity{
    NSInteger oldCapcity = _capacity;
    NSMutableArray *newArr = [[NSMutableArray alloc] initWithCapacity:newCapcity];
    for (int i = 0 ; i < [self size]; i ++ ) {
        newArr[i] = _array[i];
    }
    _capacity = newCapcity;
    _array = newArr.mutableCopy;
}
- (BOOL)isNULL:(id)obj{
    if (obj == nil) {
        return YES;
    }
    return NO;
}

-(NSString *)description{
    return _array.description;
}
@end

相关文章

网友评论

    本文标题:[数据结构与算法-iOS 实现]二叉堆原理实现及top k 问题

    本文链接:https://www.haomeiwen.com/subject/gzrfqktx.html