循环单链表,手把手注释源代码讲解,外加约瑟夫环源代码

    我通过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;//返回新创建的链表 
}

代码的运行结果如下图所示:

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值