上次学习了单链表,这次来学习双链表。二者之间的区别是,单链表中的每个结点只存有后继结点的地址,而双链表中则存了两个地址,一个是前驱结点的地址,一个是后继结点的地址。
结构体
struct ListNode{
int element; //数据域
struct ListNode *next; //后继结点的地址
struct ListNode *prev; //前驱结点的地址
};
这里的结构体中存有了两个链表结点ListNode类型的指针,一个指向前驱结点,一个指向后继结点。头结点的prev置空,尾结点的next置空。
初始化链表
void InitListNode(struct ListNode *head){
head->next=NULL;
head->prev=NULL;
}
初始化链表就是对现有的头结点进行准备操作。头结点的数据域我们一般不会用到,所以可以不用管数据域。将next和prev置空,后续需要插入元素时,再对next进行操作。
插入操作
int InsertListNode(struct ListNode *head,int element,int index){
if (index<1) return 0; //判断插入位置是否合法
struct ListNode *p=head;
while(index>1){ //循环操作将指针移至待插入位置的前驱结点
p=p->next;
if (p==NULL) return 0;
index--;
}
struct ListNode *node =malloc(sizeof(struct ListNode)); //分配新结点的内存空间
if (node==NULL) return 0; //判断空间是否分配成功
node->element=element;
if (p->next==NULL){ //第一种情况,插入在链表尾端
p->next=node;
node->prev=p;
node->next=NULL;
}else{ //第二种情况,插入不在尾端
node->prev=p;
node->next=p->next;
p->next=node;
node->next->prev=node;
}
return 1;
}
插入操作中,与单链表不同的是,我按插入位置是否在链表尾端分成了两种情况来讨论,这样便于理解。
第一种情况,插入位置在尾端,当指针通过循环到达指定位置的前驱结点时,因为下一个结点为空,也就意味着当前结点时尾结点,在此节点后再插入结点就是新的尾结点,而当前结点p就是待插入结点的前驱结点。这种情况逻辑上较为简单,只用将当前结点的next连向待插入结点,然后将新的尾结点的prev指向前驱结点,next置空。
第二种情况,插入位置不在尾端,循环结束后,如果下一结点非空,就代表当前结点不是尾结点,这时的p是待插入结点的前驱结点。这种情况稍微复杂一点,需要反复理解。第一步,先将待插入结点的next和prev分别指向p和p的后继结点。第二步,将p的next指向新的node结点,再将node的后继结点的prev指向node,这里注意node的后继结点的寻找方法,需要先用刚刚第一步中的node->next
来找到后继结点,因为当p->next
改变,不再指向这个结点时,现在只有node->next
指向该结点,否则通过其它结点无法找到该结点,这里注意理解。
这里还需要注意的是,第一步和第二步的顺序不能颠倒。如果先操作第二步,将会出现的问题是,node->prev
和node->next
找不到前后两段需要连接的链表。
删除操作
int DeleteListNode(struct ListNode *head,int index){
struct ListNode *p=head;
if (index<1) return 0;
while(index>1){ //循环操作找到前驱结点
p=p->next;
if(p==NULL) return 0;
index--;
}
if(p->next->next){ //需要删除的结点不是尾结点
struct ListNode *q=p->next;
p->next=p->next->next;
p->next->prev=p;
free(q);
}else{ //需要删除的结点是尾结点
struct ListNode *q=p->next;
p->next=NULL;
q->prev=NULL;
free(q);
}
return 0;
}
删除操作为了便于理解,也按照是否为尾结点分成了两种情况。第一种情况,因为p是前驱结点,所以p->next
是需要删除的结点,而判断p->next->next
则是看需要删除的结点是否有后继结点,如果p->next->next
存在,说明需要删除的结点后面仍存在结点,所以需要删除的结点不是尾结点,否则是尾结点。
当不是尾结点时,用一个新的指针指向需要删除的结点,否则当链表的指针跳过该结点连接后,会找不到该节点。当该结点是尾结点时,用一个新的指针标记后,直接将前驱结点的next置空即可。两种情况的最后,都需要使用free()
函数释放被删除结点的内存,完成删除。
获取操作
int GetListNode(struct ListNode *head,int index){
if(index<1) return 0;
struct ListNode *p=head;
while (index>1){
p=p->next;
index--;
}
return p->next->element;
}
这里单链表一样,通过循环操作,将指针移到前一个结点,返回该结点的后继结点的数据域。
查找操作
int FindListNode(struct ListNode *head,int target){
struct ListNode *p=head;
int i=0;
while(p->next){
p=p->next;
i++;
if(p->element==target) return i;
}
return 0;
}
通过循环,遍历链表上的每一个结点,比较数据域和待查找元素,如果相等,返回该结点的位序,如果循环结束,还没有找到,那就返回错误信息0,代表未找到该元素。
获取链表长度
int ListNodeSize(struct ListNode *head){
struct ListNode *p=head;
int i=0;
while(p->next){
p=p->next;
i++;
}
return i;
}
通过循环,遍历链表元素,每遍历一个,计数器自增一次,最后计数器计的数值就是链表长度,直接返回计数器就可以。
打印链表
void PrintListNode(struct ListNode *head){
struct ListNode *p=head;
while(p->next){
p=p->next;
printf("%d ",p->element);
}
printf("\n");
}
同样,为了方便查看链表情况,我们编写此函数来从头打印链表的每一个元素。
完整代码
#include <stdio.h>
#include <stdlib.h>
struct ListNode{
int element;
struct ListNode *next;
struct ListNode *prev;
};
void InitListNode(struct ListNode *head){
head->next=NULL;
head->prev=NULL;
}
int InsertListNode(struct ListNode *head,int element,int index){
if (index<1) return 0;
struct ListNode *p=head;
while(index>1){
p=p->next;
if (p==NULL) return 0;
index--;
}
struct ListNode *node =malloc(sizeof(struct ListNode));
if (node==NULL) return 0;
node->element=element;
if (p->next==NULL){
p->next=node;
node->prev=p;
node->next=NULL;
}else{
p->next->prev=node;
node->next=p->next;
p->next=node;
node->prev=p;
}
return 1;
}
int DeleteListNode(struct ListNode *head,int index){
struct ListNode *p=head;
if (index<1) return 0;
while(index>1){
p=p->next;
if(p==NULL) return 0;
index--;
}
if(p->next->next){
struct ListNode *q=p->next;
p->next=p->next->next;
p->next->prev=p;
free(q);
}else{
struct ListNode *q=p->next;
p->next=NULL;
q->prev=NULL;
free(q);
}
return 0;
}
int GetListNode(struct ListNode *head,int index){
if(index<1) return 0;
struct ListNode *p=head;
while (index>1){
p=p->next;
index--;
}
return p->next->element;
}
int FindListNode(struct ListNode *head,int target){
struct ListNode *p=head;
int i=0;
while(p->next){
p=p->next;
i++;
if(p->element==target) return i;
}
return 0;
}
int ListNodeSize(struct ListNode *head){
struct ListNode *p=head;
int i=0;
while(p->next){
p=p->next;
i++;
}
return i;
}
void PrintListNode(struct ListNode *head){
struct ListNode *p=head;
while(p->next){
p=p->next;
printf("%d ",p->element);
}
printf("\n");
}
int main(){
...
return 0;
}
标签:node,结点,ListNode,struct,int,next,算法,双链,数据结构
From: https://blog.csdn.net/messcodeab/article/details/143207818