双链表的插入我认为有点难理解,特别是那些指针,p->next , p->next->prior ,以及 s->next 这些
双链表有前驱结点,后驱结点
理解了p->next , p->next->prior ,以及 s->next 这些,那对双链表的插入就好理解了
一、双链表的插入
双链表插入的另一种示意图
二、双链表的删除
有了以上的了解,那它的删除也好理解了
结点删除的另一示意图(都差不多的,方便理解)
双链表的结点指针的理解示意图
这里是节选的我看完课程后自己再写一次的双链表里面的删除结点的一段代码
int delete_node(dlnode *pHeader,int data) { dlnode *p = pHeader; if(NULL == p) { return -1; } while(NULL != p->pNext) { p = p->pNext; if(p->data == data) { if (NULL == p->pNext) { // 尾节点 // p表示当前节点地址,p->pNext表示后一个节点地址,p->pPrev表示前一个节点的地址 p->pPrev->pNext = NULL; //p->pPrev = NULL; 可以省略,因为后面整个都被销毁了 // 销毁p节点 //free(p); } else { p->pPrev->pNext = p->pNext ;//把被删除的结点的前结点的pNext与后结点连起来 p->pNext->pPrev = p->pPrev; //把被删除的结点的后结点的pPrev与前结点连起来 //p->pPrev = NULL; //p->pNext = NULL; } free(p); return 0; } } printf("未找到该结点\n"); return -1; }
目前这段代码运行是正常的,已经找过课程代码对比修改过了
说说问题所在吧
1.我之前想的时候没有注意把这个函数写成一个有返回值的函数,写成了void类型,当然,那时候我也没有写return。但是想起课程里面说如果传进来的头结点是一个NULL的话那就会报错了,因为NULL的pNext是没有分配到的。所以在前面加上一个if来判断是否是NULL。
这里if判断之后可以不加return的,但是如果要判定正确0错误-1这样的话最好就加上return 0 return -1,网上查过0代表正确-1代表有误(大概这样吧)如果有返回值的话函数的返回值类型就得写成相应的类型,不能是void了,这里的话就是int类型。2.可能是没有连贯着写这段代码的原因吧,我没注意到我最后main函数里面的是前插,比如说前插顺序是33 2 1,向后遍历出来的就是1 2 33了,所以当我删除的33的时候我还以为是删除的第一个结点,运行的时候出现了段错误,其实并不是这样,我删除的33是最后一个结点,即尾结点,这时候我的while循环里面是没有判断是否为尾结点这一个判断条件的,因为不加上的话,尾结点的pNext就是NULL了,不存在后面的->pPrev了,所以编译虽然没错,但是运行起来就会出现段错误。后面我从课程代码里面吧这个if判断尾结点的拉过来了,然后就没出现段错误了。
总结一下:
大体的思路还算是清晰的,起码删掉结点之后要怎么连接前后结点这些还是没问题的,但是前面的判断头结点是否为NULL,或者尾结点的判断,这些尽管在看课程的时候是懂的,但是过后自己再写一次的时候就没有注意到,可能当时的印象不深刻吧。通过这次自己再写一次之后印象深刻了很多,对很多的基础知识也得到了运用。继续加油!
算法的思想就是:把P的前驱结点接上P的后继节点。然后P的后继节点的前驱节点指向P的前驱节点。这个时候P就被架空了。此时释放P.
代码:
void DDeleteNode(DListNode *p){ //假设*P非最后的尾结点 p->prior->next = p->next;//P的前置节点的后继节点指向P的后继节点 p->next->prior = p->prior;//P的后置节点的前驱节点指向P的前驱节点 free(p);//释放P }
双链表的插入我认为有点难理解,特别是那些指针,p->next , p->next->prior ,以及 s->next 这些
双链表有前驱结点,后驱结点
理解了p->next , p->next->prior ,以及 s->next 这些,那对双链表的插入就好理解了
一、双链表的插入
双链表插入的另一种示意图
二、双链表的删除
有了以上的了解,那它的删除也好理解了
结点删除的另一示意图(都差不多的,方便理解)
双链表的结点指针的理解示意图
转载于:https://www.cnblogs.com/yyp-20190107/p/11029111.html
数据结构:双链表的创建,在P结点前后插入结点,删除P前后的结点以及P结点
提到链表的首元结点,我们首先想到的是head标志的结点,但其实head结点只是人为规定的,首元结点既可以定义为head也可以定义为end,尾元结点也是如此,即首元结点和尾元结点可以任意定义。
有了以上的思想我们将结点看成火车的一节车厢,结点中的信息看成车厢中的货物,首元结点看作车头来看待链表。而双链表则可以看成有两个车头的火车,即首元结点和尾元结点都是车头,这样一来火车既可以正着开也可以逆着开,所以也就没有纯粹意义上的首元结点和尾元结点了,即首元结点和尾元结点等价。
所以在双链表中要实现形如下图的两种关系:
定义结构体
与单链表相比应该多增加一个指针
typedef struct node { int date; struct node * pr; struct node * next; }Node,*LinkList;
链表初始化
初始化时,为了表示方便,以head来表示首元结点,用end来表示尾元结点,同时这两个结点不用来储存信息,由于将head看作第一个结点,end看作最后的结点,所以head->pr=NULL,end->next=NULL
void InitList(LinkList head,LinkList end) { head->next=end; end->next=NULL; end->pr=head; head->pr=NULL; }
链表的创建
void Creat(LinkList end,int n) { int i; LinkList node; for(i=1;i<=n;i++) { node=(LinkList)malloc(sizeof(Node)); node->date=i; /*这里采用的是尾插法,即插在数据的尾部,但要注意,尾元结点必须在链表尾部,其实质是在尾元结点前插入新结点*/ node->next=end; end->pr->next=node; node->pr=end->pr; end->pr=node; } }
从链表中取得信息
这里仅以head结点为基准取得信息
LinkList GetElem(LinkList head,int n) { LinkList h=head->next; int i; for(i=1;i<n;i++) h=h->next; return h; }
将S结点的信息插在P结点前
void InsertBefore(LinkList P,LinkList S) {/*将S结点的信息插在P结点前*/ S->pr=P->pr; P->pr->next=S; S->next=P; P->pr=S; }
将S结点的信息插在P结点后
void InsertAfter(LinkList P,LinkList S) {/*将S结点的信息插在P结点后*/ P->next->pr=S; S->pr=P; S->next=P->next; P->next=S; }
删除P结点前的结点
void DeletBefore(LinkList P) { LinkList Q; Q=P->pr; P->pr=Q->pr; Q->pr->next=P; free(Q); }
删除P结点后的结点
void DeletAfter(LinkList P) { LinkList Q; Q=P->next; P->next=Q->next; Q->next->pr=P; free(Q); }
删除P结点
void DeletItself(LinkList P) { LinkList Q,R; Q=P->pr; R=P->next; Q->next=R; R->pr=Q; free(P); }
正序输出
void Show(LinkList head) { LinkList h=head->next; while(h->next!=NULL) { printf("%d ",h->date); h=h->next; } printf("\n"); }
逆序输出
void ReShow(LinkList end) { LinkList e=end->pr; while(e->pr!=NULL) { printf("%d ",e->date); e=e->pr; } printf("\n"); }
在主函数中验证是否正确
int main() { LinkList head,end; head=(LinkList)malloc(sizeof(Node)); end=(LinkList)malloc(sizeof(Node));//特别注意,申请空间要放到InitList函数前,如果将空间的申请放到InitList函数里执行就会出错 InitList(head,end);//链表初始化 Creat(end,5);//创建五个结点,并将其连在结点上 puts("创建完成后,顺序输出:"); Show(head);//正序输出 puts("创建完成后,逆序输出:"); ReShow(end);//逆序输出 LinkList P,S; P=GetElem(head,2); S=(LinkList)malloc(sizeof(Node)); S->date=10;//为了表示方便,在这里将S中的信息设为10 printf("P结点储存的信息为:%d\n",P->date); printf("S结点储存的信息为:%d\n",S->date); InsertBefore(P,S); puts("在P之前插入S(10),顺序输出:"); Show(head);//正序输出 puts("在P之前插入S(10),逆序输出:"); ReShow(end);//逆序输出 P=GetElem(head,2);//此时P=10 S=(LinkList)malloc(sizeof(Node)); S->date=11;//为了表示方便,在这里将S中的信息设为11 printf("P结点储存的信息为:%d\n",P->date); printf("S结点储存的信息为:%d\n",S->date); InsertAfter(P,S); puts("在P后插入S(11),顺序输出:"); Show(head);//正序输出 puts("在P后插入S(11),逆序输出:"); ReShow(end);//逆序输出 printf("P结点储存的信息为:%d\n",P->date); DeletBefore(P); puts("删除P之前结点顺序输出:"); Show(head); puts("删除P之前结点逆序输出:"); ReShow(end); P=GetElem(head,2);//此时P=11 printf("P结点储存的信息为:%d\n",P->date); DeletAfter(P); puts("删除P之后的结点顺序输出:"); Show(head); puts("删除P之后的结点逆序输出:"); ReShow(end); P=GetElem(head,2);//此时P=11 printf("P结点储存的信息为:%d\n",P->date); DeletItself(P); puts("删除P结点顺序输出:"); Show(head); puts("删除P结点逆序输出:"); ReShow(end); }
自然,程序需要有头文件#include<malloc.h>
运行结果如下: