我通过C语言代码实现了循环链表的初始化、判断是否为空、清空线性表、查询、查找、插入、删除、返回长度、循环打印、根据数组整表创建等操作,并写了主函数测试编译运行通过。
此外我通过编程实现了循环链表的经典问题,约瑟夫环,约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。我在下方一并提供了参考代码。
我对所有的代码进行了手把手的单行注释,及其适合循环链表各项操作的理解和代码记忆,现将代码分享如下.
#include <stdio.h>
#include <stdlib.h>
#define bool int
#define true 1
#define false 0
#define ok 1
#define error 0
typedef int ElemType;
typedef int Status;
typedef struct Node {//线性表的单链表存储结构
ElemType data;//数据域
struct Node *next;//指针域
} Node;
typedef struct Node* LinkList;//定义LinkList:struct Node*
//函数声明
void InitList(LinkList* L);
bool ListEmpty(LinkList L);
void ClearList(LinkList* L);
Status GetElem(LinkList L, int i, ElemType *e);
int LocateElem(LinkList L, ElemType e);
Status ListInsert(LinkList *L, int i, ElemType e);
Status ListDelete(LinkList *L, int i, ElemType *e);
int ListLength(LinkList L);
void PrintList(LinkList L);
void PrintByCount(LinkList L, int n);
void JosephusProblem(LinkList L, int n);
LinkList CreateLinklistByArray(ElemType Array[], int length);
int main(int argc, char *argv[]) {//在主函数里进行测试
ElemType e;//定义变量e用来保存被删除的数据
ElemType array[10] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};//定义一个用来创建循环链表的数组
LinkList list, listByArray;//定义两个链表list和listByArray
InitList(&list);//初始化list
printf("链表是否为空? %d\n", ListEmpty(list));//使用ListEmpty判断链表list是否为空,此时因为链表没有元素,所以得到1表示为空
ListInsert(&list, 1, 1);//使用ListInsert插入3个节点,数据分别为1,2,3;因为都是从第一个位置插入元素,相当于头插法
ListInsert(&list, 1, 2);
ListInsert(&list, 1, 3);
//PrintList(list);//打印一下当前链表,输出3,2,1
printf("数据1的位置是 %d\n", LocateElem(list, 1)); //使用LocateElem查找数据为2的元素,并打印其位置
printf("数据4的位置是 %d\n", LocateElem(list, 4)); //使用LocateElem查找数据为4的元素,并打印其位置,由于此时链表中没有数据4,所以打印的值为0
printf("链表的长度: %d\n", ListLength(list));//使用ListLength打印链表的长度,此时为3
ListInsert(&list, 1, 4);//使用ListInsert继续插入两个元素
ListInsert(&list, 1, 5);
PrintList(list);//此时打印的值为5,4,3,2,1
PrintByCount(list, 22);//在链表list循环打印22个值
printf("数据4的位置是 %d\n", LocateElem(list, 4));//此时可以使用LocateElem查找到数据4的位置
printf("链表是否为空? %d\n", ListEmpty(list));//使用ListEmpty判断链表是否为空,因为已经有5个元素,所以打印0
printf("链表的长度: %d\n", ListLength(list));//此时链表的长度为5
ListInsert(&list, 3, 100);//在中间插入一个元素100
PrintList(list);//打印显示5,4,100,3,2,1
ListDelete(&list, 3, &e);//使用 ListDelete删除第三个元素即100
PrintList(list);//再次打印发现100已经被删除,显示5,4,3,2,1
printf("被删除的元素为:%d\n", e);//通过保存刚刚删除的元素的变量e打印删除的元素的数据
JosephusProblem(list, 3);//测试约瑟夫问题,每数3个数删除一个结点
ClearList(&list);//清空链表
printf("链表是否为空? %d\n", ListEmpty(list));//此时链表为空,打印1
printf("链表的长度: %d\n", ListLength(list));//被清空后链表的长度为0
listByArray = CreateLinklistByArray(array, 10);//使用数组整表创建一个循环链表
PrintList(listByArray);//打印出刚刚创建的循环链表,和上面数组的元素值相同
PrintByCount(listByArray, 16);//在链表listByArray循环打印22个值
return 0;
}
void InitList(LinkList* L) {//初始化链表
*L = (LinkList)malloc(sizeof(Node));//为单链表分配头结点
(*L) -> next = (*L);//指针域设置为头结点
}
bool ListEmpty(LinkList L) {//判断链表是否为空
return L->next == L;//如果头结点的指针域为头结点,则表示是空链表
}
void ClearList(LinkList* L) {//清空链表
LinkList p,q;//定义两个辅助指针
p = (*L)-> next;//p指向第一个结点,从第一个结点开始删除
while (p != (*L)) {//没到表尾就继续循环
q = p->next;//q来记录下一个结点
free(p);//删除p指向的结点
p = q;//把q指向的下一个结点赋值给p
}
(*L)->next = (*L);//指针域设置为头结点
}
Status GetElem(LinkList L, int i, ElemType *e) {//将线性表L中的第i个元素值返回给e
int j;//定义计数器j
LinkList p;//定义辅助指针p
p = L->next;//p指向单链表的第一个结点,从第一个结点开始遍历
j = 1;//计数器初始化为1
while (p != L && j < i) {//如果没到表尾或者计数器不等于i就继续循环
p = p->next;//p指向下一个结点
++j;//计数器自加
}
if ((p == L) || j > i) {//到表尾或非法值,j > i 用于非法值处理,比如传入i = 0
return error;//返回错误值
}
*e = p->data;//把得到的值给参数指针e指向的空间
return ok;//返回成功
}
int LocateElem(LinkList L, ElemType e) {//在线性表L 中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败.
int j;//定义计数器j
LinkList p;//定义辅助指针p
p = L->next;//p指向单链表的第一个结点,从第一个结点开始查找
j = 1;//计数器初始化为1
while (p != L && p->data != e) {//如果没到表尾或者还没有查询到给定元素继续循环
p = p->next;//p指向下一个结点
++j;//计数器自加
}
if (p == L) {//如果到表尾
return 0;//返回0表示查找失败
}
return j;//返回查找到的元素的序号,等于计数器j的值
}
Status ListInsert(LinkList *L, int i, ElemType e) {//在链表L的第i个位置插入元素e
int j;//定义计数器j
LinkList p, s;//定义两个辅助指针p,s
p = *L;//p指向链表的头结点,插入操作需要找到指定位置的前一个位置,头结点是第一个位置的前驱
j = 1;//计数器初始化为1
while ((p != (*L) || j == 1) && j < i) {//如果没到表尾或者j<i就继续循环,因为p指向头结点,所以当j=i时p指向第i个结点的前一个结点
p = p->next;//p指向后一个结点
++j;//计数器自加1
}
if ((p == (*L) && j != 1) || j > i) {//如果到达表尾或者输入非法,j>i 用于非法值处理,比如传入i=0
return error;//返回错误
}
s = (LinkList)malloc(sizeof(Node));//给s分配空间
s->data = e;//s数据域保存元素e,
s->next = p->next;//s指向p的下一个结点或者NULL,即原来第i个位置的结点或者尾部
p->next = s;//原来第i个位置的前一个结点p指向新结点s
return ok; //返回成功
}
Status ListDelete(LinkList *L, int i, ElemType *e) {//删除链表L的第i个结点,并用e返回其值
int j;//定义计数器
LinkList p, q;//定义两个辅助指针p,q
p = *L;//p指向头结点,因为删除操作需要找到需要删除的结点的前一个结点,头结点是第一个结点的前一个结点
j = 1;// 初始化计数器为1
while (p->next != (*L) && j < i) {//当到达最后一个元素,或者找到第i个结点的前一个结点时结束循环
p = p->next;//p指向下一个节点
++j;//计数器自加1
}
if ((p->next) == *L || j > i) {//如果到达最后一个元素或者非法输入,j>i 用于非法值处理,比如传入i=0
return error;//返回错误
}
q = p->next;//q指向要删除的结点,即p的下一个结点
p->next = q->next;//前一个结点p越过要删除的结点q指向q的下一个结点
*e = q->data;//把要删除的结点p的数据给参数e
free(q);//删除结点q
return ok;//返回成功
}
int ListLength(LinkList L) {//返回链表L的长度
int j;//定义计数器j
LinkList p;//定义辅助指针P
p = L->next;//p指向第一个结点
j = 0;//计数器初始化为0
while (p != L) {//当到表尾时退出循环
p = p->next;//p指向下一个结点
++j;//计数器自加1
}
return j;//返回链表元素的个数,即计数器j的值
}
void PrintList(LinkList L) {//遍历打印链表
int i;//定义循环变量i
ElemType e;//定义一个数据变量用来保存查找到的值
for (i = 1; i < ListLength(L) + 1; i++) {//循环从1开始,到最后一个元素结束,即链表的长度值相同位置的元素
if (GetElem(L, i, &e)) {//如果查找第i个元素成功
printf("%d ",e);//打印被保存在e中的元素数据
}
}
printf("\n");
}
void PrintByCount(LinkList L, int n) {//打印循环链表L的n个值
int j;//定义计数器j
LinkList p;//定义辅助指针p
p = L;//p指向单链表的头结点,从头结点开始遍历
j = 1;//计数器初始化为1
while (n) {//n没有自减到0就继续循环
p = p->next;//p指向下一个结点
if (p == L) {//如果p指向了头结点,表示遍历了一圈
p = p->next;//跳过头结点
}
printf("%d ", p->data);//打印遍历到的值
++j;//计数器自加
--n;//n自减
}
printf("\n");
}
void JosephusProblem(LinkList L, int n) {//约瑟夫问题,参数n表示每数到几删除一个元素
LinkList p, q;//定义连个辅助指针
int j = n;//定义计数器的初值为参数n
p = L; //指针p一开始指向头结点,便于删除元素
int length = ListLength(L);//获得链表的初始长度
while(length > 1) {//当长度大于1时,继续循环约瑟夫环
--j;//计数器自减
if (j == 0) {//当计数器自减到0,表示数了n个数,需要杀死一个元素
q = p->next;//指针q指向要删除的元素
p->next = q->next;//要删除的前一个元素的指针指向要删除的下一个元素
printf("约瑟夫环:%d被杀死!\n", q->data);//输出要删除的元素值
free(q);//删除结点
--length;//目前长度自减1
j = n;//计数器重新开始计数
} else {//如果还没有数够n个数
p = p->next;//p继续指向下一个元素
}
if (p->next == L) {//如果p的下一个元素是头结点
p = p->next;//跳过头结点
}
}
printf("约瑟夫环:幸存者为:%d\n", L->next->data);//输出幸存者
}
LinkList CreateLinklistByArray(ElemType Array[], int length) {//根据数组进行整表创建,并返回创建的循环链表
LinkList p, q;//定义两个辅助指针
int i;//定义for循环变量
LinkList L = (LinkList)malloc(sizeof(Node));//给头结点分配空间
L->next = L;//把头结点的指针域指向自己
p = L;//辅助指针p指向头结点
for (i = 0; i < length; i++) {//开始遍历数组,根据数组创建链表
q = (LinkList)malloc(sizeof(Node));//给辅助指针q分配空间
q->data = Array[i];//q的数据域为数组的元素值
p->next = q;//把之前链表的最后一个结点的指针指向新的结点
p = q;//辅助指针p移动到表尾
}
p->next = L;//把表尾结点的指针指向头结点
return L;//返回新创建的链表
}
代码的运行结果如下图所示: