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

307 阅读6分钟

循环链表

循环链表就是将单向链表中最后一个结点的指针指向头结点(首元结点),使整个链表构成一个环形,这样的结构使得从表中的任一结点出发都能访问到整个链表。

循环链表创建

typedef int Status; // Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int ElemType;// ElemType类型根据实际情况而定,这里假设为int

// 定义结点
typedef struct Node{
    ElemType data;
    struct Node *next;
}Node;

typedef Node * LinkList;

/*
 3.1 循环链表创建(尾插法) (不考虑头结点,头指针指向的是首元结点)
 2种情况:① 第一次开始创建; ②已经创建,往里面新增数据
 
 1. 判断是否第一次创建链表
    YES->创建一个新结点,并使得新结点的next 指向自身; (*L)->next = (*L);
    NO-> 找链表尾结点,将尾结点的next = 新结点. 新结点的next = (*L);
 */
Status CreateList(LinkList *L){
    LinkList temp = NULL;
    LinkList target = NULL;
    printf("输入节点的值,输入0结束\n");
    int n;
    while (1) {
        scanf("%d",&n);
        if(n==0) break;
        
        //如果输入的链表是空。则创建一个新的节点,使其next指针指向自己(首元结点) (*head)->next=*head;
        if(*L==NULL){
            *L = (LinkList)malloc(sizeof(Node));
            if(!L) return ERROR;
            (*L)->data = n;
            (*L)->next = (*L);
        } else {
            
            //输入的链表不是空的,寻找链表的尾节点,使尾节点的next=新节点。新节点的next指向首元节点
            // 找尾结点target
            target = (*L);
            while (target->next != (*L)) {
                target = target->next;
            }
            
            temp = (LinkList)malloc(sizeof(Node));
            if (!temp) return ERROR;
            temp->data = n;
            temp->next = (*L);      //新节点指向首元节点
            target->next = temp;    //尾节点指向新节点
        }
    }
    
    return OK;
}
//  方法2
Status CreateList2(LinkList *L){
    LinkList temp = NULL;
    LinkList target = NULL;
    printf("输入节点的值,输入0结束\n");
    int n;
    while (1) {
        scanf("%d",&n);
        if(n==0) break;
        
        //如果输入的链表是空。则创建一个新的节点,使其next指针指向自己(首元结点) (*head)->next=*head;
        if(*L==NULL){
            *L = (LinkList)malloc(sizeof(Node));
            if(!L) return ERROR;
            (*L)->data = n;
            (*L)->next = (*L);
            target = (*L);
        } else {
            
            temp = (LinkList)malloc(sizeof(Node));
            if (!temp) return ERROR;
            temp->data = n;
            temp->next = (*L);      //新节点指向首元节点
            target->next = temp;    //尾节点指向新节点
            
            target = temp;      //目标结点 指向尾结点
        }
    }
    
    return OK;
}

循环链表遍历

//3.6 遍历循环链表,循环链表的遍历最好用do while语句,因为头节点就有值
Status printList(LinkList L) {
    
    printf("开始遍历链表结点数据:\n");
    //如果链表是空
    if(L == NULL){
        printf("打印的链表为空!\n");
    }else {
        
        LinkList p = L;
        do {
            printf("%5d\n", p->data);
            p = p->next;
        }while (p!= L);
        
        /*
         第二种遍历方式
         */
//        LinkList p = L;
//        while (p->next != L) {
//            printf("%5d",p->data);
//            p = p->next;
//        }
//        // 最后尾结点需要单独打印
//        printf("%5d\n",p->data);
        

        /*
        第三种遍历方式
        */
//        for (LinkList p = L; p->next!=L; p=p->next) {
//            printf("%5d\n", p->data);
//        }
//        // 最后尾结点需要单独打印
//        printf("%5d\n", p->data);

        printf("\n");
    }
    return OK;
}

循环链表插入数据

//3.3 循环链表插入数据
Status ListInsert(LinkList *L, int place, ElemType num){
    
    LinkList temp, target;
    
    if (place==1) {
        // 如果插入的位置为0,则属于插入首元结点,所以需要特殊处理
        // 创建新结点
        temp = (LinkList)malloc(sizeof(Node));
        
        if (!temp) return ERROR;
        temp->data = num;
        // 新结点后继指向 首元结点
        temp->next = *L;
        // 找到尾结点
        for(target = (*L); target->next != (*L); target = target->next);
        // 尾结点后继指向 新结点temp
        target->next = temp;
        // 头指针指向 该新结点
        *L = temp;
    } else {
        //如果插入的位置在其他位置;
        temp = (LinkList)malloc(sizeof(Node));
        if (!temp) return ERROR;
        temp->data = num;
        // 判断插入位置大于链表长度,则插入到尾结点之后
        int length;
        for(target = (*L), length = 1; target->next != *L; target = target->next, length++);
        if (place > length) {
            // 此时target为尾结点, *L即为target->next
            // 新结点后继指向首元结点
            temp->next = *L;
            // 尾结点后继指向新结点
            target->next = temp;
        } else {
            // 找到插入位置的前一个结点
            for(target = (*L), length = 1; length != place-1; target = target->next, length++);
            // 新结点后继指向 插入位置的后继结点
            temp->next = target->next;
            // c待插入位置的后继 指向新结点
            target->next = temp;
        }
        
        /*
         // 以上if判断可以合并成以下
         int length;
         for (target = (*L), length = 1; target->next != *L && length != place-1; target = target->next,length++);
         temp->next = target->next;
         target->next = temp;
         */
        
    }
    
    return OK;
}

循环链表删除元素

//3.4 循环链表删除元素
Status LinkListDelete(LinkList *L,int place){
        
    LinkList temp, target;
    int length;
    temp = *L;
    
    if (*L == NULL) return ERROR;
    // 如果待删除位置是首元结点
    if (place == 1) {
        //.如果删除到只剩下首元结点了,则直接将*L置空;
        if (temp->next == *L) {
            *L = NULL;
            return OK;
        }
        //.如果链表还有很多数据,但是删除的是首结点;
        // 找到尾结点
        for(target = (*L); target->next != (*L); target = target->next);
        // 尾结点的后继 指向 首元结点的后继
        target->next = (*L)->next;
        // 将首元结点 赋给 temp
        temp = (*L);
        // 将头指针 指向 原首元结点的后继
        (*L) = (*L)->next;
        // 释放temp,即原首元结点
        free(temp);
        
    } else {
    // 待删除为非首元结点
        // 判断插入位置大于链表长度,则删除尾结点
        // 找到尾结点的前一个结点
        for(target = (*L), length = 1; target->next->next != *L; target = target->next, length++);
        if (place > length) {
            // 此时target为尾结点的前一个结点, *L即为target->next->next
            temp = target->next;
            // 尾结点的前一个结点后继指向 首元结点 *L
            target->next = *L; // 即temp->next
            free(temp);
        } else {
            // 找到删除位置的前一个结点
            for(target = (*L), length = 1; length != place-1; target = target->next, length++);
            // temp 为待删除结点
            temp = target->next;
            // 待删除结点的前一个结点 的后继 指向待删除结点的后继结点
            target->next = temp->next;
            // 释放待删除结点
            free(temp);
        }
    }
    
    return OK;
}

循环链表查询值

//3.5 循环链表查询值
int findValue(LinkList L,ElemType value){
    int i = 1;
    LinkList p;
    p = L;
    
    //寻找链表中的结点 data == value
    do {
        p = p->next;
        i++;
    } while (p->data!=value && p->next != L);
    
    //没找到值
    if (p->next == L && p->data != value) {
        return  -1;
    }
    
    return i;
}

问题

关于以上方法入参(&L, L)的不同之处,详情参考C指针详解(经典,非常详细)

struct Node *LinkList; // 存储节点实体在内存中的地址,所以指针->实体节点
LinkList L;
CreateList(&L); // 传指针L的地址 : 指针的指针
.
.
Void CreateList(LinkList *pL)
{
    // pL 已经不是上面的L *pL 才是上面的L
    // 所以到这里, pL 就是指向L的指针 L指向实体节点
    // malloc 函数返回的开辟的内存空间的地址,所以要用指针接收
    *pL = (LinkList)malloc(sizeof(Node));
    // *pL 里存的就是节点的内存地址
    // 所以pL里存的是节点的内存地址的地址
}

源码地址,欢迎Star github数据结构与算法