数据结构与算法(二) -- 线性表之单向循环链表

384 阅读6分钟

一、什么是链表

链表是一种线性表, 也是一种存储数据的数据结构.

这种的一个节点中包含自身数据以及指向下一个节点的位置,一个嵌套着下一个. 这中结构就称之为链表.

二、链表的类型

开头就是最基本的链表类型 -- 单项链表. 除此之外,大致还有:

  • 单向循环链表
  • 双向链表
  • 双向循环链表

还有其他种类链表不再阐述. 这里主要来探讨一下单项循环链表.

三、单项循环链表的设计

从图中可以知道, 每一个节点需要包含两个东西: 数据与指向下一个节点的地址. 那么来设计一个这样的结构体:

typedef int Status;
typedef int LBData;
typedef struct Node {
    LBData data;//用来存储一个int数据(具体数据类型根据开发实际情况而定,此处使用int)
    struct Node *next;//指向下一个节点的指针
} Node;
typedef struct Node * LBList;

这样链表中的一个节点就构建好来. 结下来我们要将这些节点来进行创建.

四、单项循环链表的创建

(因为本人主要使用语言是面向对象的语言, 所以后面的实现均以对象的思想.) 声明一个东西首先要对它进行初始化, 一般我们再初始化的时候会将数据在初始化的时候扔进去.

LBList LBListInit(const int *data, int length) {
    LBList lb = (LBList)malloc(sizeof(Node));//定义一个节点并开辟内存(此节点为当前节点)
    if (!lb) return NULL;//开辟内存失败返回
    
    LBList headNode = lb;//记录链表的首节点,用于后面结尾指向首节点
    if (length <= 0) return headNode;
    for (int i = 0; i < length; i++) {
        lb->data = data[i];//将参数放入进当前节点
        if (i == length - 1) {//判断数据是否结尾
            return headNode;//返回链表
        }
        LBList temp = (LBList)malloc(sizeof(Node));//开辟一个节点用来当作 当前节点的下一个节点
        if (!temp) return NULL;//开辟内存失败返回
        temp->next = headNode;//最后的节点始终指向首节点
        lb->next = temp;//当前节点的下一个节点为创建的节点
        lb = temp;//将当前节点改成下一个节点
    }
    return headNode;
}

这样就可以将一个一个的节点首尾相连形成一种锁链的形式. 运行一下看看结果:

初始化一个单项循环链表成功.

五、单项循环链表的插入

得到一个链表后, 在使用时当然需要对这个链表来进行增删改查来.

首先来分析一下 “增” 是如何实现的.

  1. 首先需要得到将要插入的位置的节点A
  2. 新建一个节点B, 将值修改为需要的数据
  3. 将B的next变成A的next
  4. 将A的next指向B
  5. 特殊情况: 插入到第一个位置, 最后到一个节点需要修改next

进入代码模式:

//list: 传进来的链表‘对象’指针  data: 需要插入的数据  index: 插入到第几个位置(从1开始)
Status LBListInsert(LBList *list, LBData data, unsigned int index) {
    if (index <= 0) return 0;//不合法的位置直接返回错误
    
    LBList tempList = *list;//当前节点
    LBList insertNode = (LBList)malloc(sizeof(Node));//创建一个即将插入到节点
    if (!insertNode) return 0;//创建失败
    
    
    if (index == 1) {
        //特殊情况: 插入到第一个位置
        
        //遍历找到最后一个节点
        while (*list != tempList->next) {
            tempList = tempList->next;
        }
        insertNode->data = data;//插入的节点赋值
        insertNode->next = *list;//插入的节点的下一个即为传递过来的list指针地址
        tempList->next = insertNode;//当前节点(最后一个节点)的下一个即为插入的节点
        *list = insertNode;//将list地址修改为插入的节点地址
        return 1;
        
    } else {
    
        for (int i = 1; i < index - 1; i++) {//根据需要插入的位置来对链表进行遍历
            if (tempList->next == *list) return 0;//已经到达最后一个节点, index错误
            tempList = tempList->next;//将当前节点变成下一个节点
        }
        
        insertNode->data = data;//插入的节点赋值
        insertNode->next = tempList->next;//插入的节点的下一个即为当前节点的下一个
        tempList->next = insertNode;//当前节点的下一个变为插入的节点
        return 1;
        
    }
}

运行调试:

六、单项循环链表的删除

分析一下如何进行删除操作:

  1. 找到需要删除的节点
  2. 保留需要删除的上一个节点与下一个节点
  3. 将被删除的上一个节点的next指向被删除的下一个节点
  4. 释放被删除的节点的内存空间
  5. 特殊情况: 删除第一个节点, 同时需要修改最后一个节点

进入代码模式:


Status LBListDelete(LBList *list, unsigned int index) {

    LBList tempList = *list;//当前节点
    LBList delList = (LBList)malloc(sizeof(Node));//准备要删除的节点

    if (index <= 0) return 0;//非法参数
    
    if (index == 1) {
        //特殊情况: 删除第一个位置
        
        //找到最后一个节点
        while (*list != tempList->next) {
            tempList = tempList->next;
        }
        tempList->next = (*list)->next;//将最后一个节点的下一个设置为首节点的下一个
        *list = (*list)->next;//首节点替换为首节点的下一个
        return 1;
    } else {
        
        for (int i = 1; i < index - 1; i++) {
            tempList = tempList->next;//切换节点
            if (tempList->next == (*list)) return 0;//节点遍历结束, 非法参数
        }
        delList = tempList->next;//找到需要删除的节点
        tempList->next = delList->next;//将当前节点的下一个替换为即将删除节点的下一个
        free(delList);//释放节点
        return 1;
    }
}

运行代码调试:

七、总结

链表故名思意就是像链条一样一个嵌套一个的数据结构, 上面提到如何对链表进行了‘增’ ‘删’操作, ‘改’ ‘查’也在‘增’‘删’中有所体现.

通过对它进行操作可以发现它有个缺点,即当数据量过于庞大的时候对它进行操作会耗费大量时间来进行节点的查找.但是好处就是在内存足够的情况下可以无限延伸. 实际上也可以通过记录一些特殊节点来保证它的查找效率.

例如:

typedef struct Node {
    LBData data;//用来存储一个int数据(具体数据类型根据开发实际情况而定,此处使用int)
    struct Node *next;//指向下一个节点的指针
} Node;

typedef struct MyLB {
    struct Node *lastNode;
    struct Node *list;
} LB;

使用结构体嵌套来新增一个节点时刻记录链表的最后一个节点. 但是相对应的操作方法也要做一下改变, 这里就不做演示.