概述
本篇文章将向您介绍循环单/双链表的插入、删除、遍历操作原理及如何进行,文章最后会给正在学习链表操作的小伙伴提供两道力扣经典习题,后附参考代码。
1.循环单链表的插入操作
如图,对于插入数据的操作,基本与单链表的插入操作相同,我们可以创建一个独立的结点,通过将需要插入的结点的上一个结点的next指针指向该节点,再由需要插入的结点的next指针指向下一个结点的方式完成插入操作。
其代码可以表示为:
//插入元素
list *insert_list(list *head,int pos,int data){
//三个参数分别是链表,位置,参数
list *node=initlist(); //新建结点
list *p=head; //p表示新的链表
list *t;
t=p;
node->data=data;
if(head!=NULL){
for(int i=1;i<pos;i++){
t=t->next; //走到需要插入的位置处
}
node->next=t->next;
t->next=node;
return p;
}
return p;
}
2. 循环单链表的删除操作
如图所示,循环单链表的删除操作可以参考单链表的删除操作,其都是找到需要删除的结点,将其前一个结点的next指针直接指向删除结点的下一个结点即可,但需要注意的是尾节点和头结点的特判,尤其是尾结点,因为删除尾节点后,尾节点前一个结点就成了新的尾节点,这个新的尾节点需要指向的是头结点而不是空,其重点可以记录为【当前的前一节点.next=自身结点.next】这样的操作可以省去头尾结点的特判:
其代码可以表示为:
//删除元素
int delete_list(list *head) {
if(head == NULL) {
printf("链表为空!\\n");
return 0;
}
//建立临时结点存储头结点信息(目的为了找到退出点)
//如果不这么建立的化需要使用一个数据进行计数标记,计数达到链表长度时自动退出
//循环链表当找到最后一个元素的时候会自动指向头元素,这是我们不想让他发生的
list *temp = head;
list *ptr = head->next;
int del;
printf("请输入你要删除的元素:");
scanf("%d",&del);
while(ptr != head) {
if(ptr->data == del) {
if(ptr->next == head) {
temp->next = head;
free(ptr);
return 1;
}
temp->next = ptr->next; //核心删除操作代码
free(ptr);
//printf("元素删除成功!\\n");
return 1;
}
temp = temp->next;
ptr = ptr->next;
}
printf("没有找到要删除的元素\\n");
return 0;
}
3. 循环单链表的遍历
与普通的单链表和双向链表的遍历不同,循环链表需要进行结点的特判,找到尾节点的位置,由于尾节点的next指针是指向头结点的,所以不能使用链表本身是否为空(NULL)的方法进行简单的循环判断,我们需要通过判断结点的next指针是否等于头结点的方式进行是否完成循环的判断。
此外还有一种计数的方法,即建立一个计数器count=0,每一次next指针指向下一个结点时计数器加一,当count数字与链表的节点数相同的时候即完成循环,这样做有一个问题,就是获取到链表的节点数同时也需要完成一次遍历才可以达成目标。
其代码可以表示为:
//遍历元素
int display(list *head) {
if(head != NULL) {
list *p = head;
//遍历头节点到,最后一个数据
while(p->next != head ) {
printf("%d ",p->next->data);
p = p->next;
}
printf("\\n"); //习惯性换行 ( o=^•ェ•)o ┏━┓
//把最后一个节点赋新的节点过去
return 1;
} else {
printf("头结点为空!\\n");
return 0;
}
}
4. 进阶概念—双向循环链表
循环链表还有一个进阶的概念练习,同双向链表与单链表的关系一样,循环单链表也有一个孪生兄弟——双向循环链表,其设计思路与单链表和双向链表的设计思路一样,就是在原有的双向链表的基础上,将尾部结点和头部结点进行互相连接,这个链表的设计不难,就交给读者自主进行设计。
5. 循环单链表代码
#include<stdio.h>
#include<stdlib.h>
typedef struct list{
int data;
struct list *next;
}list;
//data为存储的数据,next指针为指向下一个结点
//初始结点
list *initlist(){
list *head=(list*)malloc(sizeof(list));
if(head==NULL){
printf("创建失败,退出程序");
exit(0);
}else{
head->next=NULL;
return head;
}
}
//创建--插入数据
int create_list(list *head){
int data; //插入的数据类型
printf("请输入要插入的元素:");
scanf("%d",&data);
list *node=initlist();
node->data=data;
//初始化一个新的结点,准备进行链接
if(head!=NULL){
list *p=head;
//找到最后一个数据
while(p->next!=head){
p=p->next;
}
p->next=node;
node->next=head;
return 1;
}else{
printf("头结点已无元素\\n");
return 0;
}
}
//插入元素
list *insert_list(list *head,int pos,int data){
//三个参数分别是链表,位置,参数
list *node=initlist(); //新建结点
list *p=head; //p表示新的链表
list *t;
t=p;
node->data=data;
if(head!=NULL){
for(int i=1;i<=pos;i++){
t=t->next;
}
node->next=t->next;
t->next=node;
return p;
}
return p;
}
//删除元素
int delete_list(list *head) {
if(head == NULL) {
printf("链表为空!\\n");
return 0;
}
//建立零时结点存储头结点信息(目的为了找到退出点)
//如果不这么建立的化需要使用一个数据进行计数标记,计数达到链表长度时自动退出
//循环链表当找到最后一个元素的时候会自动指向头元素,这是我们不想让他发生的
list *temp = head;
list *ptr = head->next;
int del;
printf("请输入你要删除的元素:");
scanf("%d",&del);
while(ptr != head) {
if(ptr->data == del) {
if(ptr->next == head) { //循环结束的条件换成ptr->next == head
temp->next = head;
free(ptr);
return 1;
}
temp->next = ptr->next;
free(ptr);
//printf("元素删除成功!\\n");
return 1;
}
temp = temp->next;
ptr = ptr->next;
}
printf("没有找到要删除的元素\\n");
return 0;
}
//遍历元素
int display(list *head) {
if(head != NULL) {
list *p = head;
//遍历头节点到,最后一个数据
while(p->next != head ) {
printf("%d ",p->next->data);
p = p->next;
}
printf("\\n"); //习惯性换行 ( o=^•ェ•)o ┏━┓
//把最后一个节点赋新的节点过去
return 1;
} else {
printf("头结点为空!\\n");
return 0;
}
}
int main(){
//初始化头结点//
list *head=initlist();
head->next=head;
通过插入元素完善链表/
for(int i=0;i<5;i++){ //只是演示使用,不具体提供输入
create_list(head);
}
display(head);
插入元素
head=insert_list(head,1,10);
display(head);
删除元素
delete_list(head);
display(head);
return 0;
}
6. 双向链表的插入操作
如图所示:
对于每一次的双向链表的插入操作,我们首先需要创建一个独立的结点并通过malloc操作开辟相应的空间,其次我们选中这个新创建的独立节点,将其的pre指针指向所需插入位置的前一个结点,同时,其所需插入的前一个结点的next指针修改指向为该新的结点,同理,该新的结点的next指针将会指向一个原本的下一个结点,而修改下一个结点的pre指针为指向新结点自身,这样的一个操作我们称之为双向链表的插入操作。
其代码可以表示为:
//插入数据
line * insertLine(line * head,int data,int add){
//三个参数分别为:进行此操作的双链表,插入的数据,插入的位置
//新建数据域为data的结点
line * temp=(line*)malloc(sizeof(line));
temp->data=data;
temp->pre=NULL;
temp->next=NULL;
//插入到链表头,要特殊考虑
if (add==1) {
temp->next=head;
head->pre=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->pre=body;
}else{
body->next->pre=temp;
temp->next=body->next;
body->next=temp;
temp->pre=body;
}
}
return head;
}
7. 双向链表的删除操作
如图:
删除操作的过程是:选择需要删除的结点,选中这个结点的前一个结点,将前一个结点的next指针指向自己的下一个结点,同时,选中该节点的下一个结点,将下一个结点的pre指针修改指向为自己的上一个结点,这样产生的效果就是在进行遍历的时候直接将这一个结点给跳过了。
在这样的指针修改操作之后,我们释放删除结点,归还空间给内存,这样的操作我们称之为双链表的删除操作。
其代码可以表示为:
//删除元素
line * deleteLine(line * head,int data){
//输入的参数分别为进行此操作的双链表,需要删除的数据
line * list=head;
//遍历链表
while (list) {
//判断是否与此元素相等
//删除该点方法为将该结点前一结点的next指向该节点后一结点
//同时将该结点的后一结点的pre指向该节点的前一结点
if (list->data==data) {
list->pre->next=list->next;
list->next->pre=list->pre;
free(list);
printf("--删除成功--\\n");
return head;
}
list=list->next;
}
printf("Error:没有找到该元素,没有产生删除\\n");
return head;
}
8. 双向链表的遍历
如同单链表的遍历一样,利用next指针逐步向后进行索引即可,注意判断这里,我们既可以用while(list)的操作直接判断是否链表为空,也可以使用while(list->next)的操作判断该链表是否为空,其下一节点为空和本结点是否为空的判断条件是一样的效果,当然了,善用双向链表的pre指针进行有效的遍历也是值得去尝试的。
其简单的代码可以表示为:
//遍历双链表,同时打印元素数据
void printLine(line *head){
line *list = head;
int pos=1;
while(list){
printf("第%d个数据是:%d\\n",pos++,list->data);
list=list->next;
}
}
9. 配套练习题目
双向链表的单独训练数据并不是很多,在网上的资料相比单链表也是偏少,不过我们在熟悉原理之后可以同单链表的题目,可以尝试使用双链表进行做题,如下题:
10. 本题目案例代码
#include<stdio.h>
#include<stdlib.h>
typedef struct line{
int data; //data
struct line *pre; //pre node
struct line *next; //next node
}line;
//分别表示该结点的前驱(pre),后继(next),以及当前数据(data)
//遍历双链表,同时打印元素数据
void printLine(line *head){
line *list = head;
int pos=1;
while(list){
printf("第%d个数据是:%d\\n",pos++,list->data);
list=list->next;
}
}
//创建双链表
line* initLine(line * head){
int number,pos=1,input_data;
printf("请输入创建结点的大小\\n");
scanf("%d",&number);
if(number<1){return NULL;} //输入非法直接结束
//头结点创建///
head=(line*)malloc(sizeof(line));
head->pre=NULL;
head->next=NULL;
printf("输入第%d个数据\\n",pos++);
scanf("%d",&input_data);
head->data=input_data;
line * list=head;
while (pos<=number) {
line * body=(line*)malloc(sizeof(line));
body->pre=NULL;
body->next=NULL;
printf("输入第%d个数据\\n",pos++);
scanf("%d",&input_data);
body->data=input_data;
list->next=body;
body->pre=list;
list=list->next;
}
return head;
}
//插入数据
line * insertLine(line * head,int data,int add){
//三个参数分别为:进行此操作的双链表,插入的数据,插入的位置
//新建数据域为data的结点
line * temp=(line*)malloc(sizeof(line));
temp->data=data;
temp->pre=NULL;
temp->next=NULL;
//插入到链表头,要特殊考虑
if (add==1) {
temp->next=head;
head->pre=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->pre=body;
}else{
body->next->pre=temp;
temp->next=body->next;
body->next=temp;
temp->pre=body;
}
}
return head;
}
//删除元素
line * deleteLine(line * head,int data){
//输入的参数分别为进行此操作的双链表,需要删除的数据
line * list=head;
//遍历链表
while (list) {
//判断是否与此元素相等
//删除该点方法为将该结点前一结点的next指向该节点后一结点
//同时将该结点的后一结点的pre指向该节点的前一结点
if (list->data==data) {
list->pre->next=list->next;
list->next->pre=list->pre;
free(list);
printf("--删除成功--\\n");
return head;
}
list=list->next;
}
printf("Error:没有找到该元素,没有产生删除\\n");
return head;
}
int main(){
line *head=NULL;
printf("创建双链表操作\\n");
head=initLine(head);
printLine(head);
//create line
printf("插入操作\\n");
head=insertLine(head,40,2); //为了简化直接写参数了
printLine(head);
//insert Line
printf("删除操作\\n");
head=deleteLine(head,2); //为了简化直接写参数了
printLine(head);
//delete Line
return 0;
}