數據結構5-雙向鏈表

雙向鏈表的結構

雙向鏈表和單向鏈表的對比

前面已經完成了單向鏈表的實現,雙向鏈表相比單向鏈表,有如下的不一樣:node

  • 節點:單向鏈表每個節點有一個next指針指向下一個節點。雙向鏈表的節點在單向鏈表節點的基礎上,增長了一個prev指針,指向改節點前一個節點。
  • 鏈表:雙向鏈表中除了_first指針指向鏈表的頭節點以外,又增長了一個_last指針指向鏈表的尾節點。

雙向鏈表數據結構和單向鏈表的對好比下圖:git

因爲Objective-C循環引用的問題,這裏每個節點next使用強引用以維持鏈表中節點引用計數大於1,而prev使用弱引用以免循環引用形成內存泄漏。github

鏈表的頭節點prev因爲沒有上一個節點,prev指向nil,鏈表的尾節點因爲沒有下一個結點,next指向nil。鏈表的first指向指向鏈表的頭節點,鏈表的last指向鏈表的尾節點,這樣就構成了一個完整的雙向鏈表。數組

雙向鏈表的結構

雙向鏈表中,須要兩個成員變量分別保存鏈表的頭節點和尾節點。bash

#import "JKRBaseList.h"
#import "JKRLinkedListNode.h"

NS_ASSUME_NONNULL_BEGIN

@interface JKRLinkedList : JKRBaseList {
    JKRLinkedListNode *_first;
    JKRLinkedListNode *_last;
}

@end

NS_ASSUME_NONNULL_END
複製代碼

雙向鏈表的節點

雙向鏈表的節點在單向鏈表的基礎上,增長了prev指向該節點的前一個節點。這裏prev使用弱引用,以免Objective-C中的循環引用問題。數據結構

NS_ASSUME_NONNULL_BEGIN

@interface JKRLinkedListNode : NSObject

@property (nonatomic, strong, nullable) id object;
@property (nonatomic, strong, nullable) JKRLinkedListNode *next;
@property (nonatomic, weak, nullable) JKRLinkedListNode *prev;

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

- (instancetype)initWithPrev:(JKRLinkedListNode *)prev object:(nullable id)object next:(nullable JKRLinkedListNode *)next;

@end

NS_ASSUME_NONNULL_END
複製代碼

經過index查找節點

和以前的單向鏈表同樣,鏈表的添加和刪除操做,都沒法避免的須要查找到要插入或者刪除的index位置相關的節點,因此要完成添加和刪除節點首先須要先完成經過index查找節點的功能。post

雙向鏈表經過index查找節點和單向鏈表相比,有一個很是大的優點就是單向鏈表只可以從頭節點一個一個的向後查找。而雙向鏈表因爲能夠直接獲取鏈表的尾節點,而且能夠經過尾節點的prev指針一步一步向前查找,這樣就能夠作一個查找優化:測試

  • 當index位於鏈表的前半部分,就從頭節點開始向後查找。
  • 當index位於鏈表的後半部分,就從尾節點開始向前查找。 這樣,經過index查找節點最多就須要查找鏈表一半的長度就能夠了。

優化的查找代碼以下:優化

- (JKRLinkedListNode *)nodeWithIndex:(NSInteger)index {
    [self rangeCheckForExceptAdd:index];
    // _size >> 1 至關於 floor(_size / 2),位運算能夠大大節省計算時間
    if (index < (_size >> 1)) { // 當index位於鏈表的前半,從頭節點向後查找
        JKRLinkedListNode *node = _first;
        for (NSUInteger i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    } else { // 當index位於鏈表的後半,從尾節點向前查找
        JKRLinkedListNode *node = _last;
        for (NSUInteger i = _size - 1; i > index; i--) {
            node = node.prev;
        }
        return node;
    }
}
複製代碼

添加節點

添加第一個節點

雙向鏈表添加第一個節點時,鏈表的first和last都指向這個新節點,新節點的prev和next都指向nil,以下圖:ui

代碼邏輯以下:

if (_size == 0) {
    JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
    _last = node;
    _first = node;
}
複製代碼

鏈表尾部追加一個節點

在鏈表的尾部追加一個節點以下圖,新添加的節點替換原來的尾節點稱爲新的尾節點。

須要的操做以下圖:

  • 新添加節點的prev指向鏈表原來的尾節點。
  • 新添加節點的next指向null。
  • 鏈表的尾節點指針last指向新添加的節點。
  • 鏈表原來尾節點的next指向如今鏈表的新尾節點(即新添加的節點)。

代碼邏輯以下:

if (index == _size) {
    JKRLinkedListNode *oldLast = _last;
    JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
    _last = node;
    oldLast.next = _last;
}
複製代碼

添加第一個節點和尾部追加節點代碼整合

if (_size == 0) {
    JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
    _last = node;
    _first = node;
}

if (index == _size) {
    JKRLinkedListNode *oldLast = _last;
    JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
    _last = node;
    oldLast.next = _last;
}
複製代碼

上面兩段代碼是添加頭節點和尾部追加節點代碼,首先能夠發現,當添加的是第一個節點時候,_size == 0,index == 0,因此上面的這裏能夠整合成一個判斷:

if (index == _size) {
    if (_size == 0) {
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
        _last = node;
        _first = node;
    } else {
        JKRLinkedListNode *oldLast = _last;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
        _last = node;
        oldLast.next = _last;
    }
}
複製代碼

當_size == 0 的時候,_first和_last都爲空,因此代碼能夠再寫成:

if (index == _size) {
    if (_size == 0) {
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
        _last = node;
        _first = node;
    } else {
        JKRLinkedListNode *oldLast = _last;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
        _last = node;
        oldLast.next = _last;
    }
}
複製代碼

提取相同的代碼放在上面:

if (index == _size) {
    JKRLinkedListNode *oldLast = _last;
    JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
    _last = node;
    if (_size == 0) {
        _first = node;
    } else {
        oldLast.next = _last;
    }
}
複製代碼

插入到鏈表頭部

插入一個新節點到鏈表的頭部以下圖:

須要的操做以下圖:

  • 首先獲取鏈表的頭節點。
  • 新節點的prev指向鏈表原頭節點的prev(null)。
  • 新節點的next指向鏈表原頭節點。
  • 鏈表原頭節點的prev指向新節點。
  • 鏈表的first指向新節點。

代碼邏輯以下

if (index == _size) { // 插入到表尾 或者 空鏈表添加第一個節點
    // ...
} else { // 插入到表的非空節點的位置上
    if (index == 0) { // 插入到表頭
        JKRLinkedListNode *next = [self nodeWithIndex:0];
        JKRLinkedListNode *prev = next.prev;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
        next.prev = node;
        _first = node;
    } else { // 插入到鏈表節點中間
        
    }
}
複製代碼

插入到鏈表節點中間

插入一個新節點到鏈表兩個節點中間以下圖:

須要的操做以下圖:

  • 首先獲取插入位置index對應的節點。
  • 新節點的prev指向鏈表插入位置原節點的prev。
  • 新節點的next指向鏈表插入位置原節點。
  • 鏈表插入位置原節點的prev指向新節點。
  • 鏈表插入位置原節點的前一個節點的next指向新節點。

代碼邏輯以下:

if (index == _size) { // 插入到表尾 或者 空鏈表添加第一個節點
    // ...
} else { // 插入到表的非空節點的位置上
    if (index == 0) { // 插入到表頭
        JKRLinkedListNode *next = [self nodeWithIndex:0];
        JKRLinkedListNode *prev = next.prev;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
        next.prev = node;
        _first = node;
    } else { // 插入到鏈表節點中間
        JKRLinkedListNode *next = [self nodeWithIndex:index];
        JKRLinkedListNode *prev = next.prev;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
        next.prev = node;
        prev.next = node;
    }
}
複製代碼

插入到表的非空節點位置的代碼邏輯整合

if (index == _size) { // 插入到表尾 或者 空鏈表添加第一個節點
    // ...
} else { // 插入到表的非空節點的位置上
    if (index == 0) { // 插入到表頭
        JKRLinkedListNode *next = [self nodeWithIndex:0];
        JKRLinkedListNode *prev = next.prev;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
        next.prev = node;
        _first = node;
    } else { // 插入到鏈表節點中間
        JKRLinkedListNode *next = [self nodeWithIndex:index];
        JKRLinkedListNode *prev = next.prev;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
        next.prev = node;
        prev.next = node;
    }
}
複製代碼

將相同的代碼邏輯提取出來:

if (index == _size) { // 插入到表尾 或者 空鏈表添加第一個節點
    // ...
} else { // 插入到表的非空節點的位置上
    JKRLinkedListNode *next = [self nodeWithIndex:0];
    JKRLinkedListNode *prev = next.prev;
    JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
    next.prev = node;
    if (index == 0) { // 插入到表頭
        _first = node;
    } else { // 插入到鏈表節點中間
        prev.next = node;
    }
}
複製代碼

添加節點代碼總結

- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
    [self rangeCheckForAdd:index];
    
    if (index == _size) { // index == size 至關於 插入到表尾 或者 空鏈表添加第一個節點
        JKRLinkedListNode *oldLast = _last;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
        _last = node;
        // 還能夠用 !oldLast 、 !_first 判斷
        if (_size == 0) { // 空鏈表添加第一個節點
            _first = _last;
        } else { // 添加到表尾
            oldLast.next = _last;
        }
    } else { // 插入到表的非空節點的位置上
        JKRLinkedListNode *next = [self nodeWithIndex:index];
        JKRLinkedListNode *prev = next.prev;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
        next.prev = node;
        // 還能夠用 !prev 、 next == _first 判斷
        if (index == 0) { // 插入到表頭
            _first = node;
        } else { // 插入到表中間
            prev.next = node;
        }
    }
    _size++;
}
複製代碼

刪除節點

刪除頭節點

刪除頭節點以下圖:

須要的操做以下圖:

  • 頭節點指針指向頭節點的下一個節點
  • 新頭節點的prev指向原頭節點的prev

刪除頭節點代碼以下:

// 刪除頭節點index == 0
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    _first = next;
    next.prev = prev;
}
複製代碼

刪除尾節點

刪除尾節點以下圖:

須要的操做以下圖:

  • 將尾節點的前一個節點的next指向尾節點的next。
  • 將鏈表last指向尾節點的前一個節點。

代碼以下:

// 刪除尾節點index == _size - 1
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    prev.next = next;
    _last = prev;
}
複製代碼

刪除惟一的節點

刪除鏈表惟一的節點以下圖:

須要的操做以下圖:

  • 將鏈表的first指向節點的next
  • 將鏈表的last指向節點的prev

代碼以下:

// 刪除惟一節點 index == 0,_size == 1
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    _first = next;
    _last = prev;
}
複製代碼

刪除鏈表節點中間的節點

刪除鏈表節點中間的節點以下圖:

須要的操做以下圖:

  • 被刪除節點的前一個節點的next指向被刪除節點的next。
  • 被刪除節點的後一個節點的prev指向被刪除節點的prev。

代碼以下:

// 刪除鏈表節點中間的節點, index > 0 && index < _size - 1
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    prev.next = next;
    next.prev = prev;
}
複製代碼

刪除代碼邏輯整合

// 刪除頭節點index == 0
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    _first = next;
    next.prev = prev;
}

// 刪除尾節點index == _size - 1
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    prev.next = next;
    _last = prev;
}

// 刪除惟一節點 index == 0,_size == 1
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    _first = next;
    _last = prev;
}

// 刪除鏈表節點中間的節點, index > 0 && index < _size - 1
- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    prev.next = next;
    next.prev = prev;
}
複製代碼

將上面三段代碼相同邏輯提取出來,不一樣邏輯地方進行判斷:

- (void)removeObjectAtIndex:(NSUInteger)index {
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev; 
    JKRLinkedListNode *next = node.next
    if (node == _first && node == _last) { // 刪除鏈表節點中間的節點
        _first = next;
        _last = prev;
    } else if (node == _first) { // 刪除頭節點
        _first = next;
        next.prev = prev;
    } else if (node == _last) { // 刪除尾節點
        prev.next = next;
        _last = prev;
    } else { // 刪除惟一節點
        prev.next = next;
        next.prev = prev;
    }
}
複製代碼

下面就是合併這段代碼:

if (node == _first && node == _last) { // 刪除惟一節點
    _first = next;
    _last = prev;
} else if (node == _first) { // 刪除頭節點
    _first = next;
    next.prev = prev;
} else if (node == _last) { // 刪除尾節點
    prev.next = next;
    _last = prev;
} else { // 刪除鏈表節點中間的節點
    prev.next = next;
    next.prev = prev;
}
複製代碼

轉化成:

if (node == _first) {
    _first = next;
} else {
    prev.next = next;
}

if (node == _last) {
    _last = prev;
} else {
    next.prev = prev;
}
複製代碼

刪除節點的完整代碼爲:

- (void)removeObjectAtIndex:(NSUInteger)index {
    [self rangeCheckForExceptAdd:index];
    JKRLinkedListNode *node = [self nodeWithIndex:index];
    JKRLinkedListNode *prev = node.prev;
    JKRLinkedListNode *next = node.next;
    if (node == _first) {
        _first = next;
    } else {
        prev.next = next;
    }
    
    if (node == _last) {
        _last = prev;
    } else {
        next.prev = prev;
    }
    _size--;
}
複製代碼

測試

測試對象爲Person對象,重寫打印方便觀察。

@interface Person : NSObject

@property (nonatomic, assign) NSInteger age;

+ (instancetype)personWithAge:(NSInteger)age;

@end


@implementation Person

+ (instancetype)personWithAge:(NSInteger)age {
    Person *p = [Person new];
    p.age = age;
    return p;
}

- (void)dealloc {
    NSLog(@"%@ dealloc", self);
}

- (NSString *)description {
    return [NSString stringWithFormat:@"%zd", self.age];
}

@end
複製代碼

測試所有功能

(W 1) -> 2 -> (null): 意味着該節點存儲的值爲2,用弱引用指向前一個節點,前一個節點的值爲1,用強引用指向後一個節點,後一個節點爲null。

JKRBaseList *list = [JKRLinkedList new];
[list addObject:[Person personWithAge:1]];
NSLog(@"添加鏈表第一個節點 \n%@\n", list);
[list addObject:[Person personWithAge:3]];
NSLog(@"尾部追加一個節點 \n%@\n", list);
[list insertObject:[Person personWithAge:2] atIndex:1];
NSLog(@"插入到鏈表兩個節點之間 \n%@\n", list);
[list insertObject:[Person personWithAge:0] atIndex:0];
NSLog(@"插入到鏈表頭部 \n%@\n", list);
[list removeFirstObject];
NSLog(@"刪除頭節點 \n%@\n", list);
[list removeObjectAtIndex:1];
NSLog(@"刪除鏈表兩個節點之間的節點 \n%@\n", list);
[list removeLastObject];
NSLog(@"刪除尾節點 \n%@\n", list);
[list removeAllObjects];
NSLog(@"刪除鏈表惟一的節點 \n%@\n", list);

// 打印
添加鏈表第一個節點 
Size: 1 [(W (null)) -> 1 -> ((null))]

尾部追加一個節點 
Size: 2 [(W (null)) -> 1 -> (3), (W 1) -> 3 -> ((null))]

插入到鏈表兩個節點之間 
Size: 3 [(W (null)) -> 1 -> (2), (W 1) -> 2 -> (3), (W 2) -> 3 -> ((null))]

插入到鏈表頭部 
Size: 4 [(W (null)) -> 0 -> (1), (W 0) -> 1 -> (2), (W 1) -> 2 -> (3), (W 2) -> 3 -> ((null))]

0 dealloc
刪除頭節點 
Size: 3 [(W (null)) -> 1 -> (2), (W 1) -> 2 -> (3), (W 2) -> 3 -> ((null))]

2 dealloc
刪除鏈表兩個節點之間的節點 
Size: 2 [(W (null)) -> 1 -> (3), (W 1) -> 3 -> ((null))]

3 dealloc
刪除尾節點 
Size: 1 [(W (null)) -> 1 -> ((null))]

刪除鏈表惟一的節點 
Size: 0 []
1 dealloc
複製代碼

雙向鏈表的時間複雜度

查找節點

由於對節點的添加和刪除操做須要查找對於index的節點,首先分析如下查找節點的代碼:

- (JKRLinkedListNode *)nodeWithIndex:(NSInteger)index {
    [self rangeCheckForExceptAdd:index];
    // _size >> 1 至關於 floor(_size / 2),位運算能夠大大節省計算時間
    if (index < (_size >> 1)) { // 當index位於鏈表的前半,從頭節點向後查找
        JKRLinkedListNode *node = _first;
        for (NSUInteger i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    } else { // 當index位於鏈表的後半,從尾節點向前查找
        JKRLinkedListNode *node = _last;
        for (NSUInteger i = _size - 1; i > index; i--) {
            node = node.prev;
        }
        return node;
    }
}
複製代碼

當index == 0 或 index == _size - 1時,即查找頭節點和尾節點時,直接就能夠一次查找就拿到須要的節點。越靠近頭節點或尾節點,查找須要遍歷的次數越小,最壞的狀況就是查找中間的節點,須要 n / 2 次查找。

添加和刪除

- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
    [self rangeCheckForAdd:index];
    
    if (index == _size) { // index == size 至關於 插入到表尾 或者 空鏈表添加第一個節點
        JKRLinkedListNode *oldLast = _last;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:nil];
        _last = node;
        // 還能夠用 !oldLast 、 !_first 判斷
        if (_size == 0) { // 空鏈表添加第一個節點
            _first = _last;
        } else { // 添加到表尾
            oldLast.next = _last;
        }
    } else { // 插入到表的非空節點的位置上
        JKRLinkedListNode *next = [self nodeWithIndex:index];
        JKRLinkedListNode *prev = next.prev;
        JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
        next.prev = node;
        // 還能夠用 !prev 、 next == _first 判斷
        if (index == 0) { // 插入到表頭
            _first = node;
        } else { // 插入到表中間
            prev.next = node;
        }
    }
    _size++;
}
複製代碼

因爲添加的全部狀況對節點prev和next操做都是相同的,區別就是經過index查找節點,這取決於上面查找節點的複雜度。所以能夠推出結論,雙向鏈表刪除頭節點、尾節點都是O(1),越靠近頭節點或尾節點操做時間越快,越靠近鏈表中間越慢。平均複雜度爲O(n),而刪除同添加。

雙向鏈表時間複雜度

狀況分類 添加 刪除
最好 頭節點 O(1) O(1)
最好 尾節點 O(1) O(1)
平均 O(n) O(n)

單向鏈表和雙向鏈表時間複雜度對比

單向鏈表原理和實現中已經和動態數組相比對比並分析了單向鏈表的時間複雜度:

狀況分類 添加 刪除
最好 頭節點 O(1) O(1)
最差 尾節點 O(n) O(n)
平均 O(n) O(n)

由上面的結論能夠知道,單向鏈表和雙向鏈表在操做頭節點、鏈表的前半部分、鏈表的中間節點時都是同樣的。而雙向鏈表在操做尾節點、鏈表後半部分是大大優於單向鏈表的。

下面是一段對比測試,分別對比了單向鏈表和雙向鏈表在對頭節點、尾節點、前半部分節點、後半部分節點、中間節點這五種狀況下的對比:

void compareSingleLinkedListAndLinkedList() {
    NSUInteger testCount = 50000;
    
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRSingleLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:0];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeFirstObject];
        }
        NSLog(@"單向鏈表操做頭節點");
    }];
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:0];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeFirstObject];
        }
        NSLog(@"雙向鏈表操做頭節點");
    }];
    
    
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRSingleLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array addObject:[NSNumber numberWithInteger:i]];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeLastObject];
        }
        NSLog(@"單向鏈表操做尾節點");
    }];
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array addObject:[NSNumber numberWithInteger:i]];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeLastObject];
        }
        NSLog(@"雙向鏈表操做尾節點");
    }];
    
    
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRSingleLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:array.count >> 2];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeObjectAtIndex:array.count >> 2];
        }
        NSLog(@"單向鏈表操做 index = 總節點數*0.25 節點");
    }];
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:array.count >> 2];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeObjectAtIndex:array.count >> 2];
        }
        NSLog(@"雙向鏈表操做 index = 總節點數*0.25 節點");
    }];
    
    
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRSingleLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:array.count * 0.75];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeObjectAtIndex:array.count * 0.75];
        }
        NSLog(@"單向鏈表操做 index = 總節點數*0.75 節點");
    }];
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:array.count * 0.75];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeObjectAtIndex:array.count * 0.75];
        }
        NSLog(@"雙向鏈表操做 index = 總節點數*0.75 節點");
    }];
    
    
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRSingleLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:array.count >> 1];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeObjectAtIndex:array.count >> 1];
        }
        NSLog(@"單向鏈表操做中間節點");
    }];
    [JKRTimeTool teskCodeWithBlock:^{
        JKRBaseList *array = [JKRLinkedList new];
        for (NSUInteger i = 0; i < testCount; i++) {
            [array insertObject:[NSNumber numberWithInteger:i] atIndex:array.count >> 1];
        }
        for (NSUInteger i = 0; i < testCount; i++) {
            [array removeObjectAtIndex:array.count >> 1];
        }
        NSLog(@"雙向鏈表操做中間節點");
    }];
}
複製代碼

打印結果:

單向鏈表操做頭節點
耗時: 0.020 s
雙向鏈表操做頭節點
耗時: 0.038 s

單向鏈表操做尾節點
耗時: 49.085 s
雙向鏈表操做尾節點
耗時: 0.033 s

單向鏈表操做 index = 總節點數*0.25 節點
耗時: 12.144 s
雙向鏈表操做 ndex = 總節點數*0.25 節點
耗時: 12.215 s

單向鏈表操做 index = 總節點數*0.75 節點
耗時: 35.735 s
雙向鏈表操做 index = 總節點數*0.75 節點
耗時: 18.488 s

單向鏈表操做中間節點
耗時: 23.856 s
雙向鏈表操做中間節點
耗時: 36.420 s
複製代碼

上面的打印能夠看到,單向鏈表在操做頭節點、尾節點、中間節點、前半部分節點時,整體是優於雙向鏈表,這也是因爲單向鏈表對節點操做次數少於雙向鏈表,由於它不用操做節點指向前一個節點的指針。可是當操做後半部分節點、尾節點時,單向鏈表的效率就會大大低於雙向鏈表。

因此,當只須要對數據的頭部或者前半部分進行操做時,單向鏈表和雙向鏈表時間複雜度一致,而且單向鏈表更加精簡高效。可是若是須要對數據的頭部、尾部都要進行操做時,雙向鏈表大大優於單向鏈表。

接下來

既然單向鏈表有對應的單向循環鏈表,那麼雙向鏈表也能夠作成循環鏈表,下面就介紹雙向循環鏈表

源碼

點擊查看源碼