数据结构——双链表和循环链表

双链表的运算

先定定义一下双链表的数据类型

typedef struct Dnode{
    int data;
    struct Dnode *prior;//指向前驱节点
    struct Dnode *next;//指向后驱节点
} DLinkList;

插入和删除节点

  1. 插入节点

    DLinkList *p //这是指向原来在链表中的
    DLinkList s //这是需要被插入的节点
    s->next = p->next;
    p->next->prior=s;
    s->prior = p;
    p->next = s;
    //这里和单链表略有不同
    
  2. 删除节点

    DLinkList *p// 指向链表 假设需要被删的元素是指 的后驱元素
    DLinkList *q//这个就是要删的节点;
    p->next = p->next->next;
    q->next->prior = p;
    

建立单链表:

  1. 头插法建表
    特点:新节点插入到当前链表的表头上,最后得到的链表是一个与插入顺序相反的链表

    void CreateHeadList(DLinkList *&L,int a[],int n){
        DLinkList *s;//指向LinkList类型数据的指针
        int i;
        L = (DLinkList*)malloc(sizeof(DLinkList));
        L->next = L->prior  = NULL;//创建头节点,他的前驱和后驱节点都是UNLL
        
        for(i = 0;i<n;i++){
            s = (DLinkList*)malloc(sizeof(DLinkList));//分配地址后,就是指针型变量;
            s->data =a[i];
            s->next = L->next;
            if(L->next!=NULL)
                L->next->prior = s
      		//当第一个插入的时候,NULL不存在指向前驱节点的问题。
            //而且这一步一定要写在这个位置,不能和下面的步骤交换
            L->next = s;
            s->prior = L;
        }
    }
    
  2. 尾插法建表:
    特点:生成的链表中节点的 次序和原数组元素的顺序一样。

    void CreateEndList(DLinkList *&L,int a[],int n){
        DLinkList *s,*r;
        int i;
        L = (DLinkList*)malloc(sizeof(DLinkList));//创建头节点
         L->next = L->prior  = NULL;
         //创建头节点,他的前驱和后驱节点都是UNLL,
         //其实L的next是不是NULL无所谓。后面还会变
        r = L//r始终指向尾节点,开始的时候,指的是头节点
        for(i = 0;i<n;i++){
            s = (DLinkList*)malloc(sizeof(DLinkList));
            s->data = a[i];
            r->next = s;
            s->prior = r;
            r=s;
        }
        r->next = UNLL;//尾节点的next为UNLL
    }
    

线性表基本运算在双链表上的实现

  1. 初始化线性表 InitList(L)
  2. 销毁线性表 DestroyList(L)
  3. 判线性表是否为空表 ListEmpty(L)
  4. 求线性表的长度 ListLength(L)
  5. 输出线性表 DispList(L)
  6. 求线性表 L 中指定位置的某个数据元素 GetElem(L, I , e)
  7. 按元素值查找 LocateElem(L,e)

其实对于双向链表,带来的便利就是可以从后继节点来访问前驱节点,进而可以有两个指针从两边来扫(遍历)这个链表。所以上面几个操作和单链表(如果不知道单链表怎么操作请看单链表操作)没什么区别,主要就是删除,和插入的时候,因为要维护前驱节点,所以有一些差别。

  1. 插入数据元素 ListInsert(L,i,e)

    bool DListInsert(*&L,int i,int e){
        DLinkList *p=L,*s;
     int j=0;
        while(j<i-1&&p!=NULL){//因为单链表只能由前去元素道后驱元素,所以如果想要在 第3个位置插要先找到第2个
            j++;
            p=p->next;
        }
        if(p==NULL)
            return false;        
        else
        {
         	s=(LinkList *)malloc(sizeof(LinkList));
            s->next = p->next;
            if(p->next!= NULL)
                p->next->Prior = s;
            p->next = s;
            s->Prior = p;
            return true;
            
        }
    }
    
  2. )删除数据元素 DListDelete(L,i,e)

    bool DListDelete(DLinkList *&L,int i;&e){
        DLinkList *p=L,*s;
        int j = 0;
        while(j<i-1&&p!=NULL){
            j++;
            p = p->next
        }
        if(p==NULL)
            return false;//没有找到第i-1个节点
        else{
            s = p->next;//s是要被删除的,即第i个节点
            if(s==NULL)
                return false;//没有找到第i个节点
            e = s->data;
            p->next =s->next;
            if(p->next!=NULL)//这里p的next已经改变,
                p->next->prior = p;
            free(s)
            return true;      
            
            }        	
        }
    }
    


循环链表

循环链表:是另一种形式的链式存储结构。它的特点 是表中最后一个节点的指针域不再是空,而是指向表头节点 ,整个链表形成一个环。由此从表中任一节点出发均可找到 链表中其他节点。

在单链表中,只要加上让尾节点指向头节点的语句就可以很轻松的做到循环单链表。

同样的在双链表中,加上让尾节点的next指向头节点,头节点的prior指向为尾节点,就可建立循环双链表

与非循环链表不同的是,判断已经扫(遍历)完整个链表的方法不再是p->next!=NULL; 而是p->next!=L

下面我们看一个小例子:

编写出判断带头节点的双向 循环链表L是否对称相等的算法

算法:p 从左向右扫描 L , q 从右向左扫描 L ,若对应 数据节点的 data 域不相等,则退出循环,否则继续比较, 直到p与q相等或p的下一个节点为*pq为止。

int Equeal(DLinkList *L)  //双向循环链表 
{  int same=1;
 DLinkList *p=L->next; //p指向第一个数据节点
 DLinkList *q=L->prior;     //q指向最后数据节点
 while (same==1)
     if (p->data!=q->data)
         same=0;
 	else         
 	{        

   		 if (p==q||p=q->prior)break;
    	 q= q->prior;//q前移
     	 p=p->next; //p后移
	 }   
 return same;
}

欢迎各位路过的大佬指正

发布了50 篇原创文章 · 获赞 50 · 访问量 2736

猜你喜欢

转载自blog.csdn.net/weixin_45691686/article/details/104761759
今日推荐