美文网首页
数据结构与算法-线性表-循环链表

数据结构与算法-线性表-循环链表

作者: Joker_King | 来源:发表于2020-04-11 09:48 被阅读0次

    将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)。

    循环链表解决了一个很麻烦的问题。如何从当中一个结点出发,访问到链表的全部结点。

    空表

    为了使空链表与非空链表处理一致,我们通常设一个头结点,当然,这并不是说,循环链表一定要头结点,这需要注意。循环链表带有头结点的空链表如图所示:

    image-20200407204842506

    非空循环链表

    对于非空的循环链表就如图所示。

    image-20200407205040736

    循环链表和单链表的区别

    其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p->next是否为空,现在则是p->next不等于头结点,则循环未结束。

    在单链表中,我们有了头结点时,我们可以用O(1)的时间访问第一个结点,但对于要访问到最后一个结点,却需要O(n)时间,因为我们需要将单链表全部扫描一遍。

    有没有可能用O(1)的时间由链表指针访问到最后一个结点呢?

    不用头指针,而是用指向终端结点的尾指针来表示循环链表(如图所示),此时查找开始结点和终端结点都很方便了。

    image-20200407205347154

    从上图中可以看到,终端结点用尾指针rear指示,则查找终端结点是O(1),而开始结点,其实就是rear->next->next,其时间复杂也为O(1)

    合并两个循环链表

    我们想要把两个循环链表合并为一个表,该如何做呢?

    有了尾指针就非常简单了。比如下面的这两个循环链表,它们的尾指针分别是rearArearB

    image-20200407205724381

    要想把它们合并,只需要如下的操作即可。

    image-20200407205914723

    算法实现思路:

    1. 声明一个p指向A表的头结点p = rearA->next;
    2. 将rearA的后继指向B表的首元结点rearA->next = rearB->next->next;
    3. 将rearB的后继指向A表的头结点;

    代码实现:

    // 存储空间初始分配量
    #define MAXSIZE 20
    // ElemtType类型根据实际情况而定,这里假设为int
    typedef int ElemType;
    
    typedef struct {
        // 数组存储数据的元素,最多为MAXSIZE个
        ElemType data[MAXSIZE];
        // 线性表当前的长度
        int length;
    } SqList;
    
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    // Status是函数的类型,其值是函数结果状态代码,如OK等
    typedef int Status;
    
    // 线性表的循环链表存储结构
    typedef struct NodeTag {
        ElemType data;
        struct NodeTag *next;
    } Node;
    
    typedef struct NodeTag *LinkList;
    
    
    /// 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
    /// @param L 要初始化的链表
    /// @param n 链表的长度
    LinkList CreateListTail(LinkList *L, int n) {
        LinkList p,r;
        // 初始化头结点
        *L = (LinkList)malloc(sizeof(Node));
        // 头结点赋值大值
        (*L)->data = 1000;
        // 让头结点的后继指向他自己
        (*L)->next = *L;
        // r为指向尾结点的指针
        r = *L;
        for (int i = 0; i < n; i++) {
            // 生成新的结点
            p = (LinkList)malloc(sizeof(Node));
            // 随机生成100以内的数字
            p->data = rand() % 100 + 1;
            // 新节点的后继指向头结点
            p->next = *L;
            // 让尾结点的后继指向p
            r->next = p;
            r = p;
        }
        return r;
    }
    

    合并

    LinkList A, B;
    LinkList rearA, rearB;
    
    rearA = CreateListTail(&A, 3);
    rearB = CreateListTail(&B, 3);
    
    // 合并
    // 1、声明p,指向A表的头结点
    LinkList p;
    p = rearA->next;
    
    // 2、将rearA->next = rearB->next->next;将A表尾结点的后继指向B表的首元结点;
    rearA->next = rearB->next->next;
    // 3、将rearB->next = p;将B表的后继指向A表的头结点
    rearB->next = p;
    
    // 便利输出合并后的链表
    LinkList tmp = A->next;
    while (tmp != A) {
        printf("%d ", tmp->data);
        tmp = tmp->next;
    }
    

    总结

    • 循环链表:循环链表实在单链表的基础上,将尾结点的指针,重新指向头结点;
    • 构成了一个闭环;
    • 循环链表遍历的退出条件在于,p->next不等于头结点;
    • 单链表遍历的退出条件在于,p->next不为NULL

    相关文章

      网友评论

          本文标题:数据结构与算法-线性表-循环链表

          本文链接:https://www.haomeiwen.com/subject/zhbemhtx.html