规划
首先这篇只是一个开篇,后面计划的更新文章包括:动态数组、链表、二叉树、哈希表、堆等,并且中间会穿插一些具体的现实中的应用和时间复杂度对比。
感谢
文章大部分的代码逻辑并非我自己想出来的,本人也没有这么好天分,大部分代码逻辑来自于由于李明杰的数据结构与算法课程。源课程语言为Java,我将其用Objective-C转写,但不是复制粘贴源码,也并非不注明出处的抄袭。如果觉得文章不错,并且想更深入系统的学习数据结构,推荐去看下试听课。
更新:
感谢哈大沙关于dealloc时,对C++数组回收内存遗漏的指正。
废话
对于一个工作了马上就要5年的iOS开发工程师来讲,本次提到自己的职位中工程师三个字都会觉得非常惭愧。深感自己能力不足,担不起工程师三个字。每次看到技术圈内那些优秀的文章和项目,在佩服作者们优秀的技术之外,更是看到了作者们的天才,同时又深感自己愚钝。虽然自己达不到大牛们的程度,不过还是希望分享这些简单易懂的数据结构基础知识,希望比我天分高的多却又缺少基础知识人,可以通过这些文章达到比我更高的层次。
语言
为了方便iOS同行朋友的阅读理解,所有的数据结构全部用Objective-C实现,并且不会做过度的封装,尽量完整的展示所有的逻辑。同时为了专注于数据结构本身的实现,尽可能的全部代码使用Objective-C本身的面向对象封装的方式实现。
语言并不能影响数据结构本身的原理,逻辑都是一样的,只是细节会有不同,比如Objective-C在链表和二叉树中要考虑循环引用的问题,而Java就不必考虑这些,但是原理和本质都是一样的。
自定义静态数组的原因
Objective-C本身是有提供NSArray和NSMutableArray两种数组来用开发者使用的,但是它们都存在一定的问题,首先就是不能够在不初始化值的时候指定容量,比如创建一个空的容量为3的数组 {nil, nil, nil},当然可以通过传入三个[NSNull new]来达到这种效果,但是并不好用。而在之后的很多数组结构中,都需要创建可以指定长度并且存放空值的数组,例如动态数组、哈希表、循环队列、二叉堆等。所以开篇就用来把这个最基础的静态数组做好,为之后的数据结构的实现做好准备。
内部的实现方案
创建一个数组对象JKRArray,直接继承自NSObject,创建完成后编译器中生成JKRArray.h、JKRArray.m两个文件。 首先自定义数组应该和Objective-C原生语言环境相匹配,那么我们的静态数组也是应该和NSArray一样,存放对象的指针,唯一的区别就是,自定义静态数组需要指定长度初始化,并且初始化之后是一个空数组,并且可以在数组长度范围内的任意位置直接通过下标插入和取出元素,比如初始化一个长度为3的数组后,数组的内部结构应该是:
[nil, nil, nil]
复制代码
直接通过下标在array[1]插入一个@1后,数组的内部结构应该是:
[nil, 1, nil]
复制代码
由于数组需要保存对象的指针,而且由于功能限制,内部必须要自己静态数组来存放指针,而Objective-C没有这种功能,我们就只能够通过C++的数组来实现。由于要其内部要创建一个C++的数组,所以需要将JKRArray.m文件改为JKRArray.mm。并且还要涉及到手动管理对象的引用计数,所以需要在项目的TARGETS-BuildPhases-Compile Sources中将JKRArray.mm的Compiler Flags添加-fno-objc-arc。
提到要手动管理内存,那么就要考虑管理的时机。首先,在添加元素的时候,需要将元素的引用计数+1,而在移除的时候,需要将其-1,以达到不会造成内存泄漏的目的。另外还要考虑的就是,在数组对象自己被销毁的时候,它不会自动将数组中的元素引用计-1,所以还需要在对象对象的dealloc方法调用时,还需要将其内部仍然存在的元素的引用计数全部-1。
对象的封装
这里尽量模仿Objective-C官方语言的接口设计风格,来达到便于记忆接口,方便使用的目的。
@interface JKRArray<ObjectType> : NSObject<NSFastEnumeration> {
@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
}
复制代码