(一)定义
将单链表中终端结点的指针端有空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,检测循环链表
(二)优点
无论从链表的哪一个结点出发,都可以访问到所有的结点
(三)结构
对于单循环链表:
使用尾指针,可以在O(1)时间内找到终端结点和开始结点
使用头指针,我们只能在O(1)时间内找到开始结点。
所以选择尾指针的效率更好
带有头结点
不带头结点
(四)实现循环链表(使用尾指针,不带头结点)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int ElemType;
typedef int Status;
typedef struct Node
{
ElemType data;
struct Node* next;
}Node;
typedef struct Node* CLinkList;
//四个基本操作,初始,清空,判断是否为空,获取长度
Status InitList(CLinkList* L);
Status ClearList(CLinkList* rear);
Status ListEmpty(CLinkList rear);
int ListLength(CLinkList rear);
//四个元素操作,插入,删除,两种查找
Status GetElem(CLinkList rear, int i, ElemType* e);
int LocateElem(CLinkList rear, ElemType e);
Status ListInsert(CLinkList* rear, int i, ElemType e);
Status ListDelete(CLinkList* rear, int i, ElemType* e);
//指定开始位置来打印数据
void PrintListByIndex(CLinkList rear,int index);
//用来打印链表
void PrintList(CLinkList rear);
//四个基本操作,初始,清空,判断是否为空,获取长度
//初始化不带头结点的链表
Status InitList(CLinkList* L)
{
CLinkList rear,q; //rear是尾结点
ElemType item;
rear = q = NULL;
printf("please enter the value of node(enter 0 to finish Initialize)
");
while (1)
{
scanf("%d", &item);
fflush(stdin);
if (item == 0)
break;
if ((*L)==NULL) //进行初始化
{
*L = (CLinkList)malloc(sizeof(Node));
if (!(*L))
return ERROR;
(*L)->data = item;
(*L)->next = *L; //自己指向自己
rear = *L; //设置尾指针位置
}
else //已初始完毕,进行数据插入
{
//生成新的节点,根据尾指针添加节点,并实时更新尾指针。注意这里数据插入是尾插法
q = (CLinkList)malloc(sizeof(Node));
q->data = item;
q->next = rear->next;
rear->next = q;
rear = q;
}
}
*L = rear; //更新指针位置,使原来指针指向链表尾部
return OK;
}
//清空链表
Status ClearList(CLinkList* rear)
{
CLinkList q, p;
q = (*rear)->next; //找到第一个结点
(*rear)->next = NULL;
while (q)
{
p = q;
q = q->next;
free(p);
}
return OK;
}
//判断链表是否为空,若是头指针和尾指针一样,为空
Status ListEmpty(CLinkList rear)
{
if (rear->next == rear) //头指针等于尾指针
return TRUE;
return FALSE;
}
//
//获取列表长度
int ListLength(CLinkList rear)
{
int length = 1;
CLinkList head = rear->next;
while (head != rear)
{
head = head->next;
length++;
}
return length;
}
//四个元素操作,插入,删除,两种查找
//按照索引查找,获取元素
Status GetElem(CLinkList rear, int i, ElemType* e)
{
int j=1;
CLinkList q=rear;
if (i<1 || i>ListLength(rear))
return ERROR;
for (; j <= i;j++)
{
q = q->next;
}
*e = q->data;
return OK;
}
//按照元素进行查找,获取索引
int LocateElem(CLinkList rear, ElemType e)
{
int j;
CLinkList q = rear;
for (j = 1; j <= ListLength(rear);j++)
{
q = q->next;
if (q->data == e)
break;
}
return j;
}
//按照索引进行插入数据
Status ListInsert(CLinkList* rear, int i, ElemType e)
{
if (*rear == NULL && i > ListLength(*rear)+1)
return ERROR;
int j = 1;
CLinkList q;
CLinkList start = (*rear); //若是想插入到第一个位置,必须从尾指针开始
for (; j < i; j++)
start = start->next; //去获取插入的前一个位置
//开始创建一个新的节点
q = (CLinkList)malloc(sizeof(Node));
q->data = e;
q->next = start->next;
start->next = q;
return OK;
}
//进行元素删除
Status ListDelete(CLinkList* rear, int i, ElemType* e)
{
if ((*rear) == NULL || i>ListLength(*rear))
return ERROR;
int j=1;
CLinkList q, p;
q = *rear;
for (; j < i; j++)
q = q->next; //找到前一个元素
p = q->next; //p是我们要删除的那个元素
q->next = p->next;
*e = p->data;
free(p);
return OK;
}
//从指定位置开始打印数据(这个才是体现循环链表的特性)
void PrintListByIndex(CLinkList rear, int index)
{
CLinkList start=rear;
for (int i = 1; i <= index;i++)
start = start->next;
rear = start;
while (start->next!=rear)
{
printf("%d ", start->data);
start = start->next;
}
printf("%d
", start->data);
}
//用来打印链表
void PrintList(CLinkList rear) //L是尾指针
{
CLinkList q = rear->next; //获取头指针
while (q!=rear)
{
printf("%d ", q->data);
q = q->next;
}
printf("%d
", rear->data);
}
int main()
{
CLinkList rear,L=NULL;
ElemType e;
InitList(&L); //初始化后,L变为尾指针
rear = L;
PrintList(rear);
ListInsert(&rear, 3, 999);
PrintList(rear);
ListDelete(&rear, 3, &e);
printf("delete:%d
", e);
GetElem(rear, 4, &e);
printf("get element:%d
", e);
printf("get index:%d by 33
", LocateElem(rear, 33));
PrintListByIndex(rear, 5);
system("pause");
return 0;
}
注意:测试时需要长度超过5,输入的数里面含有33,以便查找,需要插入999这个数。具体自己修改,这里不在重复了
(五)优点
1.可以从任意位置访问所有节点
2.若是想上面使用尾指针构造循环链表,那么开始结点和终端结点的时间复杂度都是O(1)