双方向循環リンクリストデータ構造、7-

それは以前に達成された一方向円形のリンクリストを、そして単独でリンクされたリストの双方向円形のリンクリストは、非常に類似した原理である:テール・ノードの次のヘッドノードリストに。これに基づき、前のテール・ノードのヘッドノードは、このような双方向循環リンクリストを達成します。また、循環参照を防ぐために、ヘッドノードの終点ノードは、弱参照を使用します。

ノードの双方向循環リンクリスト

#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
复制代码

ノードの追加

加算ノードと双方向循環リスト、二重リンクリストは、基本的には、ヘッドノードとテールメンテナンス操作の前のちょうどより、同一の次のノードです。

リストの最初のノードを追加します。

比較の二重にリンクされ、円形のリスト、新しいノードへの双方向リンクリストのヘッドノードおよび末尾ノード点を除いて、ほかのノード、weakNextポイント自体を前にする必要があります。

次のようにコードのロジックは次のとおりです。

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;
}
复制代码

リンクリストのノードの尾を追加

また、新たに追加されたノードの元テール・ノードは、新しいテール・ノードと呼ばれます。

図の操作は、次のものが必要です。

  • 元のリストに新たに追加されたノードの前のポイントのテール・ノード。
  • 新しく追加されたノードへの最後のノードポインタはリストの最後。
  • 現在のリストの新しいテール次のノード(すなわち、新たに追加されたノード)に元のリストの末尾ノード点。
  • 新しいノードに前のリストのヘッドノードが追加されます。
  • 新しく追加されたリストのヘッドノードへweakNextテール・ノードを指します。
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;
}
复制代码

合わせた上記の二つの同一のコード決意ロジック、別個の異なる決意ロジック:

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;
    }
}
复制代码

リストの先頭に挿入

以下のリストの先頭に新しいノードを挿入します。

図の操作は、次のものが必要です。

  • 新しいノードポイントは、前のオリジナルのヘッドノードを前。
  • オリジナルのヘッドノードの隣に新しいノード。
  • 新しいノードにオリジナルのヘッドノードのポイントを前。
  • 最初のポインタリストは、新しいノードを指します。
  • 前の元のヘッドノード(すなわち、テール・ノード)新しいヘッドノードへweakNextポイント。

以下のリストに動作ノードの完了後:

次のようにコードのロジックは次のとおりです。

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 { // 插入到两个节点中间
        
    }
}
复制代码

中間ノードのリストに挿入

下の2つの中間ノードリストに新しいノードを挿入します。

図の操作は、次のものが必要です。

  • 最初の挿入位置指標に対応するノードを取得します。
  • ノードの新しいノードリンクリスト前の元の位置に前の挿入ポイント。
  • 次のリンクリストノード原稿挿入位置に新しいノード。
  • 新しいノードにノードリスト前原点の挿入位置。
  • リンクリストに新しいノードに次のノードポイントの元の位置の前にノードを挿入します。

以下のリストに動作ノードの完了後:

次のようにコードのロジックは次のとおりです。

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++;
}
复制代码

ノードを削除します。

ノードだけを削除

以下に示すようにノードのみのリストを削除します。

図の操作は、次のものが必要です。

  • nullにヘッドノードリストのポイント
  • リストの末尾を指すヌルノード

コードは以下の通りであります:

if (_size == 1) { // 删除唯一的节点
    _first = nil;
    _last = nil;
} 
复制代码

ヘッドノードを削除します。

以下に示すように、ヘッドノードを削除します。

図の操作は、次のものが必要です。

  • 削除weakNext次のノードにノード点のノード(テール・ノード)上のノードを削除しました。
  • ノードnodeノードPREVポイントを削除された後、ノードの前に削除されます。
  • ポイントのヘッドノードリストは、次のノードにノードを削除します。

ヘッドノードコードを削除し、以下の通りであります:

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 {
        // ...
    }
}
复制代码

エンド・ノードを削除します。

以下に示すように、ヘッドノードを削除します。

図の操作は、次のものが必要です。

  • 次の原稿weakNextエンドノードを指すテール・ノード(新しいテール・ノード)の前に、元のノード(最初のノード)。
  • 元のテール・ノードの前(新しいテール・ノード)を指す元のエンドノードのノードの前のノード(最初のノード)の後。
  • オリジナルのテール・ノードのテール・ノード(新しいテール・ノード)への最後のポイントの前のノードのリンクリスト。

コードは以下の通りであります:

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を削除します。

コードは以下の通りであります:

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テイルノードサイクル。

時間の複雑さの解析

除去知ることができ、動作時間の複雑性と双方向円形のリンクリストのヘッドとテールの時、上記ロジックによって添加二重リンクリストO(1)です。中間ノードリストは、二重リンクリストを速くクエリリスト頭部または尾にリストクエリの中央に近いほど、より近い、O(N)です。

同上テストケース、異なった位置の比較双方向循環リスト二重連結リスト及び操作は50,000倍の時間比較を挿入実行削除します。

双向循环链表操作头节点
耗时: 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
复制代码

アプリケーションの循環リスト:ジョセフ問題

有名なユダヤ人の歴史家ヨセフスは、次の物語を持っていたと言われて:Qiaotapate、39人のユダヤ人とヨセフスと彼の友人が洞窟に隠れ、ローマ占領した後、39人のユダヤ人は、むしろ死んでしまう決めて、敵を巻き込まないでくださいそして自殺、円形に配置された41人の個人、最初の個人的なCountinを決め、すべての3番目の数字は、すべての自殺まで、新聞による再カウント、その後、自殺を持っている人に報告しましたこれまでに死亡しました。しかし、ヨセフスと彼の友人は従わたくありませんでした。個々のk-2(最初の男が逆になっているため)を超えて、人と起動し、k個の個人を殺します。次に、K-1、次いで、個々の上、及びk番目の個体を殺します。最終的には唯一の次の人は残すようになるまでのプロセスは、円の周り続けて、この人は生き続けることができます。問題は、与えられたと、実行を避けるために、どこかに立って始めて、ということでしょうか?ヨセフスは、彼の友人は、彼が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 
复制代码

最後の2つの図は、16と31です。

ソース

ソースコードを見るためにクリック

おすすめ

転載: blog.csdn.net/weixin_34220623/article/details/91399657