循环链表
上回咱说到这个单链表,链表有很大的优点,但缺点也很明显,咱就是说他只能从一个节点到其下一个节点,而不能访问到上一个结点,而循环链表就可以解决这一问题,当然,用双向链表更加方便
有时在解决具体问题时,需要我们对其结构进行一个稍微地调整。比如,可以把链表的两头连接,使其成为了一个环状链表,通常称为循环链表。
咱就是说这和它名字的表意一样,只需要将表中最后一个结点的指针指向头结点,链表就能成环儿,还是比较简单的。需要注意的是,虽然循环链表成环状,但本质上还是链表,
需要注意的是,虽然循环链表成环状,但本质上还是链表,因此在循环链表中,依然能够找到头指针和首元节点等。循环链表和普通链表相比,唯一的不同就是循环链表首尾相连,其他都完全一样。
咱也看一下怎么搞得的这个循环链表
1、循环链表设置尾指针。由于在链表的操作过程中,尾指针会不断变化,所以在一些函数的形参中都设置指向头指针的指针。以及链表的结束判断条件变成q是否等于尾指针。
2、注意传递的实参需要取地址
3、循环链表的优势在于双链表合并,以及实现尾插法简单(首先新建结点指向头结点,然后把尾指针的next域指向该新建结点)
4、在创建链表时,使用尾插法,而不是用头插法(因为头插法很难去更新尾指针,使得最后尾指针还需额外更新一次),直接用头插法建立的是头指针,而非尾指针
那咱现在就贴一个代码看一看?嘿,咱这就来了
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
int data;
struct Node * next;
}Node, *LinkList;
LinkList Creat();
void Destroy(LinkList *L);
void Insert(LinkList *L, int val, int index);
void Delete(LinkList *L, int index);
void Traverse(LinkList L);
int main()
{
LinkList L = Creat();
Traverse(L);
Insert(&L, 1, 5);
printf("After inserting is :\n");
Traverse(L);
printf("After deleting is :\n");
Delete(&L, 2);
Traverse(L);
Destroy(&L);
Traverse(L);
}
LinkList Creat()
{
LinkList L = (LinkList)malloc(sizeof(Node));//用L指针指向新建结点,这里L还不算尾指针
int n;
L->data = -1;
L->next = L;//头结点的指针域指向头结点, 注意!这里是对尾指针的初始化。
printf("Please enter the number you want input:(>5)");
scanf_s("%d", &n);
printf("input the number:\n");
for (int i = 0; i < n; i++)
{
LinkList p = (LinkList)malloc(sizeof(Node));
scanf_s("%d", &p->data);
p->next = L->next;
L->next = p;
L = p;
}
return L;//返回尾结点的指针
}
void Destroy(LinkList *L)
{
LinkList q = (*L)->next->next;
LinkList p;
(*L) = (*L)->next;
while (q != (*L))
{
p = q->next;
free(q);
q = p;
}
(*L)->next = (*L);
}
void Insert(LinkList *L, int val, int index)
{
LinkList p = (LinkList)malloc(sizeof(Node));
p->data = val;
LinkList q = (*L)->next;
for (int i = 1; q != (*L) && i < index; i++)
q = q->next;
p->next = q->next;
q->next = p;
if (p == (*L))
(*L) = p;
}
void Delete(LinkList *L, int index)
{
LinkList q = (*L)->next, p;
for (int i = 0; i < index; i++)
q = q->next;
p = q->next;
q->next = p->next;
free(p);
}
void Traverse(LinkList L)
{
LinkList q = L->next->next;//这才是首元结点
while (q != L)
{
printf("%d->", q->data);
q = q->next;
}
printf("NULL\n");
咱也是给咱的增删改查反也弄上去了,这部分其实和单向链表的操作差不多。【LeetCode力扣刷题 | 剑指Offer 24. 反转链表-哔哩哔哩】https://b23.tv/myXqF2V
我也是看到了一个反转链表的视频,虽说不是c语言,但是原理都是一样的原理,无伤大雅。
双向链表
循环链表就这么些东西,接下来咱来聊聊上次没聊完的双向链表。
其实这个也很简单,单向链表存了一个下一个节点的指针,咱类推一下,咱都存了next,为什么不再存一个prior
#include <stdio.h>
#include <stdlib.h>
typedef struct line{
struct line * prior;
int data;
struct line * next;
}line;
来方便咱之后的使用呢?对吧。
双向链表添加节点
根据数据添加到双向链表中的位置不同,可细分为以下 3 种情况:
添加至表头
将新数据元素添加到表头,只需要将该元素与表头元素建立双层逻辑关系即可。
换句话说,假设新元素节点为 temp,表头节点为 head,则需要做以下 2 步操作即可:
temp->next=head; head->prior=temp;
将 head 移至 temp,重新指向新的表头;
添加至表的中间位置
同单链表添加数据类似,双向链表中间位置添加数据需要经过以下 2 个步骤。
新节点先与其直接后继节点建立双层逻辑关系;新节点的直接前驱节点与之建立双层逻辑关系;双向链表中间位置添加数据元素
双向链表中间位置添加数据元素添加至表尾与添加到表头是一个道理,
找到双链表中最后一个节点;让新节点与最后一个节点进行双层逻辑关系;双向链表尾部添加数据元素
我这边也是给大家找了几个图,便于咱的理解
line * insertLine(line * head,int data,int add){
//新建数据域为data的结点
line * temp=(line*)malloc(sizeof(line));
temp->data=data;
temp->prior=NULL;
temp->next=NULL;
//插入到链表头,要特殊考虑
if (add==1) {
temp->next=head;
head->prior=temp;
head=temp;
}else{
line * body=head;
//找到要插入位置的前一个结点
for (int i=1; i<add-1; i++) {
body=body->next;
}
//判断条件为真,说明插入位置为链表尾
if (body->next==NULL) {
body->next=temp;
temp->prior=body;
}else{
body->next->prior=temp;
temp->next=body->next;
body->next=temp;
temp->prior=body;
}
}
return head;
}
那咱这次的链表就先讲到这里,还有啥,咱下次再聊,拜拜。