文章目录
- 一、循环链表
- (一)概念
- (二)示意图
- (三)操作
- 1. 创建循环链表
- (1)函数声明
- (2)注意点
- (3)代码实现
- 2. 插入(头插,尾插,任意位置插入)
- (1)头插
- ① 函数声明
- ② 注意点
- ③ 代码实现
- (2)尾插
- ① 函数声明
- ② 注意点
- ③ 代码实现
- (3)任意位置插入
- ① 函数声明
- ② 注意点
- ③ 代码实现
- 3. 删除(头删,尾删,任意位置删除)
- (1)头删
- ① 函数声明
- ② 注意点
- ③ 代码实现
- (2)尾删
- ① 函数声明
- ② 注意点
- ③ 代码实现
- (3)任意位置删除
- ① 函数声明
- ② 注意点
- ③ 代码实现
- 4. 修改
- (1)函数定义
- (2)注意点
- (3)代码实现
- 5. 查询
- (1)函数定义
- (2)注意点
- (3)代码实现
- 6. 清空和销毁
- (1)函数定义
- (2)注意点
- (3)代码实现
- 7. 打印链表(方便查看实验现象)
- (1)函数定义
- (2)注意点
- (3)代码实现
- 8. 排序(以正序排序为例)
- (1)函数定义
- (2)注意点
- (3)代码实现
- 9.剔重
- (1)函数定义
- (2)注意点
- (3)代码实现
- (四)应用:实现约瑟夫环
- 1.问题描述
- 2. 问题分析
- 3. 代码实现
- 4. 使用前面的循环链表实现
- 二、代码源码已上传资源
一、循环链表
(一)概念
操作和单向链表的操作基本一样
只是判断链表结束的条件不同
循环链表又分为有头循环链表和无头循环链表,其中无头结点的循环链表相对更常见些,因此下文以实现无头循环链表为例。
(二)示意图
(三)操作
结构体定义:
//数据元素结构体
typedef struct circle_node
{
int data;
struct circle_node *next;
}nd_t;
//数据对象结构体
typedef struct circle_list
{
nd_t *phead; //指向头节点
//还可以添加其他成员
}list_t;
1. 创建循环链表
(1)函数声明
int create_list(list_t **my_list);
创建循环链表的第一个节点,
第一个节点的next指向它自己
将第一个节点的堆区地址传给main函数中的指针
(2)注意点
- 入参不能为空
- 因为需要将申请的第一个节点的地址写入main函数中的指针变量中,因此必须传入二级指针
- 申请内存空间后检查是否申请成功
(3)代码实现
int create_list(list_t **my_list){
if(NULL==my_list){
return -1;
}
*my_list=(list_t *)malloc(sizeof(list_t));
if(NULL==*my_list){
return -1;
}
(*my_list)->phead=NULL;
return 0;
}
2. 插入(头插,尾插,任意位置插入)
(1)头插
① 函数声明
int insert_list_by_head(list_t *my_list,int num);
创建新节点
找到尾节点,将尾节点的next指向新的节点
新节点的next指向首节点
*phead指向pnew
② 注意点
- 入参不能为NULL
- 头插需要更改main函数中phead指针的值,因此需要传二级指针
- 需要保证链表中至少有一个节点,无头链表中只要phead不为NULL,就说明至少有一个节点
③ 代码实现
int insert_list_by_head(list_t *my_list,int num){
//入参不合理
if(NULL==my_list)
return -1;
//创建新节点
nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
if(NULL==pnew)
return -1;
pnew->data=num;
//插入首节点
if(NULL==my_list->phead){
my_list->phead=pnew;
pnew->next=pnew;
return 0;
}
//如果插入的不是第一个节点
//先找到尾节点
nd_t *ptemp=my_list->phead;
while(ptemp->next!=my_list->phead)
{
ptemp=ptemp->next;
}
//插入
ptemp->next=pnew;
pnew->next=my_list->phead;
my_list->phead=pnew;
return 0;
}
(2)尾插
① 函数声明
int insert_list_by_tail(list_t *my_list,int num);
创建新节点
找到尾节点,将尾节点的next指向新的节点
新节点的next指向首节点
② 注意点
- 头插和尾插的区别仅在于是否需要修改main函数中的指针变量
③ 代码实现
int insert_list_by_tail(list_t *my_list,int num){
if(NULL==my_list)
return -1;
//创建新节点
nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
if(NULL==pnew)
return -1;
pnew->data=num;
//插入首节点
if(NULL==my_list->phead){
my_list->phead=pnew;
pnew->next=pnew;
return 0;
}
//找到尾节点
nd_t *ptemp=my_list->phead;
while(ptemp->next!=my_list->phead)
{
ptemp=ptemp->next;
}
//插入
ptemp->next=pnew;
pnew->next=my_list->phead;
return 0;
}
(3)任意位置插入
① 函数声明
int insert_list_by_pos(list_t *my_list,int pos,int num);
找到要插入的位置的前一个节点
创建新节点
插入节点
② 注意点
- 不支持插入在第一个位置
- 如果插入的前一个位置在最后一个可以,但是在第一个就不合理了
③ 代码实现
int insert_list_by_pos(list_t *my_list,int pos,int num){
if(NULL==my_list)
return -1;
if(pos<0)
return -1;
//找到要插入的节点的前一位
nd_t *ptemp=my_list->phead;
int i=0;//记录ptemp移动了几步
while(ptemp->next!=my_list->phead){
if(i==pos-1) break;
ptemp=ptemp->next;
i++;
}
//如果已经到尾节点了还没移动到pos位置就说明位置不合理
if(i<pos-1){
printf("位置不合理\n");
return -1;
}
//创建新节点
nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
if(NULL==pnew)
return -1;
pnew->data=num;
//插入第一个节点
if(NULL==my_list->phead){
my_list->phead=pnew;
pnew->next=pnew;
return 0;
}
//插入到头节点
if(0==pos){
nd_t *ptail=my_list->phead;
while(ptail->next!=my_list->phead)
ptail=ptail->next;
ptail->next=pnew;
pnew->next=my_list->phead;
my_list->phead=pnew;
return 0;
}
//插入节点
pnew->next=ptemp->next;
ptemp->next=pnew;
return 0;
}
3. 删除(头删,尾删,任意位置删除)
(1)头删
① 函数声明
delete_list_by_head(list_t *my_list);
找到尾节点
尾节点的next置成*phead->next
*phead=*phead->next
free(pdef)
② 注意点
- 需要传入二级指针
- 当表中只有一个节点时,进行删除操作时相当于将表销毁了。
③ 代码实现
int delete_list_by_head(list_t *my_list){
//传参为空或者表为空
if(NULL==my_list||NULL==my_list->phead)
return -1;
//只有一个节点
if(my_list->phead->next==my_list->phead){
free(my_list->phead);
my_list->phead=NULL;
printf("表已清空\n");
return 0;
}
//多个节点
//找到尾节点
nd_t *ptemp=my_list->phead->next;
while(ptemp->next!=my_list->phead)
{
ptemp=ptemp->next;
}
//头删
nd_t *pdel=my_list->phead;
my_list->phead=my_list->phead->next;
ptemp->next=my_list->phead;
free(pdel);
pdel=NULL;
return 0;
}
(2)尾删
① 函数声明
int delete_list_by_tail(list_t *my_list);
找到尾节点的前一节点,
尾删操作
② 注意点
- 需要传入二级指针
- 当表中只有一个节点时,进行删除操作时相当于将表销毁了。
③ 代码实现
int delete_list_by_tail(list_t *my_list){
//传参为空或者表为空
if(NULL==my_list||NULL==my_list->phead)
return -1;
//只有一个节点
if(my_list->phead->next==my_list->phead){
free(my_list->phead);
my_list->phead=NULL;
printf("表已清空\n");
return 0;
}
//多个节点
//找到尾节点的前一个节点
nd_t *ptemp=my_list->phead->next;
while(ptemp->next->next!=my_list->phead)
{
ptemp=ptemp->next;
}
//尾删
nd_t *pdel=ptemp->next;
ptemp->next=my_list->phead;
free(pdel);
pdel=NULL;
return 0;
}
(3)任意位置删除
① 函数声明
int delete_list_by_pos(list_t *my_list,int pos);
② 注意点
- 链表中至少有一个节点
- 如果只有一个节点时要删除第0个位置可以成功,此时链表销毁;其他位置均为不合理
- 如果多个节点时,需要区分是不是要删除头节点
- ptemp是要删除的节点的前一个节点,它不能是尾节点
③ 代码实现
int delete_list_by_pos(list_t *my_list,int pos){
//传参为空或者表为空
if(NULL==my_list||NULL==my_list->phead)
return -1;
if(0>pos){
return -1;
}
//如果链表中只有一个节点
if(my_list->phead->next==my_list->phead){
//删除第一个位置的节点
if(0==pos){
free(my_list->phead);
my_list->phead=NULL;
return 0;
}
//删除其他位置节点,均是位置不合理
printf("位置不合理\n");
return -1;
}
//链表有多个节点
nd_t *pdel=my_list->phead;
//删除第一个位置的节点
//找到尾节点
nd_t *ptemp=my_list->phead;
while(ptemp->next!=my_list->phead)
{
ptemp=ptemp->next;
}
if(0==pos){
my_list->phead=my_list->phead->next;
ptemp->next=my_list->phead;
free(pdel);
pdel=NULL;
return 0;
}
//删除其他位置节点
//找到要删除的节点的前一个节点
ptemp=my_list->phead;
for(int i=0;i<pos-1;i++){
//要删除的节点的前一个节点不能是尾节点
if(ptemp->next->next==my_list->phead){
printf("删除位置不合理\n");
return -1;
}
ptemp=ptemp->next;
}
pdel=ptemp->next;
ptemp->next=pdel->next;
free(pdel);
pdel=NULL;
return 0;
}
4. 修改
(1)函数定义
int modify_list_by_pos(list_t *my_list,int pos,int num);
遍历链表找到第pos个位置
(2)注意点
- 如果已经到达尾节点就不能再继续向下遍历修改
(3)代码实现
int modify_list_by_pos(list_t *my_list,int pos,int num){
//至少有一个节点
if(NULL==my_list||NULL==my_list->phead)
return -1;
if(0>pos){
return -1;
}
//找到第pos个位置
nd_t *ptemp=my_list->phead;
for(int i=0;i<pos;i++){
if(ptemp->next==my_list->phead){
printf("位置不合理\n");
return -1;
}
ptemp=ptemp->next;
}
ptemp->data=num;
return 0;
}
5. 查询
(1)函数定义
int search_list_by_pos(list_t *my_list,int pos,int *num);
找到第pos个位置
读取数据域数据
(2)注意点
- 查询和修改的唯一区别是对数据的处理,修改是将新的数据写到第pos尾的数据域;修改是将pos位的数据域写到num中
- 循环结束条件与修改和删除一样
(3)代码实现
int search_list_by_pos(list_t *my_list,int pos,int *num){
//至少有一个节点
if(NULL==my_list||NULL==my_list->phead||NULL==num)
return -1;
if(0>pos){
return -1;
}
//找到第pos个位置
nd_t *ptemp=my_list->phead;
for(int i=0;i<pos;i++){
if(ptemp->next==my_list->phead){
printf("位置不合理\n");
return -1;
}
ptemp=ptemp->next;
}
*num=ptemp->data;
return 0;
}
6. 清空和销毁
(1)函数定义
int clean_list(list_t *my_list);
int destory_list(list_t **my_list);
先判断是不是只有一个节点
采用尾删(使用头删的话还要一直修改main函数中的指针变量的值)
(2)注意点
- 使用二级指针
- 入参不能为空
(3)代码实现
int clean_list(list_t *my_list){
if(NULL==my_list){
return -1;
}
//不止一个节点
//采用头删,先找到尾节点前一个节点
while(my_list->phead!=NULL)
{
delete_list_by_head(my_list);
}
return 0;
}
int destory_list(list_t **my_list){
if(NULL==my_list||NULL==*my_list){
return -1;
}
clean_list(*my_list);
//此时只有一个节点了
free(*my_list);
*my_list=NULL;
return 0;
}
7. 打印链表(方便查看实验现象)
(1)函数定义
int print_list(list_t *my_list);
先打印出第一个节点,
遍历链表,直到ptemp->next==phead时结束
(2)注意点
- 在无头链表中phead为NULL,则说明表为空。phead->next==NULL,说明没有第二个节点。
(3)代码实现
int print_list(list_t *my_list){
if(NULL==my_list)
return -1;
nd_t *ptemp=my_list->phead->next;
//打印头节点
printf("%d ",my_list->phead->data);
//当ptemp==mylist->phead时,说明回到了开头
while(ptemp!=my_list->phead){
printf("%d ",ptemp->data);
ptemp=ptemp->next;
}
putchar(10);
return 0;
}
8. 排序(以正序排序为例)
(1)函数定义
int sort_list(list_t *my_list);;
选择排序思路
(2)注意点
- 外层循环可以不比较最后一个元素
(3)代码实现
int sort_list(list_t *my_list){
if(NULL==my_list||NULL==my_list->phead){
return -1;
}
nd_t *p=my_list->phead;
nd_t *q=NULL;
while(p->next!=my_list->phead){
q=p->next;
while(q!=my_list->phead){
if(p->data>q->data){
int temp=p->data;
p->data=q->data;
q->data=temp;
}
q=q->next;
}
p=p->next;
}
return 0;
}
9.剔重
(1)函数定义
int dedup_list(list_t *my_list);
动静指针配合
选择排序思路
(2)注意点
- 此时外层循环使用p->next!=phead或者p!=phead均可,循环链表此刻不会报段错误,但是用第一种效率会相对略高
(3)代码实现
int dedup_list(list_t *my_list){
//至少有一个元素
if(NULL==my_list||NULL==my_list->phead){
return -1;
}
nd_t *p=my_list->phead;
nd_t *q=NULL;
nd_t *m=NULL;
while(p->next!=my_list->phead){
m=p;
q=p->next;
while(q!=my_list->phead){
if(p->data==q->data){
m->next=q->next;
free(q);
q=m->next;
}else{
m=q;
q=q->next;
}
}
p=p->next;
}
}
(四)应用:实现约瑟夫环
1.问题描述
有一位叫约瑟夫的将军,在一次战斗中,连同手下的士兵一起被俘虏了。手下的士兵都非常爱国,宁死不投降,约瑟夫将军想了个办法:
让大家站成一圈,开始数数,从1开始数,数到7的人就自杀,
下一个人重新从1开始数,数到7再自杀,依次类推
直到只剩下一个人为止,最终剩下的就是约瑟夫将军,
然后他不想死,他投降了。这种“圈”,我们称之为“约瑟夫环”
要求:编写代码,模拟约瑟夫环淘汰人的过程,
命令行输入 ./a.out 总人数 数到几自杀 (总人数>1 数到几自杀>1 )
要求程序输出:
第x次 淘汰的是y号
以及最终剩下的是几号
如:输入 ./a.out 5 3 则程序输出
第1次 淘汰的是3号
第2次 淘汰的是1号
第3次 淘汰的是5号
第4次 淘汰的是2号
最后剩下的是 4 号
2. 问题分析
首先需要检查参数的合理性,参数都是以字符串形式保存的,需要转换成int型数据;
无头链表创建链表时就是创建第一个节点,即编号为1的人,之后依次开始创建节点
3. 代码实现
#include <stdio.h>
#include <stdlib.h>
typedef struct _Node{
int data;
struct _Node *next;
}node_t;
node_t *create_circle(int num){
//创建第一个节点
node_t *phead=(node_t *)malloc(sizeof(node_t));
if(NULL==phead) return NULL;
phead->data=1;
phead->next=phead;
//创建剩余节点
node_t *pnew=NULL;
node_t *ptemp=phead;
for(int i=1;i<num;i++){
pnew=(node_t *)malloc(sizeof(node_t));
pnew->data=i+1;
if(NULL==pnew) return NULL;
//找到尾节点
while (ptemp->next!=phead)
ptemp=ptemp->next;
pnew->next=ptemp->next;
ptemp->next=pnew;
}
return phead;
}
int print_circle(node_t *phead){
if(NULL==phead) return -1;
node_t *ptemp=phead;
while(ptemp->next!=phead){
printf("%d ",ptemp->data);
ptemp=ptemp->next;
}
printf("%d ",ptemp->data);
putchar(10);
return 0;
}
int del_circle(node_t *phead,int count){
if(NULL==phead) return -1;
int c=1;
int sum=0;
node_t *pprev=NULL;
while(phead->next!=phead){
if(c==count){
sum++;
c=1;
printf("第%d次淘汰%d号\n",sum,phead->data);
pprev->next=phead->next;
free(phead);
phead=pprev->next;
continue;
}
c++;
pprev=phead;
phead=phead->next;
}
printf("第%d号存活\n",phead->data);
return 0;
}
int main(int argc, char const *argv[])
{
if (argc!=3){
printf("./a/out 总人数 数到几\n");
return -1;
}
int sum=atoi(argv[1]); //人数
int num=atoi(argv[2]); //数到几
node_t *my_list=NULL;
my_list=create_circle(sum);
print_circle(my_list);
del_circle(my_list,num);
return 0;
}
4. 使用前面的循环链表实现
main.c文件:
#include "circle_list.h"
int del(nd_t *phead,int n);
int main(int argc, char const *argv[])
{
if(3 != argc)
{
printf("参数不合理\n");
return -1;
}
int num=atoi(argv[1]); //保存个数
int n=atoi(argv[2]);//数到几
if(num<=0)
{
printf("人数应当大于0\n");
return-1;
}
list_t *my_list=NULL;
//第一个人及其编号
create_list(&my_list);
//后面的人
for(int i=1;i<=num;i++){
if(insert_list_by_tail(my_list,i)){
printf("插入失败\n");
return -1;
}
}
print_list(my_list);
del(my_list->phead,n);
return 0;
}
int del(nd_t *phead,int n)
{
if(NULL==phead)
{
printf("传参错误\n");
return -1;
}
if(0>=n)
{
printf("传参错误,n应当大于0\n");
return -1;
}
int index=0;
nd_t *pptemp=NULL;
while(phead->next!=phead)
{
for(int i=0;i<n-1;i++) //phead默认移到下一位了,故只需要再移动n-1次
{
pptemp=phead;
phead=phead->next;
}
//此时phead是需要删除的节点,执行删除操作
pptemp->next=phead->next;
printf("第%d次删除%d\n",index+1,phead->data);
free(phead);
//删除完成后pead等于后一个节点
phead=pptemp->next;
index++;
}
printf("%d存活\n",phead->data);
free(phead);
phead=NULL;
return 0;
}
二、代码源码已上传资源
链接:
C语言实现循环链表源码链接
C语言实现约瑟夫环源码链接