Two-way circular linked list data structure 7-

It has been previously achieved unidirectional circular linked list , and two-way circular linked list of singly linked list is very similar principles: next the tail node to the head node list. On this basis, PREV the head node to the tail node, thus achieving two-way circular linked list. Also, to prevent circular reference, the end point node of the head node to use weak references.

Two-way circular linked list of nodes

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

Add Nodes

Adding nodes and two-way circular list doubly linked list is basically the same, just more of prev next node of the head node and tail maintenance operations.

Add the first node in the list

Comparative doubly linked, circular list, except that the two-way linked list head node and tail node points to the new node, but also need to prev node, weakNext point itself.

Code logic is as follows:

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

Append the tail of a linked list node

Alternatively the newly added node original tail node called the new tail node:

FIG operation requires the following:

  • The newly added nodes prev points to the original list the tail node.
  • End of the list last node pointer points to the newly added node.
  • The original list tail node points to the new tail next node (i.e., the newly added node) of the current list.
  • prev list head node to the new node is added.
  • weakNext tail node points to the head node of the newly added list.
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;
}
复制代码

Add the first node and the tail node code for additional integration

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

The above two same code determination logic combined, separate different determination logic:

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

The proposed out of the same code:

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

Inserted into the head of the list

Insert a new node to the head of the list below:

FIG operation requires the following:

  • New node points prev prev original head node.
  • next new node to the original head node.
  • prev original head node point to the new node.
  • first pointer list points to the new node.
  • prev original head node (i.e., the tail node) weakNext point to the new head node.

After the completion of the operation node into the list below:

Code logic is as follows:

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

Inserted into the list of the intermediate node

Insert a new node to the two intermediate nodes list below:

FIG operation requires the following:

  • First acquires the node corresponding to the insertion position index.
  • Prev insertion point to the new node linked list of nodes prev original position.
  • The new node to the next linked list node original insertion position.
  • The insertion position of the node list prev original point to the new node.
  • A linked list insert node before the original position of the next node point to the new node.

After the completion of the operation node into the list below:

Code logic is as follows:

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

Code logic integrated into a table position of non-empty node

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

The same code logic extract:

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

Adding a node Code summary

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

Delete Node

The only delete nodes

The only delete the list of nodes as shown below:

FIG operation requires the following:

  • The head node list points to null
  • The null node pointing to the tail of the list

code show as below:

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

Delete the head node

Remove the head node as shown below:

FIG operation requires the following:

  • Deleted node on a node (tail node) of the node point to the deleted weakNext next node.
  • After being deleted node node node prev points to be deleted before a node.
  • Head node list of points to be deleted node to the next node.

Delete the head node code is as follows:

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

Delete the end node

Remove the head node as shown below:

FIG operation requires the following:

  • The original node before a tail node (new tail node) pointing to the next original weakNext end node (the first node).
  • After a node prior to a node of the original end node (the first node) pointing to the original tail node prev (new tail node).
  • A linked list of nodes before the last point to the original tail node tail node (a new tail node).

code show as below:

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 { // 删除节点之间的节点
        // ...
    }
}
复制代码

Middle delete nodes list

The intermediate node deletes the node list below:

FIG operation requires the following:

  • One of the nodes are removed before the next point to be deleted node next.
  • After being deleted node node prev points to be deleted node prev.

code show as below:

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

Adding a node Code summary

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

test

Still using the same test cases and doubly linked lists:

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

Print Results:

添加链表第一个节点 
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
复制代码

It can be seen by all nodes before a weak reference pointing to its own node, in addition to the end nodes, all nodes by pointing to their strong references to a node. weakNext tail node cycle by a weak reference point head node, through the head node prev through weak references to their tail node.

Time complexity analysis

Remove added by the above logic can know, at the time of two-way circular linked list head and tail with the operation time complexity doubly linked list is O (1). For intermediate node list, with the doubly linked list is O (n), the closer to the middle of the list queries, the more close to the head or tail faster the query list.

Ditto a test case, comparison of different position two-way circular list doubly linked list and delete operations performed 50,000 times insert time comparison:

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

Circular list of applications: Joseph problems

It is said that the famous Jewish historian Josephus had the following story: After the Roman occupation Qiaotapate, 39 Jews and Josephus and his friends hid in a cave, 39 Jews decide would rather die and do not get caught enemies and decided a suicide, 41 individuals arranged in a circle, the first personal Countin, every third number reported to the people who have to commit suicide, then a re-count by the newspaper, until all suicide killed so far. However, Josephus and his friends did not want to comply. Start with a person, beyond the individual k-2 (for the first man has been crossed), and kill the k individuals. Next, k-1 and then over the individual, and the k-th individual kill. The process continues around the circle, until finally only the next person to leave, this person can continue to live. The problem is that, given and, beginning to stand somewhere in order to avoid execution? Josephus wanted his friend to pretend to comply, he will arrange with his friends in the 16th and 31st position, then escaped this death game.

Before using the one-way circular list Joseph problems to solve, where the use of two-way circular list can also:

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

Print order:

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

The last two figures are 16 and 31.

Source

Click to view source code

Guess you like

Origin blog.csdn.net/weixin_34220623/article/details/91399657