使用纯C语言实现带头节点链表的基本操作
最近准备软考,复习了一下链表的相关知识,写了一个Demo。
直接上代码:
- 定义链表的节点
#include <stdio.h>
/**
结构体声明(链表的节点的数据域)
**/
typedef struct
{
int n;
}Data, *pData;
/*
链表的节点声明
*/
typedef struct Node
{
Data d; //数据域
struct Node *pNext; //指针域
}Node, *pNode; //为什么要这样写,和上面的结构体有什么区别,好处是什么
//这里 我用的枚举当做BOOL变量,可以使用stdBool.h头文件代替,也可以使用宏定义实现布尔值的定义
typedef enum {false = 0, true = 1}bool;
- 创建节点,申请空间
#include <stdio.h>
#include <stdlib.h>
pNode createNode(Data d)
{
pNode ptr = (pNode)malloc(sizeof(Node));
ptr->d.n = d.n;
ptr->pNext = NULL; //很重要的一步
return ptr;
}
- 创建List,返回头指针
#include <stdio.h>
#include <stdlib.h>
pNode List_Create()
{
//创建头结点,数据域的值赋值为-1
Data d;
d.n = -1;
return createNode(d);
}
- 释放整个链表
#include <stdio.h>
#include <stdlib.h>
void List_Delete(pNode *pHead)
{
List_DeleteAllNode(pHead); //调用删除链表所有节点的函数
free(*pHead); //再删除头结点
*pHead = NULL;
}
- 释放所有节点,返回头结点,链表仍在
pNode List_DeleteAllNode(pNode *pHead)
{
pNode ptr = (*pHead)->pNext;
while (NULL != ptr)
{
pNode pTemp = ptr;
ptr = ptr->pNext;
free(pTemp);
}
(*pHead)->pNext = NULL;
return *pHead;
}
- 追加节点(尾插法)
追加节点(尾插法), ptrNode是指向被插入节点的指针
int List_AppendTailNode(pNode *pHead, pNode ptrNode)
{
pNode ptr = (*pHead)->pNext;
//找尾节点
while (NULL != ptr->pNext)
{
ptr = ptr->pNext;
}
ptr->pNext = ptrNode;
return List_Lenth(pHead);
}
- 插入节点(头插法)
bool List_AppendFirstNode(pNode *pHead, pNode ptrNode)
{
ptrNode->pNext = (*pHead)->pNext;
(*pHead)->pNext = ptrNode;
return true;
}
- 插入节点(根据索引)
int List_InsertNode(pNode *pHead, int index, pNode ptrNode)
{
//判断传入的index是否合法
if ( index > List_Lenth(pHead) - 1 )
{
return -1;
}
pNode ptr = (*pHead)->pNext;
int i = -1; //循环变量,index从零开始计数, 第一个节点Node索引应该为0
while (NULL != ptr)
{
i++; //计数
if (i == index -1) //找到索引所在Node的前一位
{
ptrNode->pNext = ptr->pNext->pNext;
ptr->pNext = ptrNode;
break; //跳出循环
}
ptr = ptr->pNext; //计位置
}
return index;
}
- 删除某节点,根据Data
bool List_DeleteNode_ByData(pNode *pHead, Data d)
{
pNode ptr = (*pHead)->pNext;
pNode pPre = *pHead;
bool ret = false;
while (NULL != ptr)
{
if (d.n == ptr->d.n) //匹配成功
{
pPre->pNext = ptr->pNext;
deleteNode(&ptr);
ret = true;
break;
}
ptr = ptr->pNext;
pPre = pPre->pNext;
}
return ret; return false;
}
- 替换节点
先删除节点,再插入节点,思路很简单
void List_Replace(pNode *pHead, int index, pNode ptrNode)
{
//先删除节点,再插入节点
List_DeleteNode_ByIndex(pHead, index);
List_InsertNode(pHead, index, ptrNode);
}
- 删除某节点,根据节点
//删除某节点,根据节点
bool List_DeleteNode_ByNode(pNode *pHead, pNode ptrNode)
{
pNode ptr = (*pHead)->pNext;
pNode pPre = *pHead;
bool ret = false;
while (NULL != ptr)
{
if (ptrNode == ptr) //匹配成功
{
pPre->pNext = ptr->pNext;
deleteNode(&ptr);
ret = true;
break;
}
ptr = ptr->pNext;
pPre = pPre->pNext;
}
return ret;
}
- 删除某节点,根据索引
//删除某节点,根据索引
bool List_DeleteNode_ByIndex(pNode *pHead, unsigned int index)
{
if (index == 0)
{
return false;
}
pNode ptr = (*pHead)->pNext;
pNode pPre = *pHead;
unsigned int len = 0;
bool ret = false;
while (NULL != ptr)
{
if (len == index) //匹配到索引
{
pPre->pNext = ptr->pNext;
ret = true;
deleteNode(&ptr);
break;
}
ptr = ptr->pNext;
pPre = pPre->pNext;
}
return ret;
}
- 查找某节点,根据索引
//查找某节点,根据索引返回该节点指针
pNode List_FindNode_ByIndex(pNode *pHead, unsigned int index)
{
pNode ptr = (*pHead)->pNext;
pNode p = NULL;
unsigned int len = 0;
while (NULL != ptr)
{
if (len == index)
{
p = ptr;
break;
}
len++;
ptr = ptr->pNext;
}
return p;
}
- 查找某节点,根据节点指针
//查找某节点,根据节点指针返回该节点第一次出现的位置索引
int List_FindNode_ByNode(pNode *pHead, pNode ptrNode)
{
pNode ptr = (*pHead)->pNext;
unsigned int index = 0;
while (NULL != ptr)
{
index++;
if (ptr->d.n == ptrNode->d.n)
{
break;
}
ptr = ptr->pNext;
}
return index;
}
- 链表逆序(递归实现)
//链表逆序,返回头指针(递归实现)
pNode List_Invert_In(pNode *pHead)
{
if (*pHead == NULL || (*pHead)->pNext == NULL) //链表为空直接返回,而H->next为空是递归基
{
return *pHead;
}
pNode newHead = List_Invert_In(&((*pHead)->pNext)); //一直循环到链尾
(*pHead)->pNext->pNext = *pHead; //翻转链表的指向
(*pHead)->pNext = NULL; //记得赋值NULL,防止链表错乱
return newHead; //新链表头永远指向的是原链表的链尾
}
- 链表逆序(迭代实现)
pNode List_Invert(pNode *pHead)
{
//只有一个节点或者没有节点直接返回头结点
if ( NULL == (*pHead)->pNext || NULL == (*pHead)->pNext->pNext)
{
return *pHead;
}
//注意:形参是二级指针
pNode pPre = *pHead; //指向当前的Node的前一个Node
pNode pCur = pPre->pNext; //指向当前的Node
pNode pNext = NULL; //指向当前的Node的下一个Node
while (NULL != pCur)
{
pNext = pCur->pNext; //指向当前的Node的下一个Node
if (*pHead == pPre)
{
//当前节点的上一个节点是头节点,当前节点指向空
pCur->pNext = NULL;
}
else //当前节点的上一个节点不是头节点
{
pCur->pNext = pPre; //当前节点的指针域指向当前节点的上一个节点
}
pPre = pCur;
pCur = pNext; //当前指向的Node移到下一位
}
(*pHead)->pNext = pPre;
return *pHead;
}
- 链表的长度
unsigned int List_Lenth(pNode * pHead)
{
pNode ptr = (*pHead)->pNext;
unsigned int len = 0;
while (NULL != ptr)
{
ptr = ptr->pNext;
len++;
}
return len;
}
- 注:
在所有的函数参数列表中,只要用到了头结点的地方,我传参都是传的二级指针,有什么好处??
- 以下是我的测试函数,有一部分函数没有测试:
#include "List.h"
#include <stdio.h>
void printList(pNode *pHead);
int main(void)
{
printf("初始化:\n");
pNode pHead = List_Create();
int i = 10;
while (i)
{
Data d;
d.n = i;
List_AppendFirstNode(&pHead, createNode(d));
i--;
}
//打印链表
printList(&pHead);
printf("\n\n第一次逆序(迭代):\n");
pHead = List_Invert(&pHead);
//打印链表
printList(&pHead);
printf("\n\n第二次逆序(递归):\n");
pHead = List_Invert_In(&pHead); //这个函数有问题
//打印链表
printList(&pHead);
printf("\n\n 删除链表所有节点前:%d\n", List_Lenth(&pHead));
printList(&pHead);
List_DeleteAllNode(&pHead);
printf("\n\n 删除链表所有节点后:%d\n", List_Lenth(&pHead));
printf("\n\n pHead存储的内存地址:%d\n", pHead);
printf("\n\n 释放链表:\n");
List_Delete(&pHead);
printf("\n\n pHead存储的内存地址:%d\n", pHead);
printf("\n\n 保存pHead的内存地址:%d\n", &pHead);
getchar();
return 0;
}
void printList(pNode *pHead)
{
printf("\n\nStart Print:\n");
int i = -1;
pNode p = (*pHead)->pNext;
while (p != NULL)
{
i++;
printf("i = %d, D::n = %d\n", i, p->d.n);
p = p->pNext;
}
printf("End Print:\n\n");
}