数据结构3.双端链表


引言

我们已经介绍了数据结构中的单向链表以及实现了一些接口,我们发现,链表中的一个节点通过一个next指针指向下一个节点,依次传递;如果我们想要找到一个特定的节点,就算我们增加了控制信息,也必定需要从头开始遍历,如果每个节点增加一个向前一个节点的指针,它的每个数据结点中就都有两个指针,分别指向直接后继和直接前驱。我们就会可以很方便地访问它的前驱结点和后继结点。

以此,为双端链表(双向链表)。

本文所有源程序可在double_list得到。


一、双端链表的定义

我们在Dlist_node的结构体中有两个Dlist_node指针的指针域,一个void* 指针指向的数据域。由于void *是可以指向任意数据类型的指针,从而我们实现了双端链表的通用结构。

//双端链表节点定义
typedef struct Dlist_node
{
    struct Dlist_node *prev;    //指向前一个节点
    struct Dlist_node *next;    //指向后一个节点
    void              *data;    //数据(指针)
}Dlist_node;
//通用链表控制信息
typedef struct Dlist
{
    struct Dlist_node *head;   //指向头结点
    struct Dlist_node *tail;
    int               count;

    //我们还定义了一些函数指针在控制信息里:

    //这是一个指向某个需要被释放的数据域的函数指针
    void (*free)(void *ptr);
    //比较节点数据域
    Boolean (*match)(void *value1, void *value2);
    //拷贝节点数据域
    void *(*copy_node)(void *value);
}Dlist;

从而我们得到的双端链表结构如图示:

双端链表结构


二、双端链表的接口定义:

我们仍旧定义了对双端链表的操作,如以下接口:

//1.双端链表的初始化
    Dlist *init_dlist(void);
//2.双端链表的销毁
    void destroy_dlist(Dlist **dlist);//二重指针
//3.头部插入
    Boolean push_front(Dlist *dlist, void *data);
//4.尾部插入
    Boolean push_back(Dlist *dlist, void *data);
//5.头部删除
    Boolean pop_front(Dlist *dlist);
//6.尾部删除
    Boolean pop_back(Dlist *dlist);
//7.插入到当前节点前
    Boolean insert_prev(Dlist *dlist, Dlist_node *node, void *value); 
//8.插入到当前节点后
    Boolean insert_next(Dlist *dlist, Dlist_node *node, void *value); 
//9.删除某个节点
    Boolean remove_dlist_node(Dlist *dlist, Dlist_node *node);
//10.显示双端链表信息
    void show_dlist(Dlist *dlist, Print_func print);
//11.得到第一个节点数据域
    Boolean  get_front(Dlist *dlist, void **value);
//12.得到最后一个节点数据域
    Boolean  get_tail(Dlist *dlist, void **value);
//13.得到链表节点数量
    int get_dlist_count(Dlist *dlist);

我们将这些信息连同定义的宏定义一齐放入dlist.h中,

#define TRUE      (1)
#define FALSE     (0)
#define ZERO      (0)
#define ONLY_ONE  (1)
//Boolean类型不是C语言的数据类型。
typedef unsigned char Boolean ;
//这是一个函数指针,用来输出void *的数据的值的函数指针。
typedef void (*Print_func)(void *value);
//实例:void *指向int,输出int类型的函数。
void print_int(void *data);

三、其他工具类函数定义

所谓工具类,即可以调用的多处可用的工具函数,如malloc的包裹函数,或者交换函数:

void *Malloc(size_t size);
void swap(void *a, void *b, int length);

实现:

//工具类接口

void *Malloc(size_t size)
{
    void *result = malloc(size);
    if(result == NULL)
    {
        fprintf(stderr, "the memory is full !\n");
        exit(1);
    }
    return result;
}

void swap(void *a, void *b, int length)
{
    void *temp = Malloc(length);

    memcpy(temp, a, length);
    memcpy(a, b, length);
    memcpy(b, temp, length);

    free(temp);
}

其他对双端链表的有用操作工具:

void print_int(void *data)
{
    printf("%d ",*(int *)data);
}

Dlist_node *create_node(void)
{
    Dlist_node *node = (Dlist_node *)Malloc(sizeof(Dlist_node));
    bzero(node, sizeof(Dlist_node));

    return node;
}

四、双端链表节点的接口实现

//1.双端链表的初始化
Dlist *init_dlist(void)
{
    Dlist *dlist = (Dlist *)Malloc(sizeof(Dlist));
    bzero(dlist, sizeof(Dlist));

    return dlist;
}
//2.双端链表的销毁
void destroy_dlist(Dlist **dlist)//二重指针
{
    if(dlist == NULL || *dlist == NULL)
    {
        return;
    }
/*
    while((*dlist)->head)        //(*)->
    { 
        pop_back(*dlist);
    }
*/
    Dlist_node *p_node = (*dlist)->head;
    while((*dlist)->head != NULL)
    {
        (*dlist)->head = p_node->next;
        if((*dlist)->free != NULL) //函数指针free
        {
            (*dlist)->free(p_node->data);
        }
        free(p_node);
        p_node = (*dlist)->head;
    }
    free(*dlist);
    dlist = NULL;
}
//3.头部插入
    //  case 1:
    //     node
    //       |
    //   head tail 
    //
    //  case 2:
    //    node
    //    //
    //   node1==node2==node3
    //      \         /
    //       head|tail
Boolean push_front(Dlist *dlist, void *data)
{
    if(dlist == NULL || data == NULL)
    {
        return FALSE;
    }

    Dlist_node *node = create_node();  //调用链表工具
    node->data = data ;                //创建一个新节点

    if(dlist->count == ZERO)
    {
        dlist->tail = node;
    }
    else
    {
        node->next = dlist->head;
        dlist->head->prev = node;
    }
    dlist->head = node;                //头插
    dlist->count ++;

    return TRUE;
}
//4.尾部插入
    //  case 1:
    //     node
    //       |
    //   head tail 
    //
    //  case 2:
    //                  node
    //                    \\
    //   node1==node2==node3
    //      \         /
    //       head|tail
Boolean push_back(Dlist *dlist, void *data)
{
    if(dlist == NULL || data == NULL)           /*  error:value == NULL  */
    {
        return FALSE;
    }
    Dlist_node *node = create_node();           
    node->data = data ;
    if(dlist->count == ZERO)
    {
        dlist->head = node;
    }
    else
    {
        node->prev = dlist->tail;
        dlist->tail->next = node;
    }
    dlist->tail = node;
    dlist->count ++;

    return TRUE;                               
}

//5.头部删除
    //  case 1:
    //     (node)
    //       |
    //   head tail 
    //
    //  case 2:
    //   (node1)==node2==node3==node4
    //           /              /
    //          head         tail
Boolean pop_front(Dlist *dlist)
{
    if(dlist == NULL || dlist->count == ZERO)
    {
        return FALSE;
    }

    Dlist_node *node = dlist->head;       //头节点

    if(dlist->count == ONLY_ONE)
    {
        dlist->head = dlist->tail = NULL;
    }
    else
    {
  /*   node->next->prev = NULL;
   *   dlist->head = node->next;  //
   */   
       dlist->head = node->next;
       dlist->head->prev = NULL;
    }
/*
    dlist->free(node);            //free函数释放掉之前所指头节点的位置
    node = NULL;
*/
    if(dlist->free != NULL)       //这里我们调用我们自己的free指针函数
    {
        dlist->free(node->data);
    }
    free(node);
    dlist->count-- ;

    return TRUE;
}

//6.尾部删除
    //  case 1:
    //     (node)
    //       |
    //   head tail 
    //
    //  case 2:
    //   node1==node2==node3==(node4)
    //       \            \
    //      head         tail
Boolean pop_back(Dlist *dlist)
{
    if(dlist == NULL || dlist->count == ZERO)
    {
        return FALSE;
    }

    Dlist_node *node = dlist->tail;      //node指向尾节点

    if(dlist->count == ONLY_ONE)
    {
        dlist->head = dlist->tail = NULL;
    }
    else
    {
        dlist->tail = node->prev;
        dlist->tail->next = NULL;        //释放尾节点与链表的连接
    }

    if(dlist->free != NULL)
    {
        dlist->free(node->data);
    }
    free(node);
    dlist->count -- ;

    return TRUE;
}
//7.插入到当前节点前
    //
    //         node
    //        //  \\
    //   node1      node2==node3
    //      \              /
    //       head        tail
Boolean insert_prev(Dlist *dlist, Dlist_node *node, void *value)
{
    if(dlist == NULL || node == NULL )
    {
        return FALSE;
    }

    Dlist_node *p_node = create_node();
    p_node->data = value;                   //生成节点p->node

    if(dlist->count == ONLY_ONE)
    {
        push_front(dlist, value);           //只有一个节点时,头插
        return TRUE;
    }
    else
    {
        //        p_node
        //        //  \\
        //   node1      node
        p_node->next = node;
        p_node->prev = node->prev;

        node->prev->next = p_node;
        node->prev = p_node;
        dlist->count++;
    }
    return TRUE;
}
//8.插入到当前节点后
    //
    //         node
    //        //  \\
    //   node1      node2==node3
    //      \              /
    //       head        tail
Boolean insert_next(Dlist *dlist, Dlist_node *node, void *value)
{
    if(dlist == NULL || node == NULL )
    {
        return FALSE;
    }

    Dlist_node *p_node = create_node();
    p_node->data = value;                   //新节点p_node

    if(dlist->count == ONLY_ONE)
    {
        push_back(dlist, value);
        return TRUE;
    }
    else
    {
        //        p_node
        //       //    \\
        //     node     node1
        p_node->next = node->next;    //先把p_node信息挂在链表上
        p_node->prev = node;

        node->next->prev = p_node;    //再改变链表上的结构信息
        node->next = p_node;
        dlist->count++;
    }
    return TRUE;
}
//9.删除某个节点
    //  case 1:
    //     (node)
    //       |
    //   head tail 
    //
    //  case 2:
    //   node1==node2==(node3)==node4
    //       \                  /
    //      head             tail
Boolean remove_dlist_node(Dlist *dlist, Dlist_node *node)
{
    if(dlist == NULL || node == NULL)
    {
        return FALSE;
    }
    if(node->next == NULL)
    {
        return pop_back(dlist);    //在尾部,则尾删
    }
    else
    {
        //                  node   p_node
        //   node1==node2==(node3)==node4==node5
        //       \                         /
        //      head                     tail
        Dlist_node *p_node = node->next;
        node->data = p_node->data;

        //用node的下一个节点代替node

        node->next = p_node->next;
        p_node->next->prev = node;

        if(dlist->free != NULL)
        {
            dlist->free(p_node->data);
        }
        free(p_node);
        dlist->count -- ;
    }
    return TRUE;
}
//10.显示双端链表信息
void show_dlist(Dlist *dlist, Print_func print)  //我们的参数是print,是一个函数指针;
{
    Dlist_node *p_node = NULL;

    if(dlist != NULL && dlist->count >0)
    {
        for(p_node = dlist->head; p_node; p_node = p_node->next)
        {
            print(p_node->data);
        }
        printf("\n");
    }
}

//11.得到第一个节点数据域
    //**value是一个二重指针,可以用来保存void *类型的数据指针域
Boolean  get_front(Dlist *dlist, void **value)
{
    if(dlist == NULL || dlist->count == ZERO)
    {
        return FALSE;
    }
    if(value != NULL)
    {     
       *value = dlist->head->data;
    }
    return TRUE;
}
//12.得到最后一个节点数据域
    //与11同理
Boolean  get_tail(Dlist *dlist, void **value)
{
    if(dlist == NULL || dlist->count == ZERO)
    {
        return FALSE;
    }
    if(value != NULL)
    {     
       *value = dlist->tail->data;
    }
    return TRUE;
}
//13.得到链表节点数量
     //链表控制信息中有关于节点个数的变量count
int get_dlist_count(Dlist *dlist)
{
    if(dlist == NULL)
    {
        return -1;
    }
    else
    {
        return dlist->count;
    }
}

五、程序功能验证

验证程序:

#include <stdio.h>
#include "dlist.h"
/*
void print_int(void *value);
void print_int(void *value)
{
    printf("%d ",*(int *)value);
 }
*/
int main(int argc, char **argv)
{
    int i = 0;
    int a[]={1,2,3,4,5};
    void *value;

    Dlist *dlist = NULL;
    dlist = init_dlist();
    printf("push_front(dlist, &a[i]);\n");
    for(i=0; i< sizeof(a)/sizeof(a[0]);++i)
    {
        push_front(dlist, &a[i]);
    }
    show_dlist(dlist, print_int);
    printf("pop_front(dlist);\n");
    pop_front(dlist);

    show_dlist(dlist, print_int);
    printf("push_back(dlist, &a[i]);\n");
    for(i=0; i< sizeof(a)/sizeof(a[0]);++i)
    {
        push_back(dlist, &a[i]);
    }
    show_dlist(dlist, print_int);
    printf("pop_back(dlist);\n");
    pop_back(dlist);
    show_dlist(dlist, print_int);

    printf("insert_prev(dlist, dlist->head->next->next, &a[4]);\n");
    insert_prev(dlist, dlist->head->next->next, &a[4]);
    show_dlist(dlist, print_int);

    printf("insert_next(dlist, dlist->head->next->next, &a[4]);\n");
    insert_next(dlist, dlist->head->next->next, &a[4]);
    show_dlist(dlist, print_int);

    printf("remove_dlist_node(dlist, dlist->head->next->next->next);\n");
    remove_dlist_node(dlist, dlist->head->next->next->next);
    show_dlist(dlist, print_int);

    get_front(dlist, &value);
    printf("\nfront:\n");
    print_int(value);

    get_tail(dlist, &value);
    printf("\ntail:\n");
    print_int(value);

    printf("\nthe length:\n");
    printf("%d \n",get_dlist_count(dlist));
    destroy_dlist(&dlist);
    return 0;
}
root@aemonair:my_dlist# ./dlist 
push_front(dlist, &a[i]);
5 4 3 2 1 
pop_front(dlist);
4 3 2 1 
push_back(dlist, &a[i]);
4 3 2 1 1 2 3 4 5 
pop_back(dlist);
4 3 2 1 1 2 3 4 
insert_prev(dlist, dlist->head->next->next, &a[4]);
4 3 5 2 1 1 2 3 4 
insert_next(dlist, dlist->head->next->next, &a[4]);
4 3 5 5 2 1 1 2 3 4 
remove_dlist_node(dlist, dlist->head->next->next->next);
4 3 5 2 1 1 2 3 4 

front:
4 
tail:
4 
the length:
9 

本文所有源程序可在Data_struct /double_list上得到。


总结:

本文简单的实现了较通用的双向链表,其中采用的通用思想不仅仅存在于封装操作,更是旨在使用void *指向的数据域,以及可以拓展的打印方式。
简单的接口,简单的操作,在对链表进行增删查改的时候,应该充分发挥其结构优势。
我们可以发现,作为一种很重要的数据结构,双向链表还是很重要的。今后还将继续对其进行更深一步的讨论。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值