以前已經實現了單向循環鏈表,雙向循環鏈表的原理和單向鏈表很類似:尾節點的next指向鏈表的頭節點。在此基礎上,頭節點的prev指向尾節點,這樣就實現了雙向循環鏈表。一樣,爲了防止循環引用,尾節點指向頭節點要用弱引用。node
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface JKRLinkedListNode : NSObject
@property (nonatomic, strong, nullable) id object;
@property (nonatomic, weak, nullable) JKRLinkedListNode *weakNext;
@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
複製代碼
雙向循環鏈表添加節點和雙向鏈表基本同樣,只是多了頭節點的prev和尾節點的next的維護操做。git
對比雙向鏈表,雙向循環鏈表除了將鏈表的頭節點和尾節點指向新節點以外,還須要將節點的prev、weakNext都指向它本身。github
代碼邏輯以下:數組
if (_size == 0 && index == 0) {
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
_last = node;
_first = _last;
_first.prev = _first;
_first.next = nil;
_first.weakNext = _first;
}
複製代碼
新添加的節點替換原來的尾節點稱爲新的尾節點:bash
須要的操做以下圖:數據結構
if (_size == index && _size != 0) {
JKRLinkedListNode *oldLast = _last;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
_last = node;
oldLast.next = _last;
oldLast.weakNext = nil;
_first.prev = _last;
_last.next = nil;
_last.weakNext = _first;
}
複製代碼
if (_size == 0 && index == 0) {
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
_last = node;
_first = _last;
_first.prev = _first;
_first.next = nil;
_first.weakNext = _first;
}
if (_size == index && _size != 0) {
JKRLinkedListNode *oldLast = _last;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
_last = node;
oldLast.next = _last;
oldLast.weakNext = nil;
_first.prev = _last;
_last.next = nil;
_last.weakNext = _first;
}
複製代碼
上面兩段代碼將相同的判斷邏輯合併,不一樣的判斷邏輯分開:post
if (_size == index) {
if (_size == 0) {
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
_last = node;
_first = _last;
_first.prev = _first;
_first.next = nil;
_first.weakNext = _first;
} else {
JKRLinkedListNode *oldLast = _last;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:nil object:anObject next:nil];
_last = node;
oldLast.next = _last;
oldLast.weakNext = nil;
_first.prev = _last;
_last.next = nil;
_last.weakNext = _first;
}
}
複製代碼
將相同的代碼提出出來:測試
if (_size == index) {
JKRLinkedListNode *oldLast = _last;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:_first];
_last = node;
// _size == 0
// 還能夠使用 !oldLast 由於空鏈表_last爲空
if (_size == 0) { // 添加鏈表第一個元素
_first = _last;
_first.prev = _first;
_first.next = nil;
_first.weakNext = _first;
} else { // 插入到表尾
oldLast.next = _last;
oldLast.weakNext = nil;
_first.prev = _last;
_last.next = nil;
_last.weakNext = _first;
}
}
複製代碼
插入一個新節點到鏈表的頭部以下圖:ui
須要的操做以下圖:atom
節點插入操做完成後的鏈表以下:
代碼邏輯以下:
if (index == _size) { // 插入到表尾 或者 空鏈表添加第一個節點
// ...
} else {
if (index == 0) { // 插入到表頭
JKRLinkedListNode *next = [self nodeWithIndex:index];
JKRLinkedListNode *prev = next.prev;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
next.prev = node;
_first = node;
prev.next = nil;
prev.weakNext = node;
} else { // 插入到兩個節點中間
}
}
複製代碼
插入一個新節點到鏈表兩個節點中間以下圖:
須要的操做以下圖:
節點插入操做完成後的鏈表以下:
代碼邏輯以下:
if (index == _size) { // 插入到表尾 或者 空鏈表添加第一個節點
// ...
} else {
if (index == 0) { // 插入到表頭
JKRLinkedListNode *next = [self nodeWithIndex:index];
JKRLinkedListNode *prev = next.prev;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
next.prev = node;
_first = node;
prev.next = nil;
prev.weakNext = 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;
prev.weakNext = nil;
}
}
複製代碼
if (index == 0) { // 插入到表頭
JKRLinkedListNode *next = [self nodeWithIndex:index];
JKRLinkedListNode *prev = next.prev;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
next.prev = node;
_first = node;
prev.next = nil;
prev.weakNext = 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;
prev.weakNext = nil;
}
複製代碼
將相同代碼邏輯提取出來:
JKRLinkedListNode *next = [self nodeWithIndex:index];
JKRLinkedListNode *prev = next.prev;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
next.prev = node;
// 還可用 next == _first 判斷,插入到表頭即該位置的節點是鏈表的頭節點
if (index == 0) { // 插入到表頭
_first = node;
prev.next = nil;
prev.weakNext = node;
} else { // 插入到兩個節點中間
prev.next = node;
prev.weakNext = nil;
}
複製代碼
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
[self rangeCheckForAdd:index];
// index == size 至關於 插入到表尾 或者 空鏈表添加第一個節點
if (_size == index) {
JKRLinkedListNode *oldLast = _last;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:_last object:anObject next:_first];
_last = node;
// _size == 0
if (!oldLast) { // 添加鏈表第一個元素
_first = _last;
_first.prev = _first;
_first.next = nil;
_first.weakNext = _first;
} else { // 插入到表尾
oldLast.next = _last;
oldLast.weakNext = nil;
_first.prev = _last;
_last.next = nil;
_last.weakNext = _first;
}
} else { // 插入到表的非空節點的位置上
JKRLinkedListNode *next = [self nodeWithIndex:index];
JKRLinkedListNode *prev = next.prev;
JKRLinkedListNode *node = [[JKRLinkedListNode alloc] initWithPrev:prev object:anObject next:next];
next.prev = node;
// index == 0
if (next == _first) { // 插入到表頭
_first = node;
prev.next = nil;
prev.weakNext = node;
} else { // 插入到兩個節點中間
prev.next = node;
prev.weakNext = nil;
}
}
_size++;
}
複製代碼
刪除鏈表惟一的節點以下圖:
須要的操做以下圖:
代碼以下:
if (_size == 1) { // 刪除惟一的節點
_first = nil;
_last = nil;
}
複製代碼
刪除頭節點以下圖:
須要的操做以下圖:
刪除頭節點代碼以下:
if (_size == 1) { // 刪除惟一的節點
_first = nil;
_last = nil;
} else {
// 被刪除的節點
JKRLinkedListNode *node = [self nodeWithIndex:index];
// 被刪除的節點的上一個節點
JKRLinkedListNode *prev = node.prev;
// 被刪除的節點的下一個節點
JKRLinkedListNode *next = node.next;
if (node == _first) { // 刪除頭節點
prev.next = nil;
prev.weakNext = next;
next.prev = prev;
_first = next;
} else {
// ...
}
}
複製代碼
刪除頭節點以下圖:
須要的操做以下圖:
代碼以下:
if (_size == 1) { // 刪除惟一的節點
_first = nil;
_last = nil;
} else {
// 被刪除的節點
JKRLinkedListNode *node = [self nodeWithIndex:index];
// 被刪除的節點的上一個節點
JKRLinkedListNode *prev = node.prev;
// 被刪除的節點的下一個節點
JKRLinkedListNode *next = node.next;
if (node == _first) { // 刪除頭節點
prev.next = nil;
prev.weakNext = next;
next.prev = prev;
_first = next;
} else if (node == _last) { // 刪除尾節點
prev.next = nil;
prev.weakNext = next;
next.prev = prev;
_last = prev;
} else { // 刪除節點之間的節點
// ...
}
}
複製代碼
刪除鏈表節點中間的節點以下圖:
須要的操做以下圖:
代碼以下:
if (_size == 1) { // 刪除惟一的節點
_first = nil;
_last = nil;
} else {
// 被刪除的節點
JKRLinkedListNode *node = [self nodeWithIndex:index];
// 被刪除的節點的上一個節點
JKRLinkedListNode *prev = node.prev;
// 被刪除的節點的下一個節點
JKRLinkedListNode *next = node.next;
if (node == _first) { // 刪除頭節點
prev.next = nil;
prev.weakNext = next;
next.prev = prev;
_first = next;
} else if (node == _last) { // 刪除尾節點
prev.next = nil;
prev.weakNext = next;
next.prev = prev;
_last = prev;
} else { // 刪除節點之間的節點
prev.next = next;
next.prev = prev;
}
}
複製代碼
- (void)removeObjectAtIndex:(NSUInteger)index {
[self rangeCheckForExceptAdd:index];
if (_size == 1) { // 刪除惟一的節點
_first = nil;
_last = nil;
} else {
// 被刪除的節點
JKRLinkedListNode *node = [self nodeWithIndex:index];
// 被刪除的節點的上一個節點
JKRLinkedListNode *prev = node.prev;
// 被刪除的節點的下一個節點
JKRLinkedListNode *next = node.next;
if (node == _first) { // 刪除頭節點
prev.next = nil;
prev.weakNext = next;
next.prev = prev;
_first = next;
} else if (node == _last) { // 刪除尾節點
prev.next = nil;
prev.weakNext = next;
next.prev = prev;
_last = prev;
} else { // 刪除節點之間的節點
prev.next = next;
next.prev = prev;
}
}
_size--;
}
複製代碼
依然採用和雙向鏈表同樣的測試用例:
void testCirleList() {
JKRBaseList *list = [JKRLinkedCircleList new];
[list addObject:[Person personWithAge:1]];
printf("%s", [NSString stringWithFormat:@"添加鏈表第一個節點 \n%@\n\n", list].UTF8String);
[list addObject:[Person personWithAge:3]];
printf("%s", [NSString stringWithFormat:@"尾部追加一個節點 \n%@\n\n", list].UTF8String);
[list insertObject:[Person personWithAge:2] atIndex:1];
printf("%s", [NSString stringWithFormat:@"插入到鏈表兩個節點之間 \n%@\n\n", list].UTF8String);
[list insertObject:[Person personWithAge:0] atIndex:0];
printf("%s", [NSString stringWithFormat:@"插入到鏈表頭部 \n%@\n\n", list].UTF8String);
[list removeFirstObject];
printf("%s", [NSString stringWithFormat:@"刪除頭節點 \n%@\n\n", list].UTF8String);
[list removeObjectAtIndex:1];
printf("%s", [NSString stringWithFormat:@"刪除鏈表兩個節點之間的節點 \n%@\n\n", list].UTF8String);
[list removeLastObject];
printf("%s", [NSString stringWithFormat:@"刪除尾節點 \n%@\n\n", list].UTF8String);
[list removeAllObjects];
printf("%s", [NSString stringWithFormat:@"刪除鏈表惟一的節點 \n%@\n\n", list].UTF8String);
}
複製代碼
打印結果:
添加鏈表第一個節點
Size: 1 [(W 1) -> 1 -> (W 1)]
尾部追加一個節點
Size: 2 [(W 3) -> 1 -> (3), (W 1) -> 3 -> (W 1)]
插入到鏈表兩個節點之間
Size: 3 [(W 3) -> 1 -> (2), (W 1) -> 2 -> (3), (W 2) -> 3 -> (W 1)]
插入到鏈表頭部
Size: 4 [(W 3) -> 0 -> (1), (W 0) -> 1 -> (2), (W 1) -> 2 -> (3), (W 2) -> 3 -> (W 0)]
0 dealloc
刪除頭節點
Size: 3 [(W 3) -> 1 -> (2), (W 1) -> 2 -> (3), (W 2) -> 3 -> (W 1)]
2 dealloc
刪除鏈表兩個節點之間的節點
Size: 2 [(W 3) -> 1 -> (3), (W 1) -> 3 -> (W 1)]
3 dealloc
刪除尾節點
Size: 1 [(W 1) -> 1 -> (W 1)]
刪除鏈表惟一的節點
Size: 0 []
1 dealloc
複製代碼
能夠看到,全部節點都經過弱引用指向本身前一個節點,除尾節點以外,全部節點節點都經過強引用指向本身的後一個節點。尾節點的weakNext經過弱引用循環指向頭節點,頭節點通prev經過弱引用指向本身的尾節點。
經過上面添加刪除的邏輯能夠知道,雙向循環鏈表在對頭尾操做時時間複雜度同雙向鏈表,也是O(1)。對於鏈表中間的節點,同雙向鏈表也是O(n),越靠近鏈表中間查詢次數越多,越靠近鏈表頭部或尾部查詢越快。
同上一節的測試用例,對比雙向循環鏈表和雙向鏈表不一樣位置進行50000次插入刪除操做時間對比:
雙向循環鏈表操做頭節點
耗時: 0.053 s
雙向鏈表操做頭節點
耗時: 0.034 s
雙向循環鏈表操做尾節點
耗時: 0.045 s
雙向鏈表操做尾節點
耗時: 0.032 s
雙向循環鏈表操做 index = 總節點數*0.25 節點
耗時: 12.046 s
雙向鏈表操做 index = 總節點數*0.25 節點
耗時: 11.945 s
單雙向循環鏈表操做 index = 總節點數*0.75 節點
耗時: 19.340 s
雙向鏈表操做 index = 總節點數*0.75 節點
耗時: 19.162 s
雙向循環鏈表操做中間節點
耗時: 37.876 s
雙向鏈表操做中間節點
耗時: 37.862 s
複製代碼
聽說著名猶太曆史學家 Josephus有過如下的故事:在羅馬人佔領喬塔帕特後,39 個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓到,因而決定了一個自殺方式,41我的排成一個圓圈,由第1我的開始報數,每報數到第3人該人就必須自殺,而後再由下一個從新報數,直到全部人都自殺身亡爲止。然而Josephus 和他的朋友並不想聽從。首先從一我的開始,越過k-2我的(由於第一我的已經被越過),並殺掉第k我的。接着,再越過k-1我的,並殺掉第k我的。這個過程沿着圓圈一直進行,直到最終只剩下一我的留下,這我的就能夠繼續活着。問題是,給定了和,一開始要站在什麼地方纔能避免被處決?Josephus要他的朋友先僞裝聽從,他將朋友與本身安排在第16個與第31個位置,因而逃過了這場死亡遊戲。
以前使用單向循環鏈表解決約瑟夫問題,這裏使用雙向循環鏈表一樣能夠:
void useLinkedCircleList() {
JKRLinkedCircleList *list = [JKRLinkedCircleList new];
for (NSUInteger i = 1; i <= 41; i++) {
[list addObject:[NSNumber numberWithInteger:i]];
}
NSLog(@"%@", list);
JKRLinkedListNode *node = list->_first;
while (list.count) {
node = node.next;
node = node.next;
printf("%s ", [[NSString stringWithFormat:@"%@", node.object] UTF8String]);
[list removeObject:node.object];
node = node.next;
}
printf("\n");
}
複製代碼
打印順序:
3 6 9 12 15 18 21 24 27 30 33 36 39 1 5 10 14 19 23 28 32 37 41 7 13 20 26 34 40 8 17 29 38 11 25 2 22 4 35 16 31
複製代碼
最後兩個數字是16和31。
鏈表和數組如今都已經完成了,接下來能夠用這些簡單的數組結構實現以前一聽就比較厲害的數據結構:哈希表,實現以後發現其實也不是那麼的複雜呢。