C语言实现双向链表

Posted Zip Zou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言实现双向链表相关的知识,希望对你有一定的参考价值。

C语言实现双向链表

项目已经托管到github,地址:github项目托管地址

项目语言:采用C语言实现
实现思路:使用双向链表实现,双向链表具有的单向链表所不具备的优势,那就是可以双向遍历,这对增、删操作的效率都有显著的提高。而弊端则是造成空间效率的下降,即需要额外的指针来维持双向链表之间的连接关系。链表的实现中,我们默认采用了两个哨兵节点,该两节点标识了整个链表的头部和尾部,但其数据域不存储具体数据。同样,这对于查找时,可以方便的区分链表是否到尾部而结束。但是同样会造成空间的浪费。值得注意的是,链表是逻辑结构,在逻辑思维中,我们把这样的一种结构称之为线性结构,而实际在计算机的存储过程中,并不呈现一种严格的线性关系。而是零散的内存段区域。因为内存在进行分配时,是随机的,而不是按照线性的方式进行分配,这不符合操作系统的内存管理机制。

基本实现部分:

准备工作: 为优化代码风格,我们会极力避免在源代码中采用数字进行编码,这样会使得代码的可读性大大降低,于是我们通常约定,使用宏定义来将数字定义为符号常量,这样数字变得拥有具体含义,提高代码可读性。

#ifndef TRUE
#define TRUE 1
#endif // !TRUE
#ifndef FALSE
#define FALSE 0
#endif //!FALSE
#ifndef NULL
#define NULL 0
#endif // !NULL
#ifndef NODE_NOT_FOUND
#define NODE_NOT_FOUND -1 
#endif // !NODE_NOT_FOUND
#ifndef SIZE_STUDENT
#define SIZE_STUDENT sizeof(Student)
#endif // !SIZE_STUDENT
typedef unsigned int Rank;
typedef int BOOL;
typedef struct Student

    char name[20];
    int age;
    struct Student *next;
    struct Student *last;
Student, *PLinkHead, *PLinkNode;

数据定义:采用学生结构体进行测试和构建。同时应该注意,我们在进行编码时,通常是需要将逻辑与操作分离,这对于可视化界面尤为重要,我们有经典性即将表现与性了代码的可读性,还大大降低日后项目维护所需要花费的成本。

实现过程

函数前向声明:

// If you want, you can remove your statements to another new header file, and create a new 
// source file to realize every function.

/*
    To display information about student
    * @param student The student you want to display.
*/
void display(Student * student);

/* 
    Create a link list with head node empty.
    * @return Return the head of link list created. If you have get NULL, it stands for allocate failed.
*/
PLinkHead createLinkListHeadEmpty();

/*
    To insert a node into the link list.
 * @param stu_node The student to insert into the link list.
 * @param _link_list_head The destination inserted into.
 * @return Return the state of execute result.
*/
BOOL addNodeToLinkList(Student * stu_node, PLinkHead _link_list_head);

/*
    To judge whether the link list is empty.
    If you have got FALSE, it will tell you the link list isn't empty.
    Or The link list is empty.
    * @param _link_head The head of the link list.
    * @return Return the result.
*/
BOOL isEmpty(PLinkHead _link_head);

/*
    To judge whether the node is the last one.
    If you get TRUE, then the node is the last one.
    * @param stu The node you will judge.
    * @param _link_head The head of the link list.
    * @return Return the result.
*/
BOOL isLastOne(PLinkNode stu, PLinkHead _link_head);

/*
    To get the size of the link list.
    * @param _link_head The head of the link list destination.
    * @return Return the size of the link list, and you can use this value to find it empty or not.
*/
size_t linkListSize(PLinkHead _link_head);

/*
    To remove a node you appoint in the link list.
    * @param stu_node The student you will remove from the link list.
    * @param _link_head The head of the link list destination.
    * @return Return the state after removed.
*/
BOOL removeNodeInLinkList(Student * stu_node, PLinkHead _link_head);

/*
    To remove a node by index.
    You should ensure the index is right, or it will be failed.
    * @param index The index you want to remove.
    * @param _link_head The head of link list destination.
    * @return Return the state after removed.
*/
BOOL removeNodeAt(Rank index, PLinkHead _link_head);
/*
    Respond to the test on two students.
    * @param origin_stu The first student to compare.
    * @param dest_stu The second student to compare.
    * @return Return the test result.If you get TRUE, it will stand for equal.
*/
BOOL isEqualBetweenStudents(Student * origin_stu, Student * dest_stu);
/*
    To find the student being equal with the student you appoint.
    * @param stu_node The student you are about to find.
    * @param _link_head The head of link list which you want to find in.
    * @return Return the pointer of the node found.If you get NULL, it will stand for failing to find.
*/
PLinkNode findInLinkList(Student * stu_node, PLinkHead _link_head);
/*
    Find the index of the student in the link list.
    * @param stu_node The student you are about to find.
    * @param _link_head The head of link list.
    * @return Return the index of the student you appoint. If you get NODE_NOT_FOUNT, it will stand for there is such node in the link list as 
                the student you appoint.
*/
Rank indexOf(Student * stu_node, PLinkHead _link_head);
/*
    Get the student by index.
    You should ensure the index is right and not out of range.
    * @param index The index you want to get.
    * @param _link_head The head of link list.
    * @return Return the student you want to get.If you get NULL, it may be the index out of range.
*/
PLinkNode at(Rank index, PLinkHead _link_head);

/*
    Clear all nodes in the link list.
    Attention: Please call it after you used the link list. And make the header NULL to avoid wild pointer.
    * @param _link_head The head of link list destination.
    * @return Return the count of nodes cleaned.
*/
size_t clearLinkList(PLinkHead  _link_head);

/*
    To get the first student in the link list.
    * @param _link_head The head of the link list.
    * @return Return the first Student. If the link list is empty, you will get NULL.
*/
Student *first(PLinkHead _link_head);

在构建过程中,首先需要调用createLinkListHeadEmpty()函数来创建空的双向链表,该函数将会返回该链表的头部,供增删查改等操作。

创建过程尤为简单,只需要申请两段内存区域,并构建他们之间的连接关系即可了。

PLinkHead createLinkListHeadEmpty()

    PLinkHead  head = NULL;
    PLinkHead tail = NULL;
    head = (PLinkHead)malloc(SIZE_STUDENT);
    tail = (PLinkHead)malloc(SIZE_STUDENT);
    head->next = tail;
    head->last = NULL;
    tail->last = head;
    tail->next = NULL;
    return head;

如此,便完成了链表的创建,获得该非NULL的头,表示创建成功。

线性链表与线性向量对比所具有的优势,则体现在其插入和删除的高效性。
像链表中加入某个节点,只需要调用BOOL addNodeToLinkList(Student * stu_node, PLinkHead _link_list_head);函数即可,分别传入要增加的节点数据,链表头节点标识链表即可。
其实现方式也异常简单。

BOOL addNodeToLinkList(Student * stu_node, PLinkHead _link_list_head)

    if (!_link_list_head || !stu_node) // if the link list is not existent or student is not been initialied, failed.
    
        return FALSE;
    
    // insert as the last one
    PLinkNode curNode = _link_list_head->next;
    while (curNode->next != NULL)
    
        curNode = curNode->next;
    
    // get the last position, and begin to insert
    curNode->last->next = stu_node;
    stu_node->last = curNode->last;
    curNode->last = stu_node;
    stu_node->next = curNode;
    return TRUE;

这样一来,就完成了插入,我们默认是采用尾插法,其时间效率主要消耗在遍历过程中。而插入时的效率只需常数时间。

完成插入,常用的不可缺少的还有删除操作。
完成删除,有多种情况,1. 删除索引在某个位置的元素 2. 删除具体的元素

这两种方式都需要提供相应的方法,但其核心和基本都在于删除上,由于链表未进行排序,查找时进行遍历,可以花费线性的时间复杂度,这也是该实现方式中,最主要的时间效率的消耗。

BOOL removeNodeInLinkList(Student * stu_node, PLinkHead _link_head)

    if (!_link_head) // if the link list is not existent
    
        return FALSE;
    
    if (isEmpty(_link_head))
    
        return FALSE;
    

    // step 1: find the node to remove
    PLinkNode curNode = _link_head->next;
    while (curNode->next)
    
        if (isEqualBetweenStudents(stu_node, curNode)) // to find the node appointed
        
            break;
        
    
    if (!curNode->next) // if not found
    
        return FALSE;
    
    // step 2: begin to remove
    curNode->last->next = curNode->next;
    curNode->next->last = curNode->last;

    // step 3: begin to free the node removed
    free(curNode);
    // step 4: begin make pointer NULL for safety
    curNode = NULL;
    return TRUE;


BOOL removeNodeAt(Rank index, PLinkHead _link_head)

    if (index < 0) // illegal index
    
        return FALSE;
    
    if (!_link_head) // if the link list is not existent
    
        return FALSE;
    
    int curIndex = 0;
    PLinkNode curNode = at(index, _link_head);
    if (!curNode)
    
        return FALSE;
    

    // remove
    curNode->next->last = curNode->last;
    curNode->last->next = curNode->next;
    free(curNode);
    curNode = NULL;
    return TRUE;

以上则是对具体元素的删除和指定索引处的元素删除。

另一重要的操作则是查找,在对未排序线性表的查找中,遍历一次需要线性的时间复杂度,这是可以接受的。若是对线性表进行排序,则可采用折半查找的方式,这样会将线性复杂度,降低到对数。

PLinkNode findInLinkList(Student * stu_node, PLinkHead _link_head)

    PLinkNode foundNode = NULL;
    if (!stu_node || !_link_head) // if the studetn is NULL or the link list is not existent
    
        return foundNode;
    
    foundNode = _link_head->next;
    while (foundNode->next)
    
        if (isEqualBetweenStudents(stu_node, foundNode))
        
            break;
        
    
    if (!foundNode->next) // end with tail, not found
    
        return NULL;
    
    return foundNode;


Rank indexOf(Student * stu_node, PLinkHead _link_head)

    Rank curIndex = 0;
    if (!stu_node || !_link_head) // the student is null or the link list is not existent
    
        return NODE_NOT_FOUND;
    
    PLinkNode curNode = _link_head->next;
    while (curNode->next)
    
        if (isEqualBetweenStudents(stu_node, curNode))
        
            break;
        
        curIndex++; // increase when they are not equal
    
    if (!curNode->next) // end with tail, and find failed
    
        return NODE_NOT_FOUND;
    
    return curIndex;


PLinkNode at(Rank index, PLinkHead _link_head)

    if (index < 0) // illegal index
    
        return FALSE;
    
    if (!_link_head) // if the link list is not existent
    
        return FALSE;
    
    unsigned int curIndex = 0;
    PLinkNode curNode = _link_head->next;
    while (curNode->next) // to find index
    
        if (curIndex >= index)
        
            break;
        
    
    if (curIndex < index || curIndex > index) // the index is out of range or mistake when iterated
    
        return NULL;
    
    if (!curNode->next)
    
        return NULL;
    
    return curNode;

这样分别完成了,不同情况下的查找过程,同时也给出了不同的返回值,供不同需求的查找需要,其中有部分函数为辅助函数,可以下载源文件后对照查看。

在C/C++语言中,最需要注意的除了指针之外还需要内存管理。对内存管理不熟悉的朋友,可以查看博客中相应的内存管理部分:内存管理

内存管理,简单来说即避免不必要的内存泄漏,在堆区开辟的内存空间,必须在程序员使用后进行释放,否则,将造成内存泄漏。于是在使用链表的过程中,同样需要对链表进行释放。思路简单,可以采用循环方式依次移除链表中的元素,也可采用递归方式,从尾至头的移除各个元素。本实现中采用循环方式实现。

size_t clearLinkList(PLinkHead  _link_head)

    size_t _count_del = 0;
    if (!_link_head) // if the link list is not existent
    
        return _count_del;
    
    PLinkNode curNode = _link_head->next;
    while (curNode->next) // to remove student except guard pointer
    
        PLinkNode temp = NULL;
        temp = curNode->next; // for next iteration
        // remove
        curNode->last->next = curNode->next;
        curNode->next->last = curNode->last;
        free(curNode);
        // next
        curNode = temp;
        // make count increase
        _count_del++;
    
    // to remove guard pointer
    PLinkHead head = _link_head;
    PLinkNode tail = curNode;
    free(head);
    free(tail);
    head = NULL;
    tail = NULL;
    return _count_del;

至此,主要的核心的实现过程完毕,某些辅助函数,可以到github下载源文件,进行对照学习。

以上是关于C语言实现双向链表的主要内容,如果未能解决你的问题,请参考以下文章

C语言实现双向链表

数据结构:链表

双向循环链表增删查改C语言实现

双向链表排序c语言程序设计

双向链表排序c语言程序设计

python实现双向链表