接着上一篇经过静态数组的扩容实现动态数组建立动态数组以后,这里再来建立经过单向链表实现一个动态数组。首先先来分析下动态数组的缺点,才可以了解到链表的意义。 首先回顾下以前动态数组添加和删除的过程:node
动态数组添加元素的时候,最坏的状况是插入元素到数组的头部,则须要依次向后挪动因此元素,进行的操做数取决于当前元素的数量,复杂度为O(n),最好的状况是追加到数组的尾部,不须要挪动元素,复杂度为O(1)。平均复杂度为O(n)。扩容因为不是每次添加都须要的操做,只有在溢出的时候才须要扩容,扩容的时候复杂度为O(n),不扩容的时候为O(1),均摊复杂度依然为O(1)。git
删除和添加基本相同,最好的状况删除队尾复杂度为O(1),最差的状况是删除队头,复杂度为O(n),平均复杂度为O(n)。github
而在根据index取值的时候,因为本质是经过index直接从数组中取值,数组中取值的复杂度为O(1),因此取元素的复杂度为O(1)。数组
注:从数组中值并不须要遍历,而是经过地址计算直接取值,复杂度为O(1)。bash
那么链表会不会比静态数组更快呢,下面咱们来实现一个自定义单向链表,而后比较一下就知道了。首先提示一下,单向链表可能没想象的那么快哦。数据结构
首先看一下数组和链表在内存中的不一样post
这样一看单向链表是否是很是的简单,在Objective-C语言中,就至关于每个节点对象有两个成员变量,一个是存值的,另外一个是存下一个节点对象的,下面就声明一个单向链表的节点对象:测试
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface JKRSingleLinkedListNode : NSObject
@property (nonatomic, strong, nullable) id object;
@property (nonatomic, strong, nullable) JKRSingleLinkedListNode *next;
- (instancetype)init __unavailable;
+ (instancetype)new __unavailable;
- (instancetype)initWithObject:(nullable id)object next:(nullable JKRSingleLinkedListNode *)next;
@end
NS_ASSUME_NONNULL_END
复制代码
既然只须要拿到单向链表的头节点,就可以访问到所有节点,那么单向链表中须要存储成员变量只须要两个,一个是 _size,存储这链表的长度。另外一个是_first,保存链表的第一个节点。ui
注:_size存储在父类中,全部的接口声明也在父类中,父类的定义参见经过静态数组的扩容实现动态数组,所有源代码在文章结尾。atom
#import "JKRBaseList.h"
#import "JKRSingleLinkedListNode.h"
NS_ASSUME_NONNULL_BEGIN
@interface JKRSingleLinkedList : JKRBaseList {
// NSUInteger _size;
JKRSingleLinkedListNode *_first;
}
@end
NS_ASSUME_NONNULL_END
复制代码
下面是完整的单向链表的内存结构图,能够更加直接的了解单向链表的结构:
上面的链表结构图能够看到,链表对象保存这链表的长度和链表的头节点,若是要经过index得到具体的某一个节点,须要从头节点开始逐一经过next指针日后查找,直到找到第index个节点。
时间复杂度取决于index,取index为0的节点只须要访问头节点,只须要1次访问。访问尾节点须要从头节点一直访问到尾节点,访问次数取决于节点数量。综上平均时间复杂度为O(n)。
- (JKRSingleLinkedListNode *)nodeWithIndex:(NSInteger)index {
[self rangeCheckForExceptAdd:index];
JKRSingleLinkedListNode *node = _first;
for (NSInteger i = 0; i < index; i++) {
node = node.next;
}
return node;
}
复制代码
上面已经实现了拿到index位置的节点,这里只须要调用方法获取节点,而后返回节点存储的值就能够了。 时间复杂度同查找节点:O(n)
- (id)objectAtIndex:(NSUInteger)index {
return [self nodeWithIndex:index].object;
}
复制代码
在链表中间插入节点,以下图,链表中存在三个节点,此时咱们须要在链表index为1的位置插入一个节点:
此时须要作的就是让index为0的节点的next指向新节点,并让新节点的next指向原来index为1的节点:
这样插入就成功的将一个节点插入到链表的两个节点中:
在链表头部插入节点以下图:
这时须要将新节点的next指向原来链表的_first,并将链表的_first指向新节点:
在链表头部成功插入节点后链表的结构:
综上步骤,链表添加节点两种状况的代码实现以下:
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
[self rangeCheckForAdd:index];
if (index == 0) {
JKRSingleLinkedListNode *node = [[JKRSingleLinkedListNode alloc] initWithObject:anObject next:_first];
_first = node;
} else {
JKRSingleLinkedListNode *prev = [self nodeWithIndex:index - 1];
JKRSingleLinkedListNode *node = [[JKRSingleLinkedListNode alloc] initWithObject:anObject next:prev.next];
prev.next = node;
}
_size++;
}
复制代码
添加时index的越界检查有动态数组的父类统一实现。
由于添加节点时,虽然插入只须要1次操做,可是涉及到查找index位置的节点,这个查找的复杂度为O(n),因此添加节点的复杂度为O(n)。
假设删除链表index为1的节点,以下图:
只须要将被删除节点的前一个节点的next指针改变指向,指向被删除节点的下一个节点,那么被删除节点因为没有引用,就会自动被回收:
删除后链表的结构:
删除链表的头节点以下图:
只须要将单向链表的_first指针指向原来头节点下一个节点便可:
删除后链表的结构:
综上两种状况,链表删除的代码以下:
- (void)removeObjectAtIndex:(NSUInteger)index {
[self rangeCheckForExceptAdd:index];
JKRSingleLinkedListNode *node = _first;
if (index == 0) {
_first = _first.next;
} else {
JKRSingleLinkedListNode *prev = [self nodeWithIndex:index - 1];
node = prev.next;
prev.next = node.next;
}
_size--;
}
复制代码
同添加节点的操做,虽然删除节点只须要1次操做,可是涉及到查找index位置的节点,这个查找的复杂度为O(n),因此删除节点的复杂度也是为O(n)。
至于其余功能都是基于上面几个接口调用实现的,好比尾部追加、删除头节点等,就不一一列举了,最后源码中都有。
数据结构 | 动态数组 | 单向链表 |
---|---|---|
详细分类 | 最好 最差 平均 | 最好 最差 平均 |
插入任意位置元素 | O(1) O(n) O(n) | O(1) O(n) O(n) |
删除任意位置元素 | O(1) O(n) O(n) | O(1) O(n) O(n) |
替换任意位置元素 | O(1) O(1) O(1) | O(1) O(n) O(n) |
查找任意位置元素 | O(1) O(1) O(1) | O(1) O(n) O(n) |
添加元素到尾部 | O(1) O(n) O(1) | O(n) O(n) O(n) |
删除尾部元素 | O(1) O(1) O(1) | O(n) O(n) O(n) |
添加元素到头部 | O(n) O(n) O(n) | O(1) O(1) O(1) |
删除头部元素 | O(n) O(n) O(n) | O(1) O(1) O(1) |
上面是总结出来的时间复杂度对比:
由上面的分析能够直到,单向链表并非全部状况下时间复杂度都优于动态数组的,当须要频发的删除和添加到数组头部时,单向链表优于动态数组。当须要频发的删除和添加到数组尾部时,动态数组优于单向链表。
下面测试一下:
进行10000次头部的插入和删除操做,动态数组和单向链表对比:
进行10000次尾部的插入和删除操做,动态数组和单向链表对比:
因此并非说单向链表就必定时间复杂度上优于动态数组,依然要区分在不一样的应用场景。若是须要频繁的对数组头部进行插入和删除操做,单向链表是大大优于动态数组的。若是是须要频繁的在数组尾部进行插入和删除操做,动态数组又是大大优于单向链表的。
单向循环链表是基于单向链表的结构作了功能的扩展,有了单向链表的基础,下面就能够更容易理解单向循环链表的实现了。