第11课-双向链表

单链表的局限
 单链表的结点都只有一个指向下一个结点的指针
 单链表的数据元素无法直接访问其前驱元素

单链表的改进

双向链表的定义
在单链表的结点中增加一个指向其前驱的pre指针

双向链表拥有单链表的所有操作
 创建链表
 销毁链表
 获取链表长度
 清空链表
 获取第pos个元素操作
 插入元素到位置pos
 删除位置pos处的元素

NOTE:

  教科书上大多是上面这样的四步操作,但是,由于有前面单链表的经验,我们应该把上图的1和2换一下顺序,3和4换一下顺序。当然不换也可以的,因为我们引入了两个辅助指针变量,但是,建议按照之前单链表的思想来链接节点,这是比较科学的做法,算是不成为的规定吧。

双向链表的新操作
 获取当前游标指向的数据元素
 将游标重置指向链表中的第一个数据元素
 将游标移动指向到链表中的下一个数据元素
 将游标移动指向到链表中的上一个数据元素
 直接指定删除链表中的某个数据元素

 DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node);
DLinkListNode* DLinkList_Reset(DLinkList* list);
DLinkListNode* DLinkList_Current(DLinkList* list);
DLinkListNode* DLinkList_Next(DLinkList* list);
DLinkListNode* DLinkList_Pre(DLinkList* list);

代码练兵场:

必知类型声明:

typedef struct _tag_DLinkList
{
    DLinkListNode header;
    DLinkListNode* slider;
    int length;
} TDLinkList;
typedef void DLinkList;
typedef struct _tag_DLinkListNode
{
   struct _tag_DLinkListNode* next;
   struct _tag_DLinkListNode* pre;
}DLinkListNode;

链表的创建,和单链表、循环链表差不多:

DLinkList* DLinkList_Create() 
{
    TDLinkList* ret = (TDLinkList*)malloc(sizeof(TDLinkList));
    
    if( ret != NULL )
    {
        ret->length = 0;
        ret->header.next = NULL;
        ret->header.pre = NULL;
        ret->slider = NULL;
    }
    
    return ret;
}

指针域先给NULL,长度置零。

链表的销毁,清除,获得长度:

void DLinkList_Destroy(DLinkList* list) 
{
    if(list!=NULL)
    {
        free(list);
        list=NULL;
    }
}

void DLinkList_Clear(DLinkList* list) 
{
    TDLinkList* sList = (TDLinkList*)list;
    
    if( sList != NULL )
    {
        sList->length = 0;
        sList->header.next = NULL;
        sList->header.pre = NULL;
        sList->slider = NULL;
    }
}

int DLinkList_Length(DLinkList* list) 
{
    TDLinkList* sList = (TDLinkList*)list;
    int ret = -1;
    
    if( sList != NULL )
    {
        ret = sList->length;
    }
    
    return ret;
}

和之前的单链表、循环链表几乎一致,也比较简单,就不在赘述了。

链表的插入:

int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos) 
{ 
    TDLinkList* sList = (TDLinkList*)list;
    int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
    int i = 0;
    
    if( ret )
    {
        DLinkListNode* current = (DLinkListNode*)sList;
        DLinkListNode* next = NULL;
        //current->next != NULL保证了插入的pos大于链表此刻最大长度current指针的移动也不会出错
        for(i=0; (i<pos) && (current->next != NULL); i++)
        {
            current = current->next;
        }
        
        next = current->next;//赋值辅助变量next,使其指向current->next

        
        node->next = next;
        current->next = node;

        node->pre = current;
        if( next != NULL )//next如果为NULL,证明插入到了末尾,需特殊考虑,末尾时刻next为NULL,故不应该执行下面语句当然,第一次插入的时候,既是末尾也是开头
        {
            next->pre = node;
        }
        
        
        if( sList->length == 0 )//第一次插入时,node的前继应该置成NULL(覆盖之前的值),游标指向第一个节点
        {
            node->pre = NULL;
            sList->slider = node;
        }
        
        sList->length++;
    }

获取链表的pos位置(和之前链表的并无不同,不再赘述):

DLinkListNode* DLinkList_Get(DLinkList* list, int pos) 
{
    TDLinkList* sList = (TDLinkList*)list;
    DLinkListNode* ret = NULL;
    int i = 0;
    
    if( (sList != NULL) && (0 <= pos) && (pos < sList->length) )
    {
        DLinkListNode* current = (DLinkListNode*)sList;
        
        for(i=0; i<pos; i++)
        {
            current = current->next;
        }
        
        ret = current->next;
    }
    
    return ret;
}

删除操作:

 

DLinkListNode* DLinkList_Delete(DLinkList* list, int pos) // O(n)
{
    TDLinkList* sList = (TDLinkList*)list;
    DLinkListNode* ret = NULL;
    int i = 0;
    
    if( (sList != NULL) && (0 <= pos) && (pos < sList->length) )
    {
        DLinkListNode* current = (DLinkListNode*)sList;
        DLinkListNode* next = NULL;
        
        for(i=0; i<pos; i++)
        {
            current = current->next;
        }
        
        ret = current->next;//ret指向要删除的节点
        next = ret->next;//赋值next,使其指向删除节点的下一个节点
        
        current->next = next;//链接新节点(后继)
        
        if( next != NULL )//删除不是最后一个时的情况
        {
            next->pre = current;//链接新节点(前继)
          if(current==(DLinkListNode*)sList)//删除第一个,特殊考虑
            {
                next->pre = NULL;//前继置空
            }
          ret->pre = NULL;//删除之后,断开ret和前后的联系
          ret->next = NULL;
        }
        else //如果删除的是最后一个
        {
            ret->pre = NULL;//前继置空,如果不这样,删除的最后一个时,next是NULL,不能对NULL指针操作前继使其指向current
        }
        
        if( sList->slider == ret )//如果删除的是游标,游标后移
        {
            sList->slider = next;
        }
        
        sList->length--;//长度减一
    }
    
    return ret;
}

DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node)
{
    TDLinkList* sList = (TDLinkList*)list;
    DLinkListNode* ret = NULL;
    int i = 0;
    
    if( sList != NULL )
    {
        DLinkListNode* current = (DLinkListNode*)sList;
        
        for(i=0; i<sList->length; i++)
        {
            if( current->next == node )
            {
                ret = current->next;
                break;
            }
            
            current = current->next;
        }
        
        if( ret != NULL )
        {
            DLinkList_Delete(sList, i);
        }
    }
    
    return ret;
}

 

删除操作做了一点改动,考虑到参考程序没有把删除节点的前继和后继链接断开,自己实现了这一步。

 

标操作和循环链表几乎一致(不再赘述):

 

DLinkListNode* DLinkList_Reset(DLinkList* list)
{
    TDLinkList* sList = (TDLinkList*)list;
    DLinkListNode* ret = NULL;
    
    if( sList != NULL )
    {
        sList->slider = sList->header.next;
        ret = sList->slider;
    }
    
    return ret;
}

DLinkListNode* DLinkList_Current(DLinkList* list)
{
    TDLinkList* sList = (TDLinkList*)list;
    DLinkListNode* ret = NULL;
    
    if( sList != NULL )
    {
        ret = sList->slider;
    }
    
    return ret;
}

DLinkListNode* DLinkList_Next(DLinkList* list)
{
    TDLinkList* sList = (TDLinkList*)list;
    DLinkListNode* ret = NULL;
    
    if( (sList != NULL) && (sList->slider != NULL) )
    {
        ret = sList->slider;
        sList->slider = ret->next;
    }
    
    return ret;
}

DLinkListNode* DLinkList_Pre(DLinkList* list)
{
    TDLinkList* sList = (TDLinkList*)list;
    DLinkListNode* ret = NULL;
    
    if( (sList != NULL) && (sList->slider != NULL) )
    {
        ret = sList->slider;
        sList->slider = ret->pre;
    }
    
    return ret;
}

 main.c:

#include <stdio.h>
#include <stdlib.h>
#include "DLinkList.h"
struct Value
{
    DLinkListNode header;
    int v;
};

int main(int argc, char *argv[])
{
    int i = 0;
    DLinkList* list = DLinkList_Create();
    struct Value* pv = NULL;
    struct Value v1;
    struct Value v2;
    struct Value v3;
    struct Value v4;
    struct Value v5;
    
    v1.v = 1;
    v2.v = 2;
    v3.v = 3;
    v4.v = 4;
    v5.v = 5;
    
    DLinkList_Insert(list, (DLinkListNode*)&v1, DLinkList_Length(list));
    DLinkList_Insert(list, (DLinkListNode*)&v2, DLinkList_Length(list));
    DLinkList_Insert(list, (DLinkListNode*)&v3, DLinkList_Length(list));
    DLinkList_Insert(list, (DLinkListNode*)&v4, DLinkList_Length(list));
    DLinkList_Insert(list, (DLinkListNode*)&v5, DLinkList_Length(list));
    
    for(i=0; i<DLinkList_Length(list); i++)//遍历双向链表,打印1 2 3 4 5
    {
        pv = (struct Value*)DLinkList_Get(list, i);
        
        printf("%d\n", pv->v);
    }
    
    printf("\n");
    
    DLinkList_Delete(list, DLinkList_Length(list)-1);//删除最后一个节点,数据5
    DLinkList_Delete(list, 0);//删除第一个节点,数据1
    
    for(i=0; i<DLinkList_Length(list); i++)//打印删除之后剩余的元素,打印2 3 4
    {
        pv = (struct Value*)DLinkList_Next(list);
        
        printf("%d\n", pv->v);
    }
    
    printf("\n");
    
    DLinkList_Reset(list);//游标复位
    DLinkList_Next(list);//游标指向下一个,指向3
    
    pv = (struct Value*)DLinkList_Current(list);//获取现在游标指向
    
    printf("%d\n", pv->v);//打印 3
    
    DLinkList_DeleteNode(list, (DLinkListNode*)pv);//删除 3
    
    pv = (struct Value*)DLinkList_Current(list);//获取游标现在的指向,指向4
    
    printf("%d\n", pv->v);//打印4
    
    DLinkList_Pre(list);//游标前移,指向2
    
    pv = (struct Value*)DLinkList_Current(list);//获取现在游标的指向
    
    printf("%d\n", pv->v);//打印 2
    
    printf("Length: %d\n", DLinkList_Length(list));//输出链表长度
    
    DLinkList_Destroy(list);//销毁链表
    
    return 0;
}

小结
 双向链表在单链表的基础上增加了指向前驱的指针
 功能上双向链表可以完全取代单链表的使用
 循环链表的NextPreCurrent操作可以高效的遍历链表中的所有元素 

 

posted @ 2017-07-31 17:22  Crystal_Guang  阅读(375)  评论(0编辑  收藏  举报