【数据结构】【王道】【线性表】循环链表的实现及基本操作(可直接运行)

本文详细介绍了循环单链表和循环双链表的实现,包括结构体定义、初始化、判断空、判断表尾结点、结点插入与删除等基本操作。循环链表的特点在于尾结点指向头结点,简化了某些操作,如删除最后一个结点。提供了完整的C语言代码示例,并附带了运行截图。

总目录

1.说明

由于循环链表尾部并未指向空,而是指向头指针。循环单链表尾结点指针域指向头结点;循环双链表尾结点后继指针指向头结点,头结点前驱指针指向尾节点。故而各类基本操作与之前单链表、双链表基本操作相差不大,主要就是在于对尾结点的处理问题,之前非循环链表尾结点指向空,如果要执行插入、删除等可能需要对后继结点取指针域的操作会导致报错,而循环链表就没有这个问题,因为它尾结点并非指向空。综上,在此处不会给出全部的基本操作,仅给出简单插入和删除作为示例,可与博主之前实现单链表、双链表的文章参照查看,改动主要是对于尾结点的处理,其余操作可参照之前文章。

单链表实现:单链表的实现及基本操作(带头结点)(可直接运行)
双链表实现:双链表的实现及基本操作(可直接运行)

2.循环单链表

2.1 基本操作

2.1.1 结构体定义

typedef struct LNode {
    int data;   // 数据域
    struct LNode *next; // 指针域
} LNode, *LinkList;

2.1.2 初始化

// 初始化
bool InitList(LinkList &L) {
    // 申请分配空间
    L = (LNode*)malloc(sizeof(LNode));
    // 分配空间失败的情况
    if(L == NULL) {
        return false;
    }
    // 头结点后继指针指向头结点本身
    L->next = L;
    return true;
}

2.1.3 判空

// 判断循环单链表是否为空
bool Empty(LinkList L) {
    // 判断头结点指针域是否指向头结点本身即可
    if(L->next == L) {
        return true;
    } else {
        return false;
    }
}

2.1.4 判断是否是表尾结点

// 判断结点p是否是表尾结点
bool isTail(LinkList L, LNode *p) {
    // 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
    if(p->next == L) {
        return true;
    } else {
        return false;
    }
}

2.1.5 结点后插入

// 指定结点后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p, int e) {
    // p不合理的情况
    if(p == NULL) {
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 内存分配失败的情况
    if(s == NULL) {
        return false;
    }
    // 此处先将要插入结点的指针域指向前一个结点的指针域,即后一个结点的地址
    // 之后再令前一个结点的指针域指向要插入结点
    // 顺序不可颠倒,否则可能出现找不到之后结点的情况
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

2.1.6 删除指定节点

// 删除指定节点
bool DeleteNode(LNode *p) {
    // p不合理的情况
    if(p == NULL) {
        return false;
    }
    // 此操作可以理解为将p节点与其后继结点进行数据域交换
    // 之后再删除其后继结点,可以视作完成了删除,且避免了寻找前驱结点的遍历操作
    // 由于是循环单链表,所以在尾部时无需对最后一个结点进行特殊处理
    // 因为循环单链表最后一个结点的后继指针指向头结点
    // 所以即使p是最后一个结点也可以处理
    LNode *q = p->next;
    p->data = p->next->data;
    p->next = q->next;
    free(q);
    return true;
}

2.1.7 输出所有元素

// 输出所有链表元素
void PrintList(LinkList L) {
    if(Empty(L)) {
        printf("the list is empty");
    }
    LNode* p = L->next;
    while(p != NULL && p != L) {
        printf("%d  ", p->data);
        if(p->next == L) {
            break;
        }
        p = p->next;
    }
    printf("\n");
}

2.2 完整代码

#include<stdio.h>
#include<stdlib.h>

typedef struct LNode {
    int data;   // 数据域
    struct LNode *next; // 指针域
} LNode, *LinkList;

// 初始化
bool InitList(LinkList &L) {
    // 申请分配空间
    L = (LNode*)malloc(sizeof(LNode));
    // 分配空间失败的情况
    if(L == NULL) {
        return false;
    }
    // 头结点后继指针指向头结点本身
    L->next = L;
    return true;
}

// 判断循环单链表是否为空
bool Empty(LinkList L) {
    // 判断头结点指针域是否指向头结点本身即可
    if(L->next == L) {
        return true;
    } else {
        return false;
    }
}

// 判断结点p是否是表尾结点
bool isTail(LinkList L, LNode *p) {
    // 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
    if(p->next == L) {
        return true;
    } else {
        return false;
    }
}

// 指定结点后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p, int e) {
    // p不合理的情况
    if(p == NULL) {
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    // 内存分配失败的情况
    if(s == NULL) {
        return false;
    }
    // 此处先将要插入结点的指针域指向前一个结点的指针域,即后一个结点的地址
    // 之后再令前一个结点的指针域指向要插入结点
    // 顺序不可颠倒,否则可能出现找不到之后结点的情况
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

// 删除指定节点
bool DeleteNode(LNode *p) {
    // p不合理的情况
    if(p == NULL) {
        return false;
    }
    // 此操作可以理解为将p节点与其后继结点进行数据域交换
    // 之后再删除其后继结点,可以视作完成了删除,且避免了寻找前驱结点的遍历操作
    // 由于是循环单链表,所以在尾部时无需对最后一个结点进行特殊处理
    // 因为循环单链表最后一个结点的后继指针指向头结点
    // 所以即使p是最后一个结点也可以处理
    LNode *q = p->next;
    p->data = p->next->data;
    p->next = q->next;
    free(q);
    return true;
}

// 输出所有链表元素
void PrintList(LinkList L) {
    if(Empty(L)) {
        printf("the list is empty");
    }
    LNode* p = L->next;
    while(p != NULL && p != L) {
        printf("%d  ", p->data);
        if(p->next == L) {
            break;
        }
        p = p->next;
    }
    printf("\n");
}

int main() {
    LinkList L;
    InitList(L);
    PrintList(L);
    InsertNextNode(L, 5);
    InsertNextNode(L, 3);
    InsertNextNode(L, 1);
    PrintList(L);
    DeleteNode(L->next->next);
    PrintList(L);
}

2.3 运行截图

在这里插入图片描述

3.循环双链表

3.1 基本操作

3.1.1 结构体定义

typedef struct DNode {
    int data;   // 数据域
    struct DNode *prior, *next; // 指针域
} DNode, *DLinkList;

3.1.2 初始化

// 初始化
bool InitDLinkList(DLinkList &L) {
    // 申请分配空间
    L = (DNode*)malloc(sizeof(DNode));
    // 分配空间失败的情况
    if(L == NULL) {
        return false;
    }
    // 头结点前驱指针与后继指针都指向头结点本身
    L->prior = L;
    L->next = L;
    return true;
}

3.1.3 判空

// 判空
bool Empty(DLinkList L) {
    // 判断头结点后继指针是否指向头结点本身即可
    if(L->next == L) {
        return true;
    } else {
        return false;
    }
}

3.1.4 判断是否是表尾结点

// 判断结点p是否是表尾结点
bool isTail(DLinkList L, DNode *p) {
    // 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
    if(p->next == L) {
        return true;
    } else {
        return false;
    }
}

3.1.5 结点后插入

// 双链表插入
// 在p结点后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
    // 无效输入
    if(p == NULL || s == NULL) {
        return false;
    }
    // 先令s后继指针指向后继结点
    // 再令后继结点的前驱指针指向s
    // 再令s前驱指针指向p
    // 最后令前驱结点后继指针指向s
    // 顺序注意不要在连接前断开即可
    s->next = p->next;
    // 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
    // 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
    p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}

3.1.6 结点后删除

// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
    // 无效输入
    if(p == NULL) {
        return false;
    }
    // 找到p的后继结点q
    DNode *q = p->next;
    // 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
    // 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
    // 用前驱结点的后继指针指向后继结点
    p->next = q->next;
    // 用q后继结点的前驱指针指向前驱结点
    q->next->prior = p;
    // 不要忘记释放空间!!!
    free(q);
    return true;
}

3.1.7 输出所有元素

// 输出所有链表元素
void PrintList(DLinkList L) {
    if(Empty(L)) {
        printf("the list is empty");
    }
    DNode* p = L->next;
    while(p != NULL && p != L) {
        printf("%d  ", p->data);
        if(p->next == L) {
            break;
        }
        p = p->next;
    }
    printf("\n");
}

3.2 完整代码

#include<stdio.h>
#include<stdlib.h>

typedef struct DNode {
    int data;   // 数据域
    struct DNode *prior, *next; // 指针域
} DNode, *DLinkList;

// 初始化
bool InitDLinkList(DLinkList &L) {
    // 申请分配空间
    L = (DNode*)malloc(sizeof(DNode));
    // 分配空间失败的情况
    if(L == NULL) {
        return false;
    }
    // 头结点前驱指针与后继指针都指向头结点本身
    L->prior = L;
    L->next = L;
    return true;
}

// 判空
bool Empty(DLinkList L) {
    // 判断头结点后继指针是否指向头结点本身即可
    if(L->next == L) {
        return true;
    } else {
        return false;
    }
}

// 判断结点p是否是表尾结点
bool isTail(DLinkList L, DNode *p) {
    // 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
    if(p->next == L) {
        return true;
    } else {
        return false;
    }
}

// 双链表插入
// 在p结点后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
    // 无效输入
    if(p == NULL || s == NULL) {
        return false;
    }
    // 先令s后继指针指向后继结点
    // 再令后继结点的前驱指针指向s
    // 再令s前驱指针指向p
    // 最后令前驱结点后继指针指向s
    // 顺序注意不要在连接前断开即可
    s->next = p->next;
    // 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
    // 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
    p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}

// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
    // 无效输入
    if(p == NULL) {
        return false;
    }
    // 找到p的后继结点q
    DNode *q = p->next;
    // 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
    // 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
    // 用前驱结点的后继指针指向后继结点
    p->next = q->next;
    // 用q后继结点的前驱指针指向前驱结点
    q->next->prior = p;
    // 不要忘记释放空间!!!
    free(q);
    return true;
}

// 输出所有链表元素
void PrintList(DLinkList L) {
    if(Empty(L)) {
        printf("the list is empty");
    }
    DNode* p = L->next;
    while(p != NULL && p != L) {
        printf("%d  ", p->data);
        if(p->next == L) {
            break;
        }
        p = p->next;
    }
    printf("\n");
}

int main() {
    DLinkList L;
    InitDLinkList(L);
    PrintList(L);
    DNode *s1 = (DNode*)malloc(sizeof(DNode));
    s1->data = 5;
    s1->next = NULL;
    DNode *s2 = (DNode*)malloc(sizeof(DNode));
    s2->data = 3;
    s2->next = NULL;
    InsertNextDNode(L, s1);
    InsertNextDNode(L, s2);
    PrintList(L);
    DeleteNextDNode(L);
    PrintList(L);
}

3.3 运行截图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Silver Star

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值