数据结构之双向链表(C语言实现)
本次介绍双向链表,双向链表是在单链表的基础上增加一个前驱指针,通过某个节点可以直接找到它的前驱和后继,这个对于删除操作来说更加容易,不需要另外再定义一个指针来指向它的前驱。
和以前一样,首先介绍双向链表的通用操作:
1.链表的初始化
2.申请一个链表节点
3.链表的头插法
4.链表的尾插法
5.获取链表长度
6.删除链表节点
7.查找指定值的节点
8.销毁链表(释放链表所有节点的内存空间)
9.输出单链表(输出单链表所有节点的数据域)
说明:以下双向链表的实现,是数据域以整型为例,而且带有头结点。
一、双向链表
1. 链表的结构
typedef struct _DNode
{
int data;
struct _DNode* pre;//前驱指针
struct _DNode* next; //后继指针
}DNode, *DList;
2.链表的操作
(1).链表的初始化(带头结点)
这里的初始化只要是指初始化头结点的指针域
void InitList(DList plist) //初始化头结点
{
if (NULL == plist)
return;
plist->next = NULL;
plist->pre = NULL;
}
(2).申请一个链表节点
从堆中申请一个节点,注意这里是从堆中申请的内存,只能通过free(p)显式释放内存。即使是局部变量,该内存也不会随着函数调用完成而释放该内存。
DNode* BuyNode(int val)
{
DNode* pTmp = (DNode*)malloc(sizeof(DNode));
pTmp->data = val;
pTmp->next = NULL;
pTmp->pre = NULL;
return pTmp;
}
(3).链表头插法
这里的链表是带有头结点的,所以每次新插入的节点应插入头结点后面。注意看看下面的注释
void InsertHead(DList plist, int val)
{
DNode* pTmp = BuyNode(val); //申请一个新的节点
pTmp->next = plist->next;
pTmp->pre = plist;
if (NULL != pTmp->next) //这里的判断一定不能少,读者可以试想去掉这个会发生什么
{
pTmp->next->pre = pTmp; //后一个节点的前驱指向pTmp
}
plist->next = pTmp;
}
(4).链表尾接法
每次将新插入的节点插入到最后一个节点后面,所以采用尾接法插入节点时首先要找到尾节点
void InsertTail(DList plist, int val)
{
DNode* pTmp = BuyNode(val);
DNode* pCur = plist;
while (NULL != pCur->next)
{
pCur = pCur->next;
}
pTmp->pre = pCur;
pCur->next = pTmp;
}
(5).获取链表长度
对链表进行遍历,每遍历一个节点,计数器加一。
int GetListLen(DList plist) { DNode* pTmp = plist->next; int iCount = 0; while (NULL != pTmp) { ++iCount; pTmp = pTmp->next; } return iCount; }
(6).删除链表节点删除指定值的链表节点时,需要遍历该链表,找到对应节点后想要删除该节点必须要知道该节点的前驱节点,这样才能正确删除该节点。这里要注意对于最后一个节点的删除要先进行判断,读者可以自己思考一下这样做的原因
bool Delete(DList plist, int key) { DNode* pPre = plist; //前驱结点 DNode* pCur = plist->next; //后继节点 while (NULL != pCur) { if (pCur->data == key) { if (NULL != pCur->next) //这涉及到删除最后一个节点,因为对于最后一个节点来说pCur->next为空,也即pCur->next没有pre { pCur->next->pre = pCur->pre; } pCur->pre->next = pCur->next; return true; } else { pPre = pCur; pCur = pCur->next; } } return false; }
(7).查找指定值的节点
查找指定值的节点也需要从头到尾遍历链表,若找到则返回该节点,没找到则返回NULL。
DNode* Search(DList plist, int key) { DNode* pCur = plist->next; while (NULL != pCur) { if (pCur->data == key) { return pCur; } pCur = pCur->next; } return NULL; }
(8).销毁链表
销毁链表就是释放链表中所有节点的内存。
void Destroy(DList plist) { DNode* pTmp = plist->next;//这里必须从plist->next开始释放内存,原因有两个 while (NULL != pTmp) //一是头结点不是由malloc开辟的,不能有free释放 { //二是一个空链表是指只有头结点的链表,销毁操作就是把原链表置为空链表 plist = pTmp->next; free(pTmp); pTmp = plist; } }
(9).输出单链表
输出单链表的操作也比较简单,从头到尾遍历单链表,每遍历一个节点就输出该节点的指针域
void ShowDList(DList plist) { DNode* pCur = plist->next; while (NULL != pCur) { printf("%5d", pCur->data); pCur = pCur->next; } printf("\n"); }
最后附上完整代码和运行结果: //DLink.h #include<stdio.h> #include<stdlib.h> typedef struct _DNode { int data; struct _DNode* pre; //前驱指针 struct _DNode* next; //后继指针 }DNode, *DList; void InitList(DList plist); void InsertHead(DList plist, int val); void InsertTail(DList plist, int val); bool Delete(DList plist, int key); DNode* Search(DList plist, int key); int GetListLen(DList plist); void Destroy(DList plist); DNode* BuyNode(int val); void ShowDList(DList plist); //DLink.c #include "test.h" int main() { DNode head; InitList(&head); for (int i = 1; i < 5; ++i) { InsertHead(&head, i); } ShowDList(&head); printf("DList length is %d \n", GetListLen(&head)); for (int i = 6; i < 10; ++i) { InsertTail(&head, i); } ShowDList(&head); printf("DList length is %d \n", GetListLen(&head)); printf("search 第一个节点4:"); DNode* pTmp = Search(&head, 4); printf("%d\n", pTmp->data); printf("search 最后一个节点9:"); pTmp = Search(&head, 9); printf("%d\n", pTmp->data); printf("删除第一个节点4:\n"); if (Delete(&head, 4)) { ShowDList(&head); } else { printf("Not Fount!!!\n"); } printf("删除最后一个节点9:\n"); if (Delete(&head, 9)) { ShowDList(&head); } else { printf("Not Fount!!!\n"); } Destroy(&head); //销毁链表 return 0; } void InitList(DList plist) //初始化头结点 { if (NULL == plist) return; plist->next = NULL; plist->pre = NULL; } DNode* BuyNode(int val) { DNode* pTmp = (DNode*)malloc(sizeof(DNode)); pTmp->data = val; pTmp->next = NULL; pTmp->pre = NULL; return pTmp; } void ShowDList(DList plist) { DNode* pCur = plist->next; while (NULL != pCur) { printf("%5d", pCur->data); pCur = pCur->next; } printf("\n"); } void InsertHead(DList plist, int val) { DNode* pTmp = BuyNode(val); //申请一个新的节点 pTmp->next = plist->next; pTmp->pre = plist; if (NULL != pTmp->next) //这里的判断一定不能少,读者可以试想去掉这个会发生什么 { pTmp->next->pre = pTmp; //后一个节点的前驱指向pTmp } plist->next = pTmp; } void InsertTail(DList plist, int val) { DNode* pTmp = BuyNode(val); DNode* pCur = plist; while (NULL != pCur->next) { pCur = pCur->next; } pTmp->pre = pCur; pCur->next = pTmp; } bool Delete(DList plist, int key) { DNode* pPre = plist; //前驱结点 DNode* pCur = plist->next; //后继节点 while (NULL != pCur) { if (pCur->data == key) { if (NULL != pCur->next) //这涉及到删除最后一个节点,因为对于最后一个节点来说pCur->next为空,也即pCur->next没有pre { pCur->next->pre = pCur->pre; } pCur->pre->next = pCur->next; return true; } else { pPre = pCur; pCur = pCur->next; } } return false; } DNode* Search(DList plist, int key) { DNode* pCur = plist->next; while (NULL != pCur) { if (pCur->data == key) { return pCur; } pCur = pCur->next; } return NULL; } int GetListLen(DList plist) { DNode* pTmp = plist->next; int iCount = 0; while (NULL != pTmp) { ++iCount; pTmp = pTmp->next; } return iCount; } void Destroy(DList plist) { DNode* pTmp = plist->next; while (NULL != pTmp) { plist = pTmp->next; free(pTmp); pTmp = plist; } } 运行结果: