五十一、单向循环链表
51.1 单向循环链表的思想
- 创建时 : 头结点的next指针域指向头结点
- 插入时 : 尾结点的next指针域指向头结点
- 到尾结点条件 : 当前结点的next指针域是否指向头结点
51.2 与单链表的区别
- 单链表:
- 创建时 : 头结点的next指针域指向NULL
- 插入时 : 尾结点的next指针域指向NULL
- 到尾结点条件 : 当前结点的next指针域是否指向NULL
- 单向循环链表:
- 头结点的next指针域指向头结点
- 尾结点的next指针域指向头结点
- 当前结点的next指针域是否指向头结点
51.3 单向循环链表功能实现完整代码
51.3.1 接口(head.h)
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 重命名数据域的数据类型名
typedef int data;
// 数据域是一个联合体,指针域指向下一结点
typedef struct myCirLinkList
{
union
{
data num; // 表示数据内容
int len; // len表示当前有多少结点(头结点除外)
};
// 指向下一结点的指针
struct myCirLinkList *next;
} cirLinkNode; // 结构体类型名重命名为cirLinkNode
// 创建单向循环链表
cirLinkNode *init();
// 单链表的判空
int is_empty(cirLinkNode *L);
// 单链表的尾插
int tail_insert(cirLinkNode *l, data value);
// 单链表的头插
int head_insert(cirLinkNode *l, data value);
// 指定下标插入
int inset_by_index(cirLinkNode *l, int index, data value);
// 指定下标删除
int del_by_index(cirLinkNode *l, int index);
// 头删
int head_del(cirLinkNode *l);
// 尾删
int tail_del(cirLinkNode *l);
// 按值删除,返回删除的下标
int del_by_value(cirLinkNode *l, data value);
// 单链表的按值查找,返回下标
int find_by_value(cirLinkNode *l, data value);
// 单链表的按值修改,返回下标
int change_by_value(cirLinkNode *l, data old_value, data new_value);
// 单链表的按位置查找,返回对应的值
data find_by_index(cirLinkNode *l, int index);
// 单链表的选择排序
int select_sort(cirLinkNode *l);
// 单链表的冒泡排序
int bubble_sort(cirLinkNode *l);
// 输出单链表
int output(cirLinkNode *l);
// 单链表去重
int remove_duplicates(cirLinkNode *l);
// 释放单链表
int free_cirLinkNode(cirLinkNode **l);
// 单链表的翻转,循环头插
int flip_cirLinkNode(cirLinkNode *l);
#endif
51.3.2 功能实现(circularLinkedList.c)
#include "head.h"
// 创建单向循环链表
cirLinkNode *init()
{
cirLinkNode *p=(cirLinkNode*)malloc(sizeof(cirLinkNode));
// 申请失败时返回NULL
if (NULL == p)
{
printf("创建单向循环链表失败\n");
return NULL;
}
// 初始结点数为0(头结点除外)
p->len = 0;
// 初始结点的指针域指向头结点
p->next = p;
// 返回申请的堆区空间的首地址
return p;
}
// 单向循环链表的判空
int is_empty(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 只有一个头结点,或者结点数为0(头结点除外),此时链表为空,返回1
if (l == l->next || l->len == 0)
{
return 1;
}
return 0; // 链表不是空,返回0
}
// 单向循环链表的尾插
int tail_insert(cirLinkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
cirLinkNode *p = l;
// 循环向后指,直到p指向最后一个结点
while (l != p->next)
{
p = p->next;
}
// 新申请一个结点的空间,用ll指向
cirLinkNode *ll = (cirLinkNode *)malloc(sizeof(cirLinkNode));
// 新结点的值为传进来的值
ll->num = value;
// 新结点的next指针域指向头结点
ll->next = l;
// 新结点插到最后
p->next = ll;
// 结点计数变量+1
l->len++;
return 0;
}
// 单向循环链表的头插
int head_insert(cirLinkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
cirLinkNode *p = l;
// 新申请一个结点的空间,用ll指向
cirLinkNode *ll = (cirLinkNode *)malloc(sizeof(cirLinkNode));
// 新结点的值为传进来的值
ll->num = value;
// 新结点的next指针域指向头结点的原下一结点,即原第一结点
ll->next = p->next;
// 让头结点指向这个新结点
p->next = ll;
// 结点计数变量+1
l->len++;
return 0;
}
// 根据位置插入
int inset_by_index(cirLinkNode *l, int index, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (index <= 0 || index > l->len)
{
printf("传入的下标有问题\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
cirLinkNode *p = l;
// 记录当前下标
int i = 1;
while (1)
{
// 如果当前下标与传进来的下标相等,进行头插
// 因为此时指针p已经循环指向了此下标对应的结点的首地址
if (i == index)
{
// head_insert(p, value); // 不能直接调用头插法,因为传进来的头结点不是真正头结点
cirLinkNode *ll = (cirLinkNode *)malloc(sizeof(cirLinkNode));
ll->num = value;
ll->next = p->next;
p->next = ll;
// 插入后退出循环
break;
}
// 如果找到了最后还没找到,说明位置参数不对
if (l == p->next)
{
printf("传入位置参数不合法\n");
// 位置参数不对,return -1 表示函数异常结束
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p向后指,继续向后找
p = p->next;
}
// 找到并插入后,结点数+1
l->len++;
return 0;
}
// 头删
int head_del(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无法进行删除\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
cirLinkNode *p = l;
// 保存待删除的结点,第一结点
cirLinkNode *q = l->next;
// 将头结点的next指针域指向下下一结点
p->next = q->next;
// 释放第一结点的空间
free(q);
q = NULL;
// 结点计数变量-1
l->len--;
return 0;
}
// 尾删
int tail_del(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无法进行删除\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
cirLinkNode *p = l;
// 循环找到最后一个结点
while (1)
{
// 如果下一结点的next指针域指向头结点
// 说明当前结点是倒数第二个结点
if (NULL == p->next->next)
{
// q指向最后这个结点
cirLinkNode *q = p->next;
// 倒数第二个结点的next指针域指向头结点
p->next = l;
// 释放最后这个结点的空间
free(q);
q = NULL;
// 退出循环
break;
}
// 当前结点不是倒数第二个结点时继续向下找
p = p->next;
}
// 删除后,结点计数变量-1
l->len--;
return 0;
}
// 指定下标删除,头结点之后
int del_by_index(cirLinkNode *l, int index)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (index <= 0 || index > l->len)
{
printf("传入的下标有问题\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
cirLinkNode *p = l;
// 记录当前下标
int i = 1;
while (1)
{
// 如果当前下标与传进来的下标相等,进行删除
// 此时指针p的next指针域已经循环指向了要找的下标对应的结点
// 即p指向要删除的结点的前一结点
if (i == index)
{
// q指向这个要删除的结点
cirLinkNode *q = p->next;
// 让要删除的结点前一结点和后面一个结点连接
// 越过要删除的这个结点
p->next = q->next;
// 释放要删除的这一结点的空间
free(q);
q = NULL;
// 删除后退出循环
break;
}
// 循环到了最后还没找到这个下标对应的结点
if (l == p->next)
{
// 提示位置参数不对,并返回-1表示函数异常结束
printf("传入位置参数不合法\n");
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 删除后,结点计数变量-1
l->len--;
return 0;
}
// 按值删除,返回删除的下标
int del_by_value(cirLinkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
cirLinkNode *p = l;
// 记录当前下标
int i = 1;
// 循环向后找
while (1)
{
// 如果下一结点的值与传进来的值相同,则进行删除
if (value == p->next->num)
{
// 指针q指向下一结点,即要删除的结点
cirLinkNode *q = p->next;
// 当前结点的next指针域指向要删除的结点的下一个结点
p->next = q->next;
// 释放要删除的结点的空间
free(q);
l->len--;
// 返回当前所在下标
return i;
}
// 如果找到了最后还没找到就退出循环
if (l == p->next)
{
break;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("找不到这个值\n");
// 返回-1表示函数异常结束
return -1;
}
// 单向循环链表的按值查找,返回下标,头结点之后
int find_by_value(cirLinkNode *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
cirLinkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果下一结点的值与传进来的值相同,则返回当前位置
if (value == p->num)
{
return i;
}
// 如果找到了最后还没找到就退出循环
if (l == p->next)
{
break;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("链表中没有这个值\n");
// 返回-1表示函数异常结束
return -1;
}
// 单向循环链表的按值修改,返回下标
int change_by_value(cirLinkNode *l, data old_value, data new_value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
cirLinkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果下一结点的值与传进来的旧值相同,则用新值进行修改并返回当前位置
if (old_value == p->num)
{
p->num = new_value;
return i;
}
// 如果找到了最后还没找到就退出循环
if (l == p->next)
{
break;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
// 循环没找到,输出找不到这个值
printf("链表中没有这个值\n");
// 返回-1表示函数异常结束
return -1;
}
// 单向循环链表的按位置查找,返回对应的值
data find_by_index(cirLinkNode *l, int index)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (index < 0 || index > l->len)
{
printf("传入的下标有问题\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
cirLinkNode *p = l->next;
// 记录当前位置
int i = 1;
// 循环向后找
while (1)
{
// 如果当前位置与传进来的下标相同,则返回这个位置的值
if (i == index)
{
return p->num;
}
// 如果找到了最后还没找到则提示位置参数不对
if (l == p->next)
{
printf("传入位置参数不合法\n");
// 返回-1表示函数异常结束
return -1;
}
// 这一圈没找到则当前下标+1向后找
++i;
// p继续向后指,向后接着找
p = p->next;
}
}
// 单向循环链表的简单选择排序,降序
int select_sort(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无法进行简单选择排序\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
// 结点指针p后面表示当前结点,结点指针q后面表示被比较的的结点
cirLinkNode *p = l->next, *q;
// 外层循环,控制循环次数
while (1)
{
// 后面没有结点了,结束外层循环
if (l == p->next)
{
break;
}
// 指针q指向p的下一结点
// 表示被比较的结点是从当前结点的下一结点开始,会到最后一个结点
q = p->next;
// 假设最大值的结点是当前结点
cirLinkNode *max = p;
// 内层循环,表示比较次数
while (1)
{
// 如果被比较的结点比当前认定的最大结点的值还大
if (q->num > max->num)
{
// 那么让最大结点指针指向这个结点
max = q;
}
// 如果被比较的结点到了最后一个,则退出内层循环
if (l == q->next)
{
break;
}
// 被比较的结点逐个往后挪
q = q->next;
}
// 当前结点与最大结点指针指向的结点进行值交换
data t = max->num;
max->num = p->num;
p->num = t;
// 当前结点逐个往后挪
p = p->next;
}
return 0;
}
// 单向循环链表的冒泡排序,升序
int bubble_sort(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无法进行冒泡排序\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点的下一结点
// 结点指针p控制外层循环次数,结点指针end控制内层比较次数
// 结点指针q用于比较
cirLinkNode *p = l->next, *q, *end = l;
// 外层循环,表示循环次数
while (1)
{
if (l == p->next) // 后面没有结点了,直接结束
{
break;
}
// 每次都从第一个数据结点开始(头结点后的第一结点)
q = l->next;
// 内层循环,表示比比较次数
while (1)
{
// 如果 当前值 大于 后面的值,则交换
if (q->num > q->next->num)
{
data t = q->num;
q->num = q->next->num;
q->next->num = t;
}
// 到了尚未比较的结点中倒数第二个结点,前面已经比较过了,该退出进行下一轮的比较了
if (end == q->next->next)
{
// 更新结束条件
end = q->next;
break;
}
// 内层循环递进,用于比较的结点向后逐步挪移
q = q->next;
}
// 外层循环递进,p逐渐向后指向,直到最后一个结点后进入下次循环时结束
p = p->next;
}
return 0;
}
// 输出单向循环链表
int output(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
// 用于指向待输出的结点
cirLinkNode *p = l;
// 记录当前到第几个结点
int i = 0;
while (1)
{
// 是头结点时
if (0 == i)
{
printf("除头结点外,其余结点个数为:%d\n", p->len);
}
else // 非头结点时
{
printf("第%d位数据为:%d\n", i, p->num);
}
// 位置变量+1
++i;
// 如果到了最后一个结点,那么就退出(因为是先输出的,所以已经输出过了)
if (l == p->next)
break;
// 待输出的结点向后逐位移动
p = p->next;
}
return 0;
}
// 单向循环链表去重
int remove_duplicates(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无需去重\n");
return -1;
}
// 防止头结点指向改变,故新建一个指针指向头结点
// 用于记录当前结点
cirLinkNode *p = l;
// 外层循环,控制循环次数
while (1)
{
// 用于记录被比较结点的前一结点
cirLinkNode *q = p;
// 内层循环,用于控制比较次数
while (1)
{
// 如果被比较的结点到了最后一位,则退出
if (l == q->next)
{
break;
}
// 如果当前结点的值与被比较的结点的值相同,则删除这个被比较的结点
if (q->next->num == p->num)
{
// 用于指向被比较的结点
cirLinkNode *t = q->next;
// 被比较的前一结点的next指针域指向被比较的结点的后一结点
q->next = t->next;
// 释放这个被比较的结点
free(t);
t = NULL;
// 结点计数变量-1
l->len--;
}
// 让被比较的结点向后挪动
q = q->next;
}
// 让当前结点向后挪动
p = p->next;
// 如果当前结点挪到了最后,则退出
if (l == p)
{
break;
}
}
return 0;
}
// 释放单向循环链表
int free_cirLinkNode(cirLinkNode **l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
// 用于接收传来的头结点指针
// 传来的是头结点指针的地址,所以需要解引用获得其指向的地址
cirLinkNode *p = *l;
// 用于存放准备释放的结点的下一结点,免得找不到了
cirLinkNode *q = p->next;
// 进入循环,准备逐个释放
while (1)
{
// 释放当前结点
free(p);
// 让下一结点变成当前结点
p = q;
// 如果当前结点到了最后,指针又指向头结点,表示空间都释放了
// 所以需要将头结点指针的指向更改,即需要解引用指向NULL,然后退出
if (*l == p)
{
*l = NULL;
return 0;
}
// 待释放结点的下一结点向后挪
// 因为上面已经把原来待释放结点的下一结点变成了当前准备释放的结点了
q = q->next;
}
}
// 单向循环链表的翻转,循环头插
int flip_cirLinkNode(cirLinkNode *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("只有头结点,无法进行翻转\n");
return -1;
}
if (1 == l->len)
{
printf("只有一个数据结点,无需翻转\n");
return -1;
}
// 让除了第一数据结点外的其他结点依次进行头插,即可得到翻转后的链表
// 如果直接调用头插法,会重新生成结点,导致空间浪费
// 这是个面试题
cirLinkNode *p = l->next->next; // 记录当前需要头插的结点
// 将原本的第一数据结点的next指针域指向头结点
// 因为最后这个结点会变成尾结点
l->next->next = l;
cirLinkNode *q = p->next; // 记录下次需要头插的结点
// 循环头插
while (1)
{
// 将当前需要插入的结点的next指针域指向现在的第一数据结点
p->next = l->next;
// 将头结点的next指针域指向当前需要插入的结点
// 这样当前需要插入的结点就完成了头插,变成了新的第一数据结点
l->next = p;
// 让下次需要头插的结点变成当前需要插入的结点
p = q;
// 如果下次需要头插的结点没有指向原尾结点的next指针域
if (l != q)
{
q = q->next;
}
// 如果当前下次需要插入的结点已经指向原尾结点的next指针域
// 即表示已经到了原尾结点的后一个结点了,那么就退出循环
// 这样可以将原尾结点也进行头插,不然会漏掉原尾结点
else
break;
}
return 0;
}
51.3.3测试用主函数(main.c)
#include "head.h"
int main()
{
cirLinkNode *l = init();
output(l);
printf("%d\n", is_empty(l));
head_insert(l, 32);
output(l);
printf("%d\n", is_empty(l));
putchar(10);
puts("------------------------1-------------------------");
head_insert(l, 35);
output(l);
putchar(10);
puts("-------------------------2------------------------");
tail_insert(l, 100);
output(l);
putchar(10);
puts("--------------------------3-----------------------");
head_insert(l, 32);
output(l);
putchar(10);
head_insert(l, 39);
output(l);
putchar(10);
printf("两极反转\n");
flip_cirLinkNode(l);
output(l);
putchar(10);
printf("简单选择排序\n");
select_sort(l);
output(l);
putchar(10);
printf("冒泡排序\n");
bubble_sort(l);
output(l);
putchar(10);
printf("去重\n");
remove_duplicates(l);
output(l);
putchar(10);
del_by_index(l, 2);
// head_del(l);
output(l);
putchar(10);
printf("%d\n", find_by_index(l, 1));
printf("%d\n", find_by_value(l, 200));
output(l);
putchar(10);
printf("%d\n", del_by_value(l, 100));
output(l);
putchar(10);
printf("%p\n", l);
printf("释放\n");
free_cirLinkNode(&l);
printf("%p\n", l);
return 0;
}
51.3.4 Makefile
EXE=circularLinkedList
CC=gcc
CFLAGs=-c
OBJs+=circularLinkedList.o
OBJs+=main.o
all:$(EXE)
$(EXE):$(OBJs)
$(CC) $^ -o $@
%.o:%.c
$(CC) $(CFLAGs) $^ -o $@
clean:
rm *.o circularLinkedList