循环链表:循环链表的next域保存头结点的地址,其余的几乎和单链表是一致的。
下面开始写代码,依旧是先写循环链表的头文件Clist.h
#pragma once
typedef int ELEMTYPE;
//循环链表结构体设计
typedef struct Clist
{
ELEMTYPE data;//数据域 存放数据
struct Clist* next;//指针域 存放下一个节点的地址(尾节点的next保存头结点的地址)
}Clist,*PClist;
// 循环链表拥有的 可执行函数说明:
// 初始化
void InitClist(struct Clist* plist);
// 头插
bool Insert_head(PClist plist,ELEMTYPE val);
// 尾插
bool Insert_tail(PClist plist, ELEMTYPE val);
// 按位置插
bool Insert_pos(PClist plist,int pos, ELEMTYPE val);
// 头删
bool Del_head(PClist plist);
// 尾删
bool Del_tail(PClist plist);
// 按位置删
bool Del_pos(PClist plist,int pos);
// 按值删
bool Del_val(PClist plist,ELEMTYPE val);
// 查找(如果查找到,需要返回这个节点的地址)
struct Clist* Search(struct Clist* plist, ELEMTYPE val);
// 判空
bool IsEmpty(struct Clist* plist);
// 判满(循环链表不需要这个操作)
// 获取长度
int Get_length(PClist plist);
// 清空
void Clear(PClist plist);
// 销毁1
void Destroy1(PClist plist);
// 销毁2
void Destroy2(PClist plist);
// 打印
void Show(struct Clist* plist);
然后接着写循环链表的Clist.cpp文件(注意这里的内容有和单链表不一致的地方就是在尾节点处判断节点是不是为节点我们之前使用的是判断p->next等不等于NULL,而循环链表一般是不存在NULL的,所以尾节点的判断依据就要改为p->next等不等于plist(头结点))
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
#include"Clist.h"
// 初始化(数据域不处理,next域不为空而是赋值为自身的地址)
void InitClist(struct Clist* plist)
{
//assert
//plist->data;
plist->next = plist;//循环链表是几乎不会出现NULL的
}
// 头插
bool Insert_head(PClist plist, ELEMTYPE val)
{
//assert
//1.购买新节点
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val; //购买的新节点 处理完毕
//2.找到插入位置(头插不用找)
//3.插入(通用) 如果是空链这两行代码依旧通用
pnewnode->next = plist->next;
plist->next = pnewnode;
return true;
}
// 尾插
bool Insert_tail(PClist plist, ELEMTYPE val)
{
//assert
//1.购买新节点
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val; //购买的新节点 处理完毕
//2.找到插入位置(通过带前驱的for循环)
struct Clist* p = plist;
for (p; p->next != plist; p = p->next); //此时for循环执行结束,p指向尾节点
//3.插入(通用)
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
// 按位置插
bool Insert_pos(PClist plist, int pos, ELEMTYPE val)
{
assert(plist != NULL);
assert(pos >= 0 && pos <= Get_length(plist));
//1.购买新节点
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val; //购买的新节点 处理完毕
//2.找到插入位置
struct Clist* p = plist;
for (int i = 0; i < pos; i++)
{
p = p->next;
}
//此时for循环执行结束,p指向待插入的合适位置
//3.插入(通用)
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
/* 删除节点:头删 尾删 按位置删
// 1.指针p找到待删除节点的位置
// 2.指针q找到待删除节点的上一个节点
// 3.跨越指向
前两步,有两种方式实现:
第一种:先让p通过标志指向待删除节点,再让q通过p来指向p的前一个节点
第二种:因为我们很明确q的前一个节点就是p,所以我们可以一步到位,
直接通过标记将q的位置找到,这时p可以直接赋值为q->next
*/
// 头删
bool Del_head(PClist plist)
{
//assert
if (IsEmpty(plist)) //不空 则至少存在一个有效值
{
return false;
}
//1.指针p找到待删除节点的位置
struct Clist* p = plist->next;
//2.指针q找到待删除节点的上一个节点
//q就是头结点 这里就不额外处理
//3.跨越指向
plist->next = p->next;
free(p);
return true;
}
// 尾删
bool Del_tail(PClist plist)
{
//assert
if (IsEmpty(plist)) //不空 则至少存在一个有效值
{
return false;
}
//1.指针p找到待删除节点的位置(尾删的话,这里指向为节点)
struct Clist* p = plist->next;
for (p; p->next != plist; p = p->next);
//2.指针q找到待删除节点的上一个节点(倒数第二个节点)
struct Clist* q = plist;
for (q; q->next != p; q = q->next);
//第二种方法
//struct Clist* q = plist;
//for (q; q->next->next != plist; q = q->next);
//此时for结束 q指向倒数第二个节点
//struct Clist* p = q->next;
//3.跨越指向
q->next = p->next;
free(p);
return true;
}
// 按位置删
bool Del_pos(PClist plist, int pos)
{
assert(plist != NULL);
assert(pos >= 0 && pos <= Get_length(plist));
if (IsEmpty(plist)) //不空 则至少存在一个有效值
{
return false;
}
//1.指针p找到待删除节点的位置
//2.指针q找到待删除节点的上一个节点(倒数第二个节点)
//用的第二种方法
struct Clist* q = plist;
for (int i = 0; i < pos; i++)
{
q = q->next;
}
struct Clist* p = q->next;
//3.跨越指向
q->next = p->next;
free(p);
return true;
}
// 按值删
bool Del_val(PClist plist, ELEMTYPE val)
{
//assert
struct Clist* p = Search(plist, val); //直接用搜索函数看链表中是否存在该值
if (p == NULL)
{
return false;
}
struct Clist* q = plist;
for (q; q->next != p; q = q->next);
//跨越指向
q->next = p->next;
free(p);
return true;
}
// 查找(如果查找到,需要返回这个节点的地址)
struct Clist* Search(struct Clist* plist, ELEMTYPE val)
{
//assert
for (struct Clist* p = plist->next; p != plist; p = p->next)
{
if (p->data == val)
{
return p;
}
}
return NULL;
}
// 判空
bool IsEmpty(struct Clist* plist)
{
//assert
return plist->next == plist;
}
// 判满(循环链表不需要这个操作)
// 获取长度
/*指针p从头结点的下一个节点开始跑,如果p遇到头结点,
证明p走了一圈回来了,这是有效节点肯定已经遍历结束*/
int Get_length(PClist plist)
{
//不带前驱的for循环 跑一遍就好
int count = 0;
for (struct Clist* p = plist->next; p != plist; p = p->next)
{
count++;
}
return count;
}
// 清空
void Clear(PClist plist)
{
//assert
Destroy1(plist);
}
//销毁1(无限头删)
void Destroy1(PClist plist)
{
//assert
while (plist->next != plist)
{
struct Clist* p = plist->next;
plist->next = p->next;
free(p);
}
plist->next = plist;
}
最后在主函数中测试我们写的代码的正确性。
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
#include<vld.h>
#include"Clist.h"
//循环链表测试用例
int main()
{
struct Clist head;
InitClist(&head);
for (int i = 0; i < 20; i++)
{
Insert_pos(&head, i, i + 1);
}
Insert_head(&head, 100);
Insert_tail(&head, 200);
Show(&head);
Del_head(&head);
Del_tail(&head);
Show(&head);
printf("length = %d\n ", Get_length(&head));
Del_pos(&head, 3);
Del_val(&head, 14);
Show(&head);
printf("length = %d\n ", Get_length(&head));
//Destroy1(&head);
Destroy2(&head);
return 0;
}
调试结果: