iOS程序员iOS程序猿

数据结构系列:Objective-C实现单链表

2018-11-22  本文已影响1人  ChinaChong

摘自《维基百科》

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接("links")。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。

单向链表

摘自《维基百科》

链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。

一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。

链表最基本的结构是在每个节点保存数据和到下一个节点的地址,在最后一个节点保存一个特殊的结束标记,另外在一个固定的位置保存指向第一个节点的指针,有的时候也会同时储存指向最后一个节点的指针。一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。但是也可以提前把一个节点的位置另外保存起来,然后直接访问。当然如果只是访问数据就没必要了,不如在链表上储存指向实际数据的指针。这样一般是为了访问链表中的下一个或者前一个(需要储存反向的指针,见下一篇的双向链表)节点。

这种普通的,每个节点只有一个指针的链表也叫单向链表,或者单链表,通常用在每次都只会按顺序遍历这个链表的时候(例如图的邻接表,通常都是按固定顺序访问的)。



单向链表的大致情况介绍完毕,以上就是维基百科中对于单向链表的简介。接下来我以最简单的形式实现单向链表,链表中的节点储存最基础的数据类型NSInteger,便于思考也方便初学者学习。好了,程序员的世界没有BB,直接上源码!下面就是单向链表的核心实现代码以及实例调用。

@implementation SinglyLinkedList

/**
 初始化一个单向链表
 
 @param node 链表的头节点
 @return 返回一个已初始化的单向链表
 */
- (instancetype)initWithNode:(SinglyLinkedListNode *)node {
    self = [super init];
    
    if (self) {
        self.headNode = node;
    }
    
    return self;
}

/**
 判断链表是否为空
 
 @return 为空返回YES,反之为NO
 */
- (BOOL)isEmpty {
    return self.headNode == nil;
}

/**
 获取链表拥有的总节点数
 
 @return 总节点数
 */
- (NSInteger)length {
    // cur游标(指针),用来移动遍历节点
    SinglyLinkedListNode *cur = self.headNode;
    
    // count记录数量
    NSInteger count = 0;
    
    while (cur) {
        count++;
        cur = cur.nextNode;
    }
    
    return count;
}

/**
 遍历链表
 */
- (void)travel {
    // 空链表的情况
    if ([self isEmpty]) return;
    
    SinglyLinkedListNode *cur = self.headNode;
    
    while (cur) {
        NSLog(@"%ld",cur.element);
        cur = cur.nextNode;
    }
}

/**
 头插法:在链表的头部插入节点
 
 @param item 插入的元素
 */
- (void)insertNodeAtHeadWithItem:(NSInteger)item {
    SinglyLinkedListNode *node = [[SinglyLinkedListNode alloc] initWithItem: item];
    
    node.nextNode = self.headNode;
    self.headNode = node;
}

/**
 尾插法:在链表的尾部插入节点
 
 @param item 插入的元素
 */
- (void)appendNodeWithItem:(NSInteger)item {
    SinglyLinkedListNode *node = [[SinglyLinkedListNode alloc] initWithItem: item];
    
    if ([self isEmpty]) {
        self.headNode = node;
    }
    else {
        SinglyLinkedListNode *cur = self.headNode;
        
        while (cur.nextNode) {
            cur = cur.nextNode;
        }
        
        cur.nextNode = node;
    }
}

/**
 指定位置插入节点
 
 @param item 插入的元素
 @param index 位置的索引
 */
- (void)insertNodeWithItem:(NSInteger)item atIndex:(NSInteger)index {
    
    if (index <= 0) {
        [self insertNodeAtHeadWithItem: item];
    }
    else if (index > ([self length] - 1)) {
        [self appendNodeWithItem: item];
    }
    else {
        SinglyLinkedListNode *pre = self.headNode;
        
        for (int i = 0; i < index - 1; i++) {
            pre = pre.nextNode;
        }
        
        SinglyLinkedListNode *node = [[SinglyLinkedListNode alloc] initWithItem: item];
        
        node.nextNode = pre.nextNode;
        pre.nextNode = node;
    }
}

/**
 删除节点
 
 @param item 删除的元素
 */
- (void)removeNodeWithItem:(NSInteger)item {
    SinglyLinkedListNode *cur = self.headNode;
    
    SinglyLinkedListNode *pre = [[SinglyLinkedListNode alloc] init];
    
    while (cur) {
        if (cur.element == item) {
            // 先判断此节点是否是头节点,如果是头节点
            if (cur == self.headNode) {
                self.headNode = cur.nextNode;
            }
            else {
                pre.nextNode = cur.nextNode;
            }
            cur = nil;
            break;
        }
        else {
            pre = cur;
            cur = cur.nextNode;
        }
    }
}

/**
 查询某个节点是否存在
 
 @param item 查询的元素
 @return 存在返回YES,反之为NO
 */
- (BOOL)searchNodeWithItem:(NSInteger)item {
    SinglyLinkedListNode *cur = self.headNode;
    
    while (cur) {
        if (cur.element == item) {
            return YES;
        }
        else {
            cur = cur.nextNode;
        }
    }
    
    return NO;
}

@end

实际使用案例


- (void)viewDidLoad {
    [super viewDidLoad];
    
    SinglyLinkedList *lkList = [[SinglyLinkedList alloc] initWithNode: nil];
    
    NSLog(@"%d", [lkList isEmpty]); // 1
    NSLog(@"%ld", [lkList length]); // 0
    
    [lkList appendNodeWithItem: 1];
    NSLog(@"%d", [lkList isEmpty]); // 0
    NSLog(@"%ld", [lkList length]); // 0
    
    [lkList appendNodeWithItem: 2];
    [lkList insertNodeAtHeadWithItem: 8];
    [lkList appendNodeWithItem: 3];
    [lkList appendNodeWithItem: 4];
    [lkList appendNodeWithItem: 5];
    
    [lkList appendNodeWithItem: 6];                 // 8 1 2 3 4 5 6
    [lkList insertNodeWithItem: 9   atIndex: -1];   // 9 8 1 2 3 4 5 6
    [lkList insertNodeWithItem: 100 atIndex: 3];    // 9 8 1 100 2 3 4 5 6
    [lkList insertNodeWithItem: 200 atIndex: 10];   // 9 8 1 100 2 3 4 5 6 200
    [lkList removeNodeWithItem: 100];               // 9 8 1 2 3 4 5 6 200
    
    [lkList travel];
    
    NSLog(@"result: %d", [lkList searchNodeWithItem:200]);
}

寡人的思路


单向循环链表

摘自《维基百科》

在一个循环链表中, 首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环链表可以被视为“无头无尾”。这种列表很利于节约数据存储缓存, 假定你在一个列表中有一个对象并且希望所有其他对象迭代在一个非特殊的排列下。

循环链表中第一个节点之前就是最后一个节点,反之亦然。循环链表的无边界使得在这样的链表上设计算法会比普通链表更加容易。对于新加入的节点应该是在第一个节点之前还是最后一个节点之后可以根据实际要求灵活处理,区别不大(详见下面实例代码)。当然,如果只会在最后插入数据(或者只会在之前),处理也是很容易的。

另外有一种模拟的循环链表,就是在访问到最后一个节点之后的时候,手工的跳转到第一个节点。访问到第一个节点之前的时候也一样。这样也可以实现循环链表的功能,在直接用循环链表比较麻烦或者可能会出现问题的时候可以用。

@implementation SinglyCycleLinkedList

/**
 初始化一个单向循环链表

 @param node 链表的头节点
 @return 返回一个已初始化的单向循环链表
 */
- (instancetype)initWithNode:(SinglyCycleLinkedListNode *)node {
    self = [super init];
    
    if (self) {
        self.headNode = node;

        // 判断node不为空的情况,循环指向自己
        if (node) {
            node.nextNode = node;
        }
    }
    return self;
}

/**
 判断链表是否为空

 @return 为空返回YES,反之为NO
 */
- (BOOL)isEmpty {
    return self.headNode == nil;
}

/**
 获取链表拥有的总节点数

 @return 总节点数
 */
- (NSInteger)length {
    if ([self isEmpty]) return 0;
    
    SinglyCycleLinkedListNode *cur =  self.headNode;
    
    NSInteger count = 1;
    
    while (cur.nextNode != self.headNode) {
        count++;
        cur = cur.nextNode;
    }
    
    return count;
}

/**
 遍历链表
 */
- (void)travel {
    // 空链表的情况
    if ([self isEmpty]) return;
    
    SinglyCycleLinkedListNode *cur = self.headNode;
    
    while (cur.nextNode != self.headNode) {
        NSLog(@"%ld", cur.element);
        cur = cur.nextNode;
    }
    
    // 退出循环,cur指向尾节点,但尾节点的元素未打印
    NSLog(@"%ld", cur.element);
}

/**
 头插法:在链表的头部插入节点

 @param item 插入的元素
 */
- (void)insertNodeAtHeadWithItem:(NSInteger)item {
    SinglyCycleLinkedListNode *node = [[SinglyCycleLinkedListNode alloc] initWithItem: item];
    
    if ([self isEmpty]) {
        self.headNode = node;
        node.nextNode = node;
    }
    else {
        SinglyCycleLinkedListNode *cur = self.headNode;
        
        while (cur.nextNode != self.headNode) {
            cur = cur.nextNode;
        }
        
        // 利用循环将游标指向尾节点,退出循环
        node.nextNode = self.headNode;
        self.headNode = node;
        cur.nextNode = self.headNode; // cur.nextNode = node;
    }
}

/**
 尾插法:在链表的尾部插入节点

 @param item 插入的元素
 */
- (void)appendNodeWithItem:(NSInteger)item {
    SinglyCycleLinkedListNode *node = [[SinglyCycleLinkedListNode alloc] initWithItem: item];
    
    if ([self isEmpty]) {
        self.headNode = node;
        node.nextNode = node;
    }
    else  {
        SinglyCycleLinkedListNode *cur = self.headNode;
        
        while (cur.nextNode != self.headNode) {
            cur = cur.nextNode; // 让游标指向尾节点
        }
        
        cur.nextNode = node;
        node.nextNode = self.headNode;
        
    }
}

/**
 指定位置插入节点

 @param item 插入的元素
 @param index 位置的索引
 */
- (void)insertNodeWithItem:(NSInteger)item atIndex:(NSInteger)index {
    if (index <= 0) {
        [self insertNodeAtHeadWithItem: item];
    }
    else if (index > ([self length] - 1)) {
        [self appendNodeWithItem:item];
    }
    else {
        // 这里需要的就是让游标指向前一个节点
        SinglyCycleLinkedListNode *pre = self.headNode;
        
        for (int i = 0; i < index - 1; ++i) {
            pre = pre.nextNode;
        }
        
        SinglyCycleLinkedListNode *node = [[SinglyCycleLinkedListNode alloc] initWithItem: item];
        node.nextNode = pre.nextNode;
        pre.nextNode = node;
    }
}

/**
 删除节点

 @param item 删除的元素
 */
- (void)removeNodeWithItem:(NSInteger)item {
    if ([self isEmpty]) return;
    
    SinglyCycleLinkedListNode *cur = self.headNode;
    SinglyCycleLinkedListNode *pre = [[SinglyCycleLinkedListNode alloc] init];
    
    // 这个循环的终点就是cur指向尾节点
    while (cur.nextNode != self.headNode) {
        if (cur.element == item) {
            if (cur == self.headNode) // 恰好这个cur指向的是头节点
            {
                // 我们需要先找到尾节点
                SinglyCycleLinkedListNode *rear = self.headNode;
                while (rear.nextNode != self.headNode) {
                    rear = rear.nextNode;
                }
                
                self.headNode = cur.nextNode;
                rear.nextNode = self.headNode;
                
            }
            else {
                pre.nextNode = cur.nextNode;
            }
            return;
        }
        else {
            pre = cur;
            cur = cur.nextNode;
        }
    }
    
    // 退出循环,cur指向尾节点
    if (cur.element == item) {
        if (cur == self.headNode) // 证明链表只有一个头节点
        {
            self.headNode = nil;
        }
        else {
            pre.nextNode = cur.nextNode;
        }
    }
}

/**
 查询某个节点是否存在

 @param item 查询的元素
 @return 存在返回YES,反之为NO
 */
- (BOOL)searchNodeWithItem:(NSInteger)item {
    if ([self isEmpty]) return NO;
    
    SinglyCycleLinkedListNode *cur = self.headNode;
    
    while (cur.nextNode != self.headNode) {
        if (cur.element == item) {
            return YES;
        }
        else {
            cur = cur.nextNode;
        }
    }
    
    // 循环结束,cur指向尾节点
    if (cur.element == item) {
        return YES;
    }
    
    return NO;
}

@end

实际使用案例

- (void)viewDidLoad {
    [super viewDidLoad];
    
    SinglyCycleLinkedList *ll = [[SinglyCycleLinkedList alloc] initWithNode: nil];
    
//    NSLog(@"%d", [ll isEmpty]); // 1
//    NSLog(@"%ld", [ll length]); // 0
    
    [ll appendNodeWithItem: 1];
//    NSLog(@"%d", [ll isEmpty]); // 0
//    NSLog(@"%ld", [ll length]); // 1
    
    [ll appendNodeWithItem: 2];
    [ll insertNodeAtHeadWithItem: 8];
    [ll appendNodeWithItem: 3];
    [ll appendNodeWithItem: 4];
    [ll appendNodeWithItem: 5];
    [ll appendNodeWithItem: 6];
//    [ll travel]; // 8 1 2 3 4 5 6

    [ll insertNodeWithItem: 9 atIndex: -1];
//    [ll travel]; // 9 8 1 2 3 4 5 6

    [ll insertNodeWithItem: 100 atIndex: 3];
//    [ll travel]; // 9 8 1 100 2 3 4 5 6

    [ll insertNodeWithItem: 200 atIndex: 10];
//    [ll travel]; // 9 8 1 100 2 3 4 5 6 200

    [ll removeNodeWithItem: 100];
    [ll travel]; // 9 8 1 2 3 4 5 6 200
    
    NSLog(@"result: %d", [ll searchNodeWithItem:200]); // result: 1
}

寡人的思路


Github完整源码

单向链表
单向循环链表


参考资料

链表-维基百科,自由的百科全书

上一篇 下一篇

猜你喜欢

热点阅读