前面已經完成了單向鏈表的實現,雙向鏈表相比單向鏈表,有如下的不一樣:node
雙向鏈表數據結構和單向鏈表的對好比下圖: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查找節點的功能。post
雙向鏈表經過index查找節點和單向鏈表相比,有一個很是大的優點就是單向鏈表只可以從頭節點一個一個的向後查找。而雙向鏈表因爲能夠直接獲取鏈表的尾節點,而且能夠經過尾節點的prev指針一步一步向前查找,這樣就能夠作一個查找優化:測試
優化的查找代碼以下:優化
- (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;
}
複製代碼
在鏈表的尾部追加一個節點以下圖,新添加的節點替換原來的尾節點稱爲新的尾節點。
須要的操做以下圖:
代碼邏輯以下:
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;
}
}
複製代碼
插入一個新節點到鏈表的頭部以下圖:
須要的操做以下圖:
代碼邏輯以下
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 { // 插入到鏈表節點中間
}
}
複製代碼
插入一個新節點到鏈表兩個節點中間以下圖:
須要的操做以下圖:
代碼邏輯以下:
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++;
}
複製代碼
刪除頭節點以下圖:
須要的操做以下圖:
刪除頭節點代碼以下:
// 刪除頭節點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;
}
複製代碼
// 刪除頭節點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
複製代碼
上面的打印能夠看到,單向鏈表在操做頭節點、尾節點、中間節點、前半部分節點時,整體是優於雙向鏈表,這也是因爲單向鏈表對節點操做次數少於雙向鏈表,由於它不用操做節點指向前一個節點的指針。可是當操做後半部分節點、尾節點時,單向鏈表的效率就會大大低於雙向鏈表。
因此,當只須要對數據的頭部或者前半部分進行操做時,單向鏈表和雙向鏈表時間複雜度一致,而且單向鏈表更加精簡高效。可是若是須要對數據的頭部、尾部都要進行操做時,雙向鏈表大大優於單向鏈表。
既然單向鏈表有對應的單向循環鏈表,那麼雙向鏈表也能夠作成循環鏈表,下面就介紹雙向循環鏈表。