數據結構1-靜態數組

規劃

首先這篇只是一個開篇,後面計劃的更新文章包括:動態數組、鏈表、二叉樹、哈希表、堆等,而且中間會穿插一些具體的現實中的應用和時間複雜度對比。git

感謝

文章大部分的代碼邏輯並不是我本身想出來的,本人也沒有這麼好天分,大部分代碼邏輯來自於因爲李明傑的數據結構與算法課程。源課程語言爲Java,我將其用Objective-C轉寫,但不是複製粘貼源碼,也並不是不註明出處的抄襲。若是以爲文章不錯,而且想更深刻系統的學習數據結構,推薦去看下試聽課github

更新:算法

感謝哈大沙關於dealloc時,對C++數組回收內存遺漏的指正。數組

語言

爲了方便iOS同行朋友的閱讀理解,全部的數據結構所有用Objective-C實現。 語言並不能影響數據結構自己的原理,邏輯都是同樣的,只是細節會有不一樣,好比Objective-C在鏈表和二叉樹中要考慮循環引用的問題,而Java就沒必要考慮這些,可是原理和本質都是同樣的。bash

自定義靜態數組的緣由

就一個緣由吧,Objective-C這傢伙沒有,只能本身搞一個。數據結構

內部的實現方案

建立一個數組對象JKRArray,直接繼承自NSObject。 首先自定義數組應該和Objective-C原生語言環境相匹配,那麼咱們的靜態數組也是應該和NSArray同樣,存放對象的指針,惟一的區別就是,自定義靜態數組須要指定長度初始化,而且初始化以後是一個空數組,而且能夠在數組長度範圍內的任意位置直接經過下標插入和取出元素,好比初始化一個長度爲3的數組後,數組的內部結構應該是:app

[nil, nil, nil]
複製代碼

直接經過下標在array[1]插入一個@1後,數組的內部結構應該是:post

[nil, 1, nil]
複製代碼

因爲數組須要保存對象的指針,並且因爲功能限制,內部必需要本身靜態數組來存放指針,而Objective-C沒有這種功能,咱們就只可以經過C++的數組來實現。將JKRArray.m文件改成JKRArray.mm,在項目的TARGETS-BuildPhases-Compile Sources中將JKRArray.mm的Compiler Flags添加-fno-objc-arc。學習

對象的封裝

這裏儘可能模仿Objective-C官方語言的接口設計風格,來達到便於記憶接口,方便使用的目的。測試

{
@protected
    void ** _array;
    NSUInteger _length;
}

- (instancetype)init __unavailable;
+ (instancetype)new __unavailable;

+ (instancetype)arrayWithLength:(NSUInteger)length;

- (instancetype)initWithLength:(NSUInteger)length;
- (NSUInteger)length;
- (void)setObject:(nullable ObjectType)object AtIndex:(NSUInteger)index;
- (nullable ObjectType)objectAtIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (NSUInteger)indexOfObject:(nullable ObjectType)object;
- (BOOL)containsObject:(ObjectType)object;

@end

@interface JKRArray<ObjectType> (JKRExtendedArray)

- (void)enumerateObjectsUsingBlock:(void (^)(_Nullable ObjectType obj, NSUInteger idx, BOOL *stop))block;
- (_Nullable ObjectType)objectAtIndexedSubscript:(NSUInteger)idx;
- (void)setObject:(_Nullable ObjectType)obj atIndexedSubscript:(NSUInteger)idx;

@end

@interface JKRArray<ObjectType> (NSGenericFastEnumeraiton) <NSFastEnumeration>

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;

@end
複製代碼

接口的定義和功能,都和官方的NSArray所有保持一致,因此大部分接口就不解釋了,這裏逐一說明一些NSArray存在單又不經常使用的和咱們自定義的接口。

{
// 如下兩個成員變量的訪問權限設定私有,由於它們不該該被更改
@private
    // 指針數組,存放是加入到數組中對象的指針
    void ** _array;
    // 數組的長度
    NSUInteger _length;
}

// 如下定義是爲了讓咱們的數組只可以經過指定長度來建立,由於靜態數據必需要指定長度
- (instancetype)init __unavailable;
+ (instancetype)new __unavailable;
+ (instancetype)arrayWithLength:(NSUInteger)length;
- (instancetype)initWithLength:(NSUInteger)length;
/// 如下兩個方法的聲明和實現是爲了讓咱們自定義的數組類可以實現經過[]取值和賦值,例如id obj = array[10]、array[3] = [NSObject new]。
- (_Nullable ObjectType)objectAtIndexedSubscript:(NSUInteger)idx;
- (void)setObject:(_Nullable ObjectType)obj atIndexedSubscript:(NSUInteger)idx;

// <NSFastEnumeration>協議和這個方法是爲了實現 for in 快速遍歷
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;
複製代碼

接口實現

初始化接口實現

初始化的同時,建立一個指針數組,長度爲初始化方法傳入的length。

#pragma mark - 初始化
+ (instancetype)arrayWithLength:(NSUInteger)length {
    // 手動管理類方法建立對象須要調用autorelease
    return [[[self alloc] initWithLength:length] autorelease];
}

- (instancetype)initWithLength:(NSUInteger)length {
    self = [super init];
    _length = length;
    // 建立一個指針數組
    _array = new void*[length]();
    return self;
}
複製代碼

邊界檢查

先封裝一個方法,用於全部關於index操做的方法執行前,先檢查時候越界

#pragma mark - 邊界檢查
- (void)checkRangeWithIndex:(NSUInteger)index {
    if (index < 0 || index >= _length) NSAssert(NO, @"Index: %zd, Length: %zd", index, _length);
}
複製代碼

在index位置添加元素

時間複雜度:O(1)

#pragma mark - 添加元素
- (void)setObject:(id)object AtIndex:(NSUInteger)index {
    // 檢查傳入的index是否超過數組的範圍
    [self checkRangeWithIndex:index];
    // 先獲取index位置原來存放的元素
    id oldObject = [self objectAtIndex:index];
    // 若是添加的元素和原來的元素同樣,則直接返回。
    if (oldObject == object) return;
    // 若是添加的元素是新元素,則舊元素引用計數-1
    if (oldObject != nil) [oldObject release];
    // 新添加元素引用計數+1
    if (object) [object retain];
    // 新元素指針添加到數組中
    *(_array + index) = (__bridge void *)object;
}
複製代碼

經過index刪除元素

時間複雜度:O(1)

#pragma mark - 刪除元素
- (void)removeObjectAtIndex:(NSUInteger)index {
    // 檢查傳入的index是否超過數組的範圍
    [self checkRangeWithIndex:index];
    // 將index位置元素取出
    id object = (__bridge id)(*(_array + index));
    // 若是該位置存放了元素,刪除引用計數-1(由於添加的時候+1)
    if (object) [object release];
    *(_array + index) = 0;
}
複製代碼

經過index獲取元素

時間複雜度:O(1)

#pragma mark - 獲取元素
- (id)objectAtIndex:(NSUInteger)index {
    [self checkRangeWithIndex:index];
    // 直接在數組指針中經過index取值便可
    id object = (__bridge id)(*(_array + index));
    return object;
}
複製代碼

枚舉全部元素

#pragma mark - 枚舉
- (void)enumerateObjectsUsingBlock:(void (^)(id _Nullable, NSUInteger, BOOL * _Nonnull))block {
    BOOL stop = NO;
    // 遍歷長度範圍內的全部index取值
    for (NSUInteger i = 0; i < _length && !stop; i++) {
        id object = [self objectAtIndex:i];
        block(object, i, &stop);
    }
}
複製代碼

獲取元素的index

時間複雜度:O(N)

#pragma mark - 元素在數組中存儲的第一個下標
- (NSUInteger)indexOfObject:(id)object {
    // 若是找不到知足條件的index,就返回NSUIntegerMax(這裏和NSArray處理同樣)
    __block NSUInteger index = NSUIntegerMax;
    // 經過遍歷,從index爲0開始,找到第一個知足的index
    [self enumerateObjectsUsingBlock:^(id  _Nullable obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (object == obj) {
            index = idx;
            *stop = YES;
        } else if ([object isEqual:obj]){
            index = idx;
            *stop = YES;
        } 
    }];
    return index;
}
複製代碼

返回數組是否包含某元素

時間複雜度:O(N)

#pragma mark - 是否包含
- (BOOL)containsObject:(id)object {
    // 查看元素的index,若是index<_length則包含(不存在會index爲NSUIntegerMax)
    NSUInteger index = [self indexOfObject:object];
    return index < _length;
}
複製代碼

返回數組長度

時間複雜度:O(1)

#pragma mark - 返回長度
- (NSUInteger)length {
    return _length;
}
複製代碼

支持數組運算符

#pragma mark - 支持數組運算符
- (id)objectAtIndexedSubscript:(NSUInteger)idx {
    return [self objectAtIndex:idx];
}

- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
    [self setObject:obj AtIndex:idx];
}

複製代碼

支持 for in 快速遍歷

#pragma mark - 快速遍歷
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id  _Nullable [])buffer count:(NSUInteger)len {
    if (state->state > 0) return 0;
    state->mutationsPtr = (unsigned long*)self;
    NSUInteger retCount = state->extra[0];
    state->itemsPtr = (id *)(_array + state->extra[1]);
    if (retCount == 0) retCount = _length;
    if (retCount > len) {
        state->extra[0] = retCount - len;
        state->extra[1] += len;
        retCount = len;
    } else {
        state->state++;
    }
    return retCount;
}
複製代碼

重寫dealloc

#pragma mark - dealloc
- (void)dealloc {
    // dealloc以前,要將處理內存在的元素引用計數還原(-1),不然會形成內存泄漏
    for (NSUInteger i = 0; i < _length; i++) {
        id object = [self objectAtIndex:i];
        if (object) [object release];
    }
    delete _array;
    [super dealloc];
}
複製代碼

重寫 description方法

咱們要讓自定義數組也能像NSArray同樣,有數組形式的打印

- (NSString *)description {
    NSMutableString *mutableString = [NSMutableString string];
    [mutableString appendString:[NSString stringWithFormat:@"<%@: %p>: \nlength: %zd\n{\n", self.class, self, _length]];
    for (NSUInteger i = 0; i < _length; i++) {
        if (i) [mutableString appendString:@"\n"];
        id object = [self objectAtIndex:i];
        if (object) {
            [mutableString appendString:@" "];
            [mutableString appendString:[object description]];
        } else {
            [mutableString appendString:@" "];
            [mutableString appendString:@"Null"];
        }
    }
    [mutableString appendString:@"\n}"];
    return mutableString;
}
複製代碼

測試功能

初始化測試

JKRArray *array = [JKRArray arrayWithLength:6];
NSLog(@"%@", array);

打印:
<JKRArray: 0x102107cb0>: 
length: 6
{
   Null
   Null
   Null
   Null
   Null
   Null
}
複製代碼

插入測試

array[2] = [Person new];
NSLog(@"%@", array);

打印:
<JKRArray: 0x1006079f0>: 
length: 6
{
   Null
   Null
   <Person: 0x10286bc60>
   Null
   Null
   Null
}
複製代碼

刪除測試

array[2] = nil;
NSLog(@"%@", array);

打印:
<Person: 0x100713400> dealloc
<JKRArray: 0x100607e20>: 
length: 6
{
   Null
   Null
   Null
   Null
   Null
   Null
}
複製代碼

接下來

有了靜態數據,下面就用它爲基礎實現一個相似於NSMutableArray的自定義動態數組

源碼

點擊查看源碼

相關文章
相關標籤/搜索