链表算法五之双向链表

来源:互联网 发布:短信轰炸机易安卓源码 编辑:程序博客网 时间:2024/06/05 20:45

     前面都是介绍单链表,本节介绍双向链表的一些基本操作。我们知道在单链表中只有指向后继结点的指针,即使是循环链表,也需要几乎循环一圈之后才知道自己的前驱结点,在双向链表中的结点有两个指针域,一个指向后继结点,一个指向前驱结点,数据结构如下:

struct biLinkList{    struct biLinkList *prior,*next;    int data;     }; 
   前面在链表算法一之单链表基本操作中用头插法和尾插法两种方法实现了创建单链表,本节也是这样。但是其实插入的操作都一样,下面的图片来源于《大话数据结构》,如图


   头插法的思想在链表算法一种介绍了,这里不重复介绍了,代码如下:

struct biLinkList *headInsert(){    struct biLinkList *head=NULL,*p=NULL;    int val;    while(scanf("%d",&val)){        if(val==-1) break;        p=(struct biLinkList *)malloc(sizeof(struct biLinkList));        p->data=val;        p->prior=NULL;        p->next=NULL;        if(head==NULL){           head=(struct biLinkList *)malloc(sizeof(struct biLinkList));           p->prior=head;           head->next=p;        }        else{           p->prior=head;           p->next=head->next;           head->next->prior=p;           head->next=p;        }    }    return head;       }

这段程序的关键也就是一个if和一个else,首先是if

p->prior=head;   ①
head->next=p;   ②

为什么这样写?

   我们知道,对于双向链表插入一个结点通常会涉及到图片上的四个操作,为什么这里只用两句就搞定了,因为head->prior不用写,而p->next在初始化结点的时候就赋值为NULL了,所以只需要两步,一步是当前malloc的结点p的前驱是head,head的后继是p就行了,剩下的问题就是②中每条语句会不会写反?因为如果p->prior和head->next作为左值的话,p->prior是为NULL和head->next是未知的,所以只能都是右值了。

再就是else

p->prior=head;  ①
p->next=head->next;  ②
head->next->prior=p;  ③
head->next=p;   ④

   我感觉顺序需要记住,就是④的顺序,就是一个结点首先让它的前驱指针指向前面一个结点head,然后让它的后继指针指向head的后一个结点head->next,接着让head->next的结点前驱指向p,最后head的后继指向p,其实我感觉④的操作和单链表的插入类似,单链表的插入不就是,先让这个结点挂这个链表上(p->next=head->next),然后才是插入到链表中(head->next=p),我们看图片的时候感觉③好像是在操作后面一个结点,但是仔细想一想不就是模仿单链表插入的时候,让p结点挂在链表上,然后④衔接上。

   ①②的代码和if中的一样,主要是④,我记得我写的时候就把③给写反了,写成了p=head->next->prior,这样写会有什么问题,head->next->prior作为右值,不就是等于head嘛,p就被赋值给head了,这样就明显错了,但是如果head->next->prior作为左值,不就修改了head->next结点的前驱指针的指向了,这样才是对的;④这个操作和单链表的插入一样。


  下面是尾插法的代码,如下:

struct biLinkList *tailInsert(){    struct biLinkList *head=NULL,*p=NULL,*q=NULL;    int val;    while(scanf("%d",&val)){        if(val==-1) break;        p=(struct biLinkList *)malloc(sizeof(struct biLinkList));        p->data=val;        p->prior=NULL;        p->next=NULL;        if(head==NULL){           head=(struct biLinkList *)malloc(sizeof(struct biLinkList));           p->prior=head;           head->next=p;           q=p;        }        else{           p->prior=q;           q->next=p;           q=p;        }    }    return head;}
  尾插法的代码比头插法简洁,如果头插法的代码看懂了的话,尾插法不在话下,自己思考吧。


当然还有删除操作,代码中只是练习删除的代码怎么写,所以就写了一个删除首元结点的代码,如下:

void *deleteFirst(struct biLinkList *head){if(head==NULL) return NULL;struct biLinkList *tmp=head->next;tmp->prior->next=tmp->next;tmp->next->prior=tmp->prior;}

   在实际的总的代码中,尾插法的代码增加了一个函数struct biLinkList *tailInsert2(struct biLinklist **q),这个函数这样写的原因在于:由于函数已经返回了头结点head,而尾插法的q总是指向链表的尾结点,我希望记住尾结点,打算从尾结点向前遍历,也就是用双向链表中的prior指针来遍历链表,代码如下:

#include <stdio.h>#include <stdlib.h>struct biLinkList{    struct biLinkList *prior,*next;    int data;     }; struct biLinkList *headInsert(){    struct biLinkList *head=NULL,*p=NULL;    int val;    while(scanf("%d",&val)){        if(val==-1) break;        p=(struct biLinkList *)malloc(sizeof(struct biLinkList));        p->data=val;        p->prior=NULL;        p->next=NULL;        if(head==NULL){           head=(struct biLinkList *)malloc(sizeof(struct biLinkList));           p->prior=head;           head->next=p;        }        else{           p->prior=head;           p->next=head->next;           head->next->prior=p;           head->next=p;        }    }    return head;       }struct biLinkList *tailInsert( ){    struct biLinkList *head=NULL,*p=NULL,*q=NULL;    int val;    while(scanf("%d",&val)){        if(val==-1) break;        p=(struct biLinkList *)malloc(sizeof(struct biLinkList));        p->data=val;        p->prior=NULL;        p->next=NULL;        if(head==NULL){           head=(struct biLinkList *)malloc(sizeof(struct biLinkList));           p->prior=head;           head->next=p;           q=p;        }        else{           p->prior=q;           q->next=p;           q=p;        }    }    return head;}struct biLinkList *tailInsert2(struct biLinkList **q){    struct biLinkList *head=NULL,*p=NULL;    int val;    while(scanf("%d",&val)){        if(val==-1) break;        p=(struct biLinkList *)malloc(sizeof(struct biLinkList));        p->data=val;        p->prior=NULL;        p->next=NULL;        if(head==NULL){           head=(struct biLinkList *)malloc(sizeof(struct biLinkList));           p->prior=head;           head->next=p;           *q=p;        }        else{           p->prior=*q;           (*q)->next=p;           *q=p;        }    }    return head;}void display(struct biLinkList *head){   if(head==NULL) return ;   struct biLinkList *tmp=head->next;   while(tmp){      printf("%d ",tmp->data);      tmp=tmp->next;   }   printf("\n");}void displayTail(struct biLinkList *head,struct biLinkList *tail){   if(head==NULL||tail==NULL) return ;   while(tail!=head){      printf("%d ",tail->data);      tail=tail->prior;   }   printf("\n");}void *deleteFirst(struct biLinkList *head){if(head==NULL) return NULL;struct biLinkList *tmp=head->next;tmp->prior->next=tmp->next;tmp->next->prior=tmp->prior;}int main(int argc, char *argv[]){  struct biLinkList *head=headInsert();  display(head);  deleteFirst(head);  display(head);     head=tailInsert();   display(head);  struct biLinkList *tail=NULL;  head=tailInsert2(&tail);  displayTail(head,tail);  system("PAUSE");  return 0;}

   当然对于这个怎么记住尾结点,先开始我也不会,后来问了高手才知道的,其实问题就是

   比如说函数f(int *val),主函数中调用f函数时,f(&val),这样当f函数中的val值改变了,主函数的val值也跟着改变了(链表算法一之单链表基本操作中的删除第i个元素和查找第i个元素,打印这个元素的值就是这样实现的),但是如果f(struct biLinkList *q)本来就是个指针,你该怎么传?

   高手的回答是:对指针取地址,行参为二级,就可以改变指针的地址值了


未完待续: 下一节   

链表算法六之多项式相加


如果文章有什么错误或者有什么建议,欢迎提出,大家共同交流,一起进步

文章转载请注明出处,请尊重知识产权

0 0
原创粉丝点击