单向循环链表

86 阅读15分钟
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int DataType_t; // 单向循环链表结点的有效数据类型

typedef struct circularlinkedlist {
    DataType_t data;
    struct circularlinkedlist *next;
} CircLList_t;

// 1.创建单向循环链表
CircLList_t *CircLList_Create() {
    CircLList_t *Head = (CircLList_t *)calloc(1, sizeof(CircLList_t));
    if (Head == NULL) {
        perror("申请头节点空间失败!");
        return NULL;
    }
    Head->next = Head;
    return Head;
}

// 2.创建新节点
CircLList_t *CircLList_NewNode(DataType_t data) {
    CircLList_t *New = (CircLList_t *)calloc(1, sizeof(CircLList_t));
    if (New == NULL) {
        perror("申请新节点内存空间失败!");
        return NULL;
    }
    New->next = NULL;
    New->data = data;
    return New;
}

// 3.头插
bool CircLList_HeadInsert(CircLList_t *Head, DataType_t data) {
    CircLList_t *Phead = Head;
    CircLList_t *New = CircLList_NewNode(data);
    if (New == NULL) {
        printf("无法插入新节点!\n");
        return false;
    }

    if (Head->next == Head) {
        Head->next = New;
        New->next = New;
        return true;
    }

    // 找到尾节点
    while (Phead->next != Head->next) {
        Phead = Phead->next;
    }
    
    Phead->next = New;
    New->next = Head->next;
    Head->next = New;
    return true;
}

// 4.尾插
bool CircLList_TailInsert(CircLList_t *Head, DataType_t data) {
    CircLList_t *Phead = Head;
    CircLList_t *New = CircLList_NewNode(data);
    if (New == NULL) {
        printf("无法插入新节点!\n");
        return false;
    }

    if (Head->next == Head) {
        Head->next = New;
        New->next = New;
        return true;
    }

    // 找到尾节点
    while (Phead->next != Head->next) {
        Phead = Phead->next;
    }
    
    Phead->next = New;
    New->next = Head->next;
    return true;
}

// 5.中间插(在指定值节点后插入)
bool CircLList_MidInsert(CircLList_t *Head, DataType_t destval, DataType_t data) {
    CircLList_t *Phead = Head->next; // 从首节点开始查找
    CircLList_t *New = CircLList_NewNode(data);
    if (New == NULL) {
        printf("无法插入新节点!\n");
        return false;
    }

    if (Head->next == Head) {
        Head->next = New;
        New->next = New;
        return true;
    }

    // 查找目标节点
    while (Phead != Head && Phead->data != destval) {
        Phead = Phead->next;
    }

    if (Phead == Head) {
        printf("未找到值为%d的节点!\n", destval);
        free(New);
        return false;
    }

    New->next = Phead->next;
    Phead->next = New;
    return true;
}

// 6.遍历链表
bool CircLList_Print(CircLList_t *Head) {
    if (Head->next == Head) {
        printf("当前链表为空!\n");
        return false;
    }

    CircLList_t *Phead = Head->next; // 从首节点开始
    printf("链表内容: ");
    
    do {
        printf("%d ", Phead->data);
        Phead = Phead->next;
    } while (Phead != Head->next); // 循环直到回到首节点
    
    printf("\n");
    return true;
}

// 7.头删
bool CircLList_HeadDel(CircLList_t *Head) {
    if (Head->next == Head) {
        printf("链表为空!\n");
        return false;
    }

    CircLList_t *Temp = Head->next; // 要删除的首节点
    if (Temp->next == Temp) { // 只有一个节点的情况
        Head->next = Head;
    } else {
        // 找到尾节点
        CircLList_t *Tail = Head->next;
        while (Tail->next != Head->next) {
            Tail = Tail->next;
        }
        
        Head->next = Temp->next; // 更新首节点
        Tail->next = Head->next; // 更新尾节点指向新的首节点
    }

    free(Temp);
    return true;
}

// 8.尾删
bool CircLList_TailDel(CircLList_t *Head) {
    if (Head->next == Head) {
        printf("链表为空!\n");
        return false;
    }

    CircLList_t *Prev = Head;
    CircLList_t *Tail = Head->next; // 从首节点开始查找尾节点
    
    // 找到尾节点和前驱节点
    while (Tail->next != Head->next) {
        Prev = Tail;
        Tail = Tail->next;
    }

    if (Tail == Head->next && Tail->next == Tail) { // 只有一个节点的情况
        Head->next = Head;
    } else {
        Prev->next = Head->next; // 前驱节点指向首节点
    }

    free(Tail);
    return true;
}

// 9.中间删(删除指定值的节点)
bool CircLList_MidDel(CircLList_t *Head, DataType_t destval) {
    if (Head->next == Head) {
        printf("链表为空!\n");
        return false;
    }

    CircLList_t *Prev = Head;
    CircLList_t *Current = Head->next;
    
    // 查找目标节点
    while (Current != Head && Current->data != destval) {
        Prev = Current;
        Current = Current->next;
    }

    if (Current == Head) {
        printf("未找到值为%d的节点!\n", destval);
        return false;
    }

    if (Current == Head->next) { // 要删除的是首节点
        if (Current->next == Current) { // 只有一个节点
            Head->next = Head;
        } else {
            // 找到尾节点
            CircLList_t *Tail = Head->next;
            while (Tail->next != Head->next) {
                Tail = Tail->next;
            }
            
            Head->next = Current->next;
            Tail->next = Head->next;
        }
    } else {
        Prev->next = Current->next;
    }

    free(Current);
    return true;
}

// 10.销毁链表
void CircLList_Destroy(CircLList_t *Head) {
    while (Head->next != Head) {
        CircLList_HeadDel(Head);
    }
    free(Head);
}

int main() {
    // 创建链表
    CircLList_t *list = CircLList_Create();
    if (list == NULL) {
        printf("链表创建失败!\n");
        return -1;
    }

    // 测试插入操作
    printf("测试头插:\n");
    CircLList_HeadInsert(list, 10);
    CircLList_HeadInsert(list, 20);
    CircLList_HeadInsert(list, 30);
    CircLList_Print(list); // 应该输出: 30 20 10

    printf("\n测试尾插:\n");
    CircLList_TailInsert(list, 40);
    CircLList_TailInsert(list, 50);
    CircLList_Print(list); // 应该输出: 30 20 10 40 50

    printf("\n测试中间插(在20后插入25):\n");
    CircLList_MidInsert(list, 20, 25);
    CircLList_Print(list); // 应该输出: 30 20 25 10 40 50

    // 测试删除操作
    printf("\n测试头删:\n");
    CircLList_HeadDel(list);
    CircLList_Print(list); // 应该输出: 20 25 10 40 50

    printf("\n测试尾删:\n");
    CircLList_TailDel(list);
    CircLList_Print(list); // 应该输出: 20 25 10 40

    printf("\n测试中间删(删除10):\n");
    CircLList_MidDel(list, 10);
    CircLList_Print(list); // 应该输出: 20 25 40

    // 销毁链表
    CircLList_Destroy(list);
    printf("\n链表已销毁\n");

    return 0;
}

这是我自己写的 以上是完整的代码


// 单向循环链表(与单链表唯一的区别就是单向循环链表尾结点的指针域里面存放的是首结点的地址,说白了就是尾结点的指针指向了首结点)
// 设计单向循环链表的接口

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

typedef int DataType_t; // 单向循环链表结点的有效数据类型,用户可以根据需求进行修改

// 构造单向循环链表的结点,链表中所有节点的数据类型是相同的
typedef struct circularlinkedlist
{
  DataType_t data;                 // 单向循环链表节点的数据域
  struct circularlinkedlist *next; // 单向循环链表节点的指针域(用来存储直接后继的地址,地址下值的类型是结构体类型;
} CircLList_t;

// 1.创建一个单向循环链表,空链表应该有一个头节点,对链表进行初始化;
CircLList_t *CircLList_Create()
{
  // 1.1创建一个头节点,并为头节点申请内存空间;
  CircLList_t *Head = (CircLList_t *)calloc(1, sizeof(CircLList_t)); // Head是头指针,里面存储的是头节点的首地址,用头指针就可以访问到头节点里面的值,也就是说可以访问到结构体里面的值,因为一个结构体就是一个节点;
  if (Head == NULL)                                                  // 判断在堆内存中申请的空间是否成功,若失败,则退出此函数,因为若申请内存失败,还继续访问节点里面的元素,则会出现段错误;
  {
    perror("申请头节点空间失败!");
    return NULL;
  }

  // 1.2对头节点进行初始化,头节点是不存储有效内容的
  Head->next = Head; // 要体现出循环这种思想,循环中不能出现NULL,但是现在没节点可以指向,所以先指向自己(重点)(指针域指向自身,体现循环思想)
  // 1.3把头节点的地址返回即可
  return Head;
}

// 2.创建一个新节点,并对新节点进行初始化(说白了就是对结构体里面的数据域和指针域进行初始化)
CircLList_t *CircLList_NewNode(DataType_t data) // 因为除了头节点数据域里面没有值,其他节点都有,所以要传入数据进去
{
  // 2.1 创建一个新节点,并对新节点进行初始化
  CircLList_t *New = (CircLList_t *)calloc(1, sizeof(CircLList_t)); // 为新节点申请内存空间
  if (New == NULL)
  {
    perror("申请新节点内存空间失败!");
    return NULL;
  }

  // 2.2 对新节点进行初始化
  New->next = NULL; // 这里写NULL就可以,因为创建新节点,就是为了插入的,到时候会改变next的指向
  New->data = data;

  // 2.3将新节点的地址返回即可
  return New;
}

// 3.头插(不管是头插还是尾插还是中间插都要考虑链表为空和链表非空的两种条件)(头插不需要遍历,尾插和中间插会涉及到遍历的动作) (插成功返回true,失败返回false);
bool CircLList_HeadInsert(CircLList_t *Head, DataType_t data)
{
  // 备份头指针(备份头节点地址)
  CircLList_t *Phead = Head;

  // 3.1创建新节点,并对新节点进行初始化
  CircLList_t *New = CircLList_NewNode(data); // 返回的是地址,用指针去接收
  if (New == NULL)
  {
    printf("can not insetr new node!\n");
    return false;
  }

  // 3.2判断链表是否为空,如果为空,则把新节点作为首节点,体现“循环”(指向自己)
  if (Head->next == Head) // 头节点的指针域里面的值是否和头节点的地址(头指针)相同,如果是,说明当前链表是空的,只有一个头节点
  {
    Head->next = New; // 将新节点连接到新节点的后面
    New->next = New;  // 因为是循环链表,指针域不能出现NULL,所以指向首节点,此时的首节点就是新加入的新节点(要体现循环思想)
    return true;      // 链表为空,插入成功,返回true;
  }

  // 3.3.如果链表是非空的(直接执行下面的代码),则还需要对链表尾节点的next的指针进行处理,指向首节点
  // 先处理尾接节点,怎么才能有尾节点的地址呢,肯定是要变量头指针,才能访问到尾节点,但是头指针会变化,所有第一步备份头指针
  while (Phead->next) // 第一次进入循环判断的是首节点的地址。  注意循环找的是尾节点
  {
    Phead = Phead->next;           // 移动头指针(每循环一次,头指针向后移动一个节点)
    if (Phead->next == Head->next) // 判断有没有到达尾节点,由于循环链表里面没有NULL,所以这里用if进行判断
    {
      break; // (当前节点的指针域指向首节点,也就说明找到尾节点了,所以退出循环)
    }
  }
  // 执行完上面的while,说明Phead现在指向的是尾节点
  Phead->next = New; //(现在的New就可以说是新的首节点)(体现循环)(尾节点的next指向新的首节点)

  New->next = Head->next; // 先连接 手机截图的圈1 (新节点的next指针指向原本的首节点);
  Head->next = New;       // 再断开 (此时的New就是新的首节点)(更新首节点地址,让头节点的next指针指向新节点);

  return true; // 链表不为空,插入成功,返回true;
}

// 4.尾插(需要设计遍历的动作,因为是尾插,所以遍历找到尾节点)
bool CircLList_TailInsert(CircLList_t *Head, DataType_t data)
{
  // 备份头指针(备份头节点地址)
  CircLList_t *Phead = Head;

  // 4.1创建新节点,并对新节点进行初始化
  CircLList_t *New = CircLList_NewNode(data); // 返回的是地址,用指针去接收
  if (New == NULL)
  {
    printf("can not insetr new node!\n");
    return false;
  }

  // 4.2判断链表是否为空,如果为空,则把新节点作为首节点,体现“循环”(指向自己)
  if (Head->next == Head) // 头节点的指针域里面的值是否和头节点的地址(头指针)相同,如果是,说明当前链表是空的,只有一个头节点
  {
    Head->next = New; // 将新节点连接到新节点的后面
    New->next = New;  // 因为是循环链表,指针域不能出现NULL,所以指向首节点,此时的首节点就是新加入的新节点(要体现循环思想)
    return true;      // 链表为空,插入成功,返回true;
  }

  // 4.3.如果链表是非空的(直接执行下面的代码),则还需要对链表尾节点的next的指针进行处理,指向首节点
  // 先处理尾接节点,怎么才能有尾节点的地址呢,肯定是要变量头指针,才能访问到尾节点,但是头指针会变化,所有第一步备份头指针
  // 手机截图第一步
  while (Phead->next) // 第一次进入循环判断的是首节点的地址。  注意循环找的是尾节点
  {
    Phead = Phead->next;           // 移动头指针(每循环一次,头指针向后移动一个节点)
    if (Phead->next == Head->next) // 判断有没有到达尾节点,由于循环链表里面没有NULL,所以这里用if进行判断
    {
      break; // (当前节点的指针域指向首节点,也就说明找到尾节点了,所以退出循环)
    }
  }
  // 执行完上面的while,说明Phead现在指向的是尾节点
  Phead->next = New;      // 手机截图第2步(旧的尾节点的next指向新节点,此时的新节点就是新的尾节点);
  New->next = Head->next; // 手机截图第3步(此时的New就是尾节点,New的next指向的是首结点)(新的尾节点的next指针指向的是首节点地址)

  return true;
}

// 5.中间插(需要设计到遍历的动作,要往中中间哪里插,循环遍历找到目标节点)(一般情况插到目标节点的后面)
bool CircLList_TailInsert(CircLList_t *Head, DataType_t destval, DataType_t data)
{
  // 备份头指针(备份头节点地址)
  CircLList_t *Phead = Head;

  // 5.1创建新节点,并对新节点进行初始化
  CircLList_t *New = CircLList_NewNode(data); // 返回的是地址,用指针去接收
  if (New == NULL)
  {
    printf("can not insetr new node!\n");
    return false;
  }

  // 5.2判断链表是否为空,如果为空,则把新节点作为首节点,体现“循环”(指向自己)
  if (Head->next == Head) // 头节点的指针域里面的值是否和头节点的地址(头指针)相同,如果是,说明当前链表是空的,只有一个头节点
  {
    Head->next = New; // 将新节点连接到新节点的后面
    New->next = New;  // 因为是循环链表,指针域不能出现NULL,所以指向首节点,此时的首节点就是新加入的新节点(要体现循环思想)
    return true;      // 链表为空,插入成功,返回true;
  }

  // 5.3.如果链表是非空的(直接执行下面的代码),所以此时不是对尾节点的指针进行处理了,但要考虑到目标节点就是尾节点的情况,也要考虑到目标节点是首节点和正好在中间的情况

  while (Phead != Head && Phead->data != destval)
  // Phead != Head:确保遍历不会绕回头节点(循环链表的终止条件),如果 Phead == Head,说明已经遍历完所有节点
  // Phead->data != destval:检查当前节点的数据是否匹配目标值
  {
    Phead = Phead->next; // 每一次遍历, 移动头指针(刚开始头指针是指向头节点的), 指向下一个结点(Phead就是下一个结点的地址)
  }

  if (Phead == Head) // 如果遍历后 Phead 回到头节点,说明链表中没有 destval
  {
    return false; // 未找到
  }

  // 插入到目标节点之后
  New->next = Phead->next;
  Phead->next = New;

  return true;
}

// 6.遍历链表(说白了就是打印节点的数据,不需要返回值)
bool CircLList_Print(CircLList_t *Head) // 注意如果写了bool,函数里面既要返回false,又要返回true,才算完美的代码
{
  // 6.1 对单向循环链表的头节点的地址进行备份,备份的原因手机截屏有,自己看
  CircLList_t *Phead = Head;

  // 如果是空链表,那么头节点的next是指向头节点的,但是头节点里面没有数据域,直接做判断退出
  if (Head->next = Head) // 判断头指针中的next是否指向头节点,Head是头节点的地址
  {
    printf("current linkedlist is empty!");
    return false;
  }

  // 6.2开始从首节点进行遍历
  while (Phead->next) //// 先判断头结点的next是不是空(NULL),若不是空,进行操作,若是空,根本需要操作,因为没有任何节点可以操作(Phead->next记录的是头节点直接后继节点的地址)
  {
    // 把头结点的直接后继作为新的头节点(因为头节点不存数据域,所以改变头指针的指向,找到新节点,输出新节点里面的数据)
    Phead = Phead->next; // 由于头结点没有有效数据,所以让头指针指向首结点,紧接着输出首节点的值,进行循环判断,再移动头指针,指向新节点

    // 输出头节点的直接后继的数据域
    printf("data = %d", Phead->data);

    //  因为尾节点不在指向NULL,根据while循环判断的条件就成了死循环了,所以这里必须要加条件
    // 判断是否达到尾节点,尾节点的next指针是指向首节点的
    if (Phead->next == Head->next) //  ==的后面不能写Phead->next,因为Phead早已经做了偏移的动作,此时的Phead并不指向头节点,所以用Head,Head我并没有动过;
    {
      // 分析一下 Head->next一直是首节点的地址,不会改变 一直改变的是Phead->next 截屏中有图自己分析一下
      break; // 到达了尾节点之后,直接退出函数
    }
  }
  return true;
}

// 7.头删(因为要遍历找到尾指针(会移动头指针),所以要备份头节点地址,找到尾节点,将尾节点的next置为首节点的下一个节点的地址)
bool CircLList_Print(CircLList_t *Head) // 注意如果写了bool,函数里面既要返回false,又要返回true,才算完美的代码
{
  // 7.1 对单向循环链表的头节点的地址进行备份,备份的原因手机截屏有,自己看
  CircLList_t *Phead = Head;

  // 提前备份首节点的地址
  CircLList_t *Temp = Head->next;

  // 7.2链表为空,如果为空,则退出
  if (Head->next == Head)
  {
    printf("LinkedList is Empty!\n");
    return false;
  }

  // 7.3当链表中只有一个头结点和首节点的情况
  if (Head->next = Head->next->next) // 因为是循环链表,所以此时判断条件为只有首节点
  {
    Temp->next = NULL; // 首节点的next指向NULL;
    Head->next = Head; // 头节点的next指针指向头节点,体现'循环'的思想;
    free(Temp);        // 释放首节点
    return true;
  }

  // 7.4如果链表是非空的(并且不是只有一个首节点的情况),则对尾节点的next指针进行处理,指向新的首节点(新的首节点说白了,就是旧的首节点的直接后继)
  // 遍历链表,找到尾节点(手机截屏图示第一步)
  while (Phead->next) // 第一次进入循环判断的是首节点的地址。  注意循环找的是尾节点
  {
    Phead = Phead->next;           // 移动头指针(每循环一次,头指针向后移动一个节点)
    if (Phead->next == Head->next) // 判断有没有到达尾节点,由于循环链表里面没有NULL,所以这里用if进行判断
    {
      break; // (当前节点的指针域指向首节点,也就说明找到尾节点了,所以退出循环)
    }
  }
  // 此时的Phead就是尾节点了

  // 7.5让尾节点的next指针指向新的首节点(手机图示第二步)
  Phead->next = Head->next->next;

  // 7.6跟新首节点,让头节点的next指向新的首节点(手机截屏第三步)
  Head->next = Phead->next; // 注意做完这一步,那么连接好了,但是头节点和首节点就断开了,因为还需要对首节点进行操作,所以提前备份首节点的地址

  // 7.7将旧的首节点的next指针指向NULL,从表中中断(手机截屏第四步)
  Temp->next = NULL;

  // 7.8释放旧的首节点(手机截屏第五步)
  free(Temp); // 释放待删除结点的内存

  return true;
}

// 8.尾删

// 8.中间删