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语言实现双向链表的主要内容,如果未能解决你的问题,请参考以下文章