1、预前知识:
1、一个节点的前驱或者后继指向的都是节点的地址,这个地址包含当前节点的数据域和指针域。
2、对于节点p,有p->prior->next=p=p->next->prior
3、单链表的插入只需要修改两个指针,而双链表需要修改四次指针,即p的前驱、后继,s的前驱、后继。
4、双链表删除节点时比单链表方便很多。因为单链表删除时需要进行一次循环来查找它上一个节点的地址,而双链表因为有前驱,所以可以直接查找到它的上一个地址
5、虽然使用单链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定结点的前驱结点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。
6、插入元素前,首元节点的前驱和后继都指向NULL,插入后首元节点的前驱指向NULL,尾节点的后继指向NULL。
插入前:
插入后:
2、实现步骤:
(1)双向链表之创建
DLNode* DList_Create(DLNode *head,int a[],int n){
//链表初始化
head = (DLNode*)malloc(sizeof(DLNode));//为头节点开辟一块地址,头节点是不存放数据的,只起到标杆的作用
head->prior=NULL;
head->next=NULL;
DLNode *p;//创建临时头节点p,
p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
DLNode *s;//创建新增节点
for(int i=0;i<n;i++){
s = (DLNode*)malloc(sizeof(DLNode));
s->next = s->prior = NULL;
s->data = a[i];
p->next = s;
s->prior = p;
p = p->next; //这步是核心,将原来指向head地址的p变为指向s的地址
}
return head;
}
(2)双向链表之插入
例如我们现在想要插入一个新节点s到a、b之间,首先需要定位到b的位置,假设我们现在已经定位到了b,并且b的位置我们用节点p来表示。
第一步,将s的前驱指向a的地址,即将地址a赋值给s的前驱:因为插入后s的前驱是指向a的地址,又因为a的地址为b的前驱,即 p->prior,所以有:
s->prior = p->prior
第二步,将a的后继指向s的地址,即将s的地址赋值给a的后继:因为插入后a的后继是指向s的地址,又因为a的后继为p的前驱的后继(可能有点绕),即p->prior->next,所以有:
p->prior->next = s
第三步,类似的,将s的后继指向b,即将b的地址赋值给s的后继:因为b的地址(节点)为p,所以有:
s->next = p
第四步,最后将b的前驱指向地址s,即将s的地址赋值给b的前驱,因为b的前驱为 p->prior,所以有:
p->prior = s
实现代码:
DLNode* DList_insert(DLNode *head,int i,int num){ //i为插入的位置,num为插入的元素
DLNode *p;//创建临时头节点p,
DLNode *s;//创建新增节点
int j=0;
p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
s = (DLNode*)malloc(sizeof(DLNode));
while(j<i && p){
p = p->next;
j++;
}
if(j==i&&i!=0)
{
s->data = num;
s->prior = p->prior; //s的前驱指向p的上一个节点的地址
p->prior->next = s;//p的上一个节点的后继指向地址s
s->next = p; //s的后继指向地址p
p->prior = s;//p的前驱指向地址s
//完成一次节点的插入过程
}
return head;
}
(3)双向链表之删除:
如果前面插入的过程看明白了删除应该不用说也就懂了,过程就是将a的后继指向地址c,c的前驱指向地址a,最后释放掉b的内存。
p-prior->next = p->next //a的后继指向c的地址,即将c的地址赋值给a的后继
p->next->prior = p->prior //c的前驱指向a的地址,即将a的地址赋值给c的前驱
free(p) //释放掉p的内存
DLNode* DList_Delete(DLNode *head,int i) //删除的位置
{
DLNode *p;
p = head;
int j=0;
while(j<i && p != NULL){
p = p->next;
j++;
}
if(i==j && p != NULL){
p->prior->next = p->next;//将p的前一个节点的后继指向p的下一个节点的地址
p->next->prior = p->prior;//将p的下一个节点的前驱指向p的前一个节点的地址
free(p);//释放p的内存空间
}
return head;
}
4、完整实现代码:
#include<stdio.h>
#include <stdlib.h>
typedef struct Node
{
struct Node *prior;//前驱
int data;//数据域
struct Node *next;//后继
/* data */
}DLNode,*DLinkList; //DLNode翻译成双链表节点
DLNode* DList_Create(DLNode *head,int a[],int n){
//链表初始化
head = (DLNode*)malloc(sizeof(DLNode));//为头节点开辟一块地址,头节点是不存放数据的,只起到标杆的作用
head->prior=NULL;
head->next=NULL;
DLNode *p;//创建临时头节点p,
p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
DLNode *s;//创建新增节点
for(int i=0;i<n;i++){
s = (DLNode*)malloc(sizeof(DLNode));
s->data = a[i];
p->next = s;
s->prior = p;
p = p->next;
}
return head;
}
DLNode* DList_insert(DLNode *head,int i,int num){ //i为插入的位置,num为插入的元素
DLNode *p;//创建临时头节点p,
DLNode *s;//创建新增节点
int j=0;
p = head; //将head地址给临时头节点地址,原因是因为下面的这条语句 p = p->next,每新增一个节点,p的地址就要向后移动一位,等插完之后p的地址是链表的末尾地址,所以需要返回的是head,即链表的首地址;
s = (DLNode*)malloc(sizeof(DLNode));
while(j<i && p){
p = p->next;
j++;
}
if(j==i&&i!=0)
{
s->data = num;
s->prior = p->prior; //s的前驱指向p的上一个节点的地址
p->prior->next = s;//p的上一个节点的后继指向地址s
s->next = p; //s的后继指向地址p
p->prior = s;//p的前驱指向地址s
//完成一次节点的插入过程
}
return head;
}
DLNode* DList_Delete(DLNode *head,int i) //删除的位置
{
DLNode *p;
p = head;
int j=0;
while(j<i && p != NULL){
p = p->next;
j++;
}
if(i==j && p != NULL){
p->prior->next = p->next;//将p的前一个节点的后继指向p的下一个节点的地址
p->next->prior = p->prior;//将p的下一个节点的前驱指向p的前一个节点的地址
free(p);//释放p的内存空间
}
return head;
}
int main(){
int arr[] = {1,2,3,4,5};
int n = 5;
DLNode *L = NULL;
L = DList_Create(L,arr,n);
L = DList_insert(L,3,50); //例如将50插入到链表的第三个位置
L = DList_Delete(L,4);//删除第四个位置的节点
while(L->next!=NULL){
printf(" %d ",L->next->data);//算上头节点一共六个节点,并且头节点数据域为NULL,所以输出的第一个值应该为 L->next->data
L = L->next;
}
}
有一处BUG,不能删除尾节点,希望有人可以帮忙解决这个问题~~~