【数据结构与算法】之单向循环链表的建立/遍历/插⼊/查找/删除算法实现

单向循环链表简介

  • 单向循环链表是一个收尾相接的链表,将单链表的最后一个指针域改由NULL改成指向表头结点这就是单链式的循环链表,并称为循环单链表。
    在这里插入图片描述web

  • 带头结点的循环单链表的各类操做的算法实现与带头结点单链表的算法实现相似,差异仅在于算法判别当前结点p是否为尾结点的条件不一样。单链表中的判别条件为p!=NULL或p->next!=NULL,而单循环链表判别条件是p!=L或p->next!=L。算法

  • 在循环单链表中附设尾指针有时候比附设头指针更简单。如:在用头指针的循环单链表中找a1的时间复杂度是O(1),找an须要从头找到尾,时间复杂度是O(n),若是用为指针rear,找开始结点和终端结点的存储位置分别是rear->next->next和rear。svg

单向循环链表的建立

1、思路
  • 第一次建立:若是链表为空,建立新的结点,并将新结点的next指向自身((*L)->next = (*L)),不然寻找尾结点;
  • 不是第一次建立:找到链表结尾的位置,将尾节点next指向新结点,并将新结点的next等于首元节点(新结点的next = (*L));
  • 空表时:
    在这里插入图片描述
*L = (LinkList)malloc(sizeof(Node));
    (*L)->data = item;
    (*L)->next = *L;
  • 插入一个结点
    在这里插入图片描述
temp->data = item;
	// 新节点指向头结点
    temp->next = *L;  
    // 尾节点指向新结点
    target->next = temp;
  • 插入两个结点
    在这里插入图片描述
temp->data = item;
	// 新节点指向头结点
    temp->next = *L;  
    // 尾节点指向新结点
    target->next = temp;
2、建立
  • 定义一个ElemType和结构体
// ElemType类型根据实际状况而定,这里假设为int
typedef int ElemType;

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

typedef struct Node * LinkList;
  • 建立链表
// 方法一
void CreateList(LinkList *L) {
 	int item;
    LinkList temp = NULL;
    LinkList target = NULL;
    printf("Please input data and enter 0 end:\n");
    while(1) {
        scanf("%d",&item);
        if(item == 0)
            break;
        // 若是输入的链表是空,则建立一个新的结点,使其next指针指向本身:(*head)->next=*head
        if(*L == NULL) {
            *L = (LinkList)malloc(sizeof(Node));
            if(!L) {
               exit(0);
            }
            (*L)->data = item;
            (*L)->next = *L;
        } else {
           // 输入的链表不是空的,寻找链表的尾结点,使尾结点的next=新结点,新结点的next指向头结点
            for (target = *L; target->next != *L; target = target->next);
            temp = (LinkList)malloc(sizeof(Node));
            if(!temp) {
                return;
            }
            temp->data = item;
            // 新节点指向头结点
            temp->next = *L;
            // 尾节点指向新结点
            target->next = temp;
        }
    }
}
// 方法二
void CreateList(LinkList *L){
    
    int item;
    LinkList temp = NULL;
    LinkList r = NULL;
    printf("Please input data and enter 0 end:\n");
    while (1) {
        scanf("%d",&item);
        if (item == 0) {
            break;
        }
        
        // 第一次建立
        if(*L == NULL){
            *L = (LinkList)malloc(sizeof(Node));
            if(!*L) {
                return;
            }
            (*L)->data = item;
            (*L)->next = *L;
            r = *L;
        } else {
            temp = (LinkList)malloc(sizeof(Node));
            if(!temp) {
                return;
            }
            temp->data = item;
            temp->next = *L;
            r->next = temp;
            r = temp;
        }
        
    }
}

单向循环链表的遍历

// 遍历循环链表,循环链表的遍历最好用do while语句,由于头结点就有值
void show(LinkList p) {
    // 若是链表是空
    if(p == NULL){
        printf("打印的链表为空!\n");
        return;
    } else {
        LinkList temp;
        temp = p;
        do {
            printf("%5d",temp->data);
            temp = temp->next;
        } while (temp != p);
        printf("\n");
    }
}

单向循环链表的插入

1、思路

插入位置在首元节点
  • 判断插入位置是否在首元结点1上;
  • 建立新结点2,并赋值给新结点;
  • 将新结点2的next指向首元结点1(图中1⃣️);
  • 找到链表的尾结点20,将20的next指向新结点2(图中2⃣️);
  • 将头结点next指向新结点2(图中3⃣️);

在这里插入图片描述

插入位置在其余结点
  • 建立新结点2,并赋值给新结点;
  • 找到插入位置的前一个结点的target(图中1⃣️);
  • 将新结点2的next指向target的下一个结点18(图中2⃣️);
  • 将target的next指向新结点2(图中3⃣️);
    在这里插入图片描述
2、插入
// 循环链表插入数据
void ListInsert(LinkList *L, int place, int num) {
    
    LinkList temp ,target;
    int i;
    // 若是插入的位置为1,则属于插入首元结点,因此须要特殊处理
    if (place == 1) {
        // 建立新结点temp,并判断是否建立成功,成功则赋值,不然直接return;
        temp = (LinkList)malloc(sizeof(Node));
        if (temp == NULL) {
            return;
        }
        // 找到链表最后的结点_尾结点
        temp->data = num;
        for (target = *L; target->next != *L; target = target->next);
        
        // 让新结点的next执行头结点
        temp->next = *L;
        // 尾节点的next 指向新的头结点
        target->next = temp;
        // 让头指针指向temp(临时的新节点)
        *L = temp;
    } else { // 若是插入的位置在其余位置
        // 建立新结点temp,并判断是否建立成功,成功则赋值,不然return
        temp = (LinkList)malloc(sizeof(Node));
        if (temp == NULL) {
            return;
        }
        // 先找到插入的位置,若是超过链表长度,则自动插入队尾
        temp->data = num;
        for ( i = 1,target = *L; target->next != *L && i != place - 1; target = target->next,i++) ;
        // 经过target找到要插入位置的前一个结点, 让target->next = temp;
        temp->next = target->next;
        // 插入结点的前驱指向新结点,新结点的next 指向target原来的next位置
        target->next = temp;
    }
}

单向循环链表的删除

1、思路

删除首元节点
  • 若是删除到只剩下首元结点了,则直接将*L置空;
  • 链表还有不少数据,可是删除的是首结点:
    找到尾结点, 使得尾结点next 指向头结点的下一个结点 target->next = (*L)->next;
    新结点作为头结点,则释放原来的头结点;
删除其余节点
  • 找到删除节点前一个结点target;
  • 使得target->next 指向下一个结点;
  • 释放须要删除的结点temp;

2、循环链表的删除

// 循环链表删除元素
void LinkListDelete(LinkList *L, int place) {
    LinkList temp,target;
    int i;
    // temp 指向链表首元结点
    temp = *L;
    if(temp == NULL) {
    	return;
    }
    if (place == 1) {
        // 若是删除到只剩下首元结点了,则直接将*L置空
        if((*L)->next == (*L)){
            (*L) = NULL;
            return;
        }
        // 链表还有不少数据,可是删除的是首结点
        // 找到尾结点, 使得尾结点next 指向头结点的下一个结点 target->next = (*L)->next
        for (target = *L; target->next != *L; target = target->next);
        temp = *L;
        *L = (*L)->next;
        // 新结点作为头结点,则释放原来的头结点
        target->next = *L;
        free(temp);
    } else { // 若是删除其余结点--其余结点
        // 找到删除结点前一个结点target
        for(i = 1,target = *L; target->next != *L && i != place -1; target = target->next,i++) ;
        // 使得target->next 指向下一个结点
        temp = target->next;
        target->next = temp->next;
        // 释放须要删除的结点temp
        free(temp);
    }
}

单向循环链表的查找

int findValue(LinkList L,int value) {
    
    int i = 1;
    LinkList p;
    p = L;
    
    // 寻找链表中的结点 data == value
    while (p->data != value && p->next != L) {
        i++;
        p = p->next;
    }
    
    // 当尾结点指向头结点就会直接跳出循环,因此要额外增长一次判断尾结点的data == value;
    if (p->next == L && p->data != value) {
        return  -1;
    }
    
    return i;
    
}

完整示例传送门

单向线性表链式存储spa