单链表,双链表,静态链表,循环链表... ...
链表:链式存储结构,用于存储逻辑关系为"一对一"的数据.
与顺序表不同在于:链表的物理地址是不一定连续的
节点
-
结构体 指针
-
节点类型一般都是自定义的
头节点,尾节点,首元节点
首元节点: 第一个真正存储数据的节点
头指针,尾指针
创建单链表
首先,定义一个存放结点相关信息的结构体,结构体有两个元素,分别是键值和一个指向下一节点的指针
// 方便灵活改变类型
typedef int DataType;
// 节点结构体
struct node
{
DataType key; 0/
struct node * pnext;
};
typedef struct node Node;
//想要创建一个单链表,可以先创建一个表头结点(哑结点),然后在表头结点后不断插入新的结点即 可,需要注意的是,每新建一个结点都要为该结点分配一段内存空间
// **创建链表并给初始值
// 参数:长度
// 返回值:头指针
Node* CreateList(int Length)
{
// 判断长度
if (Length<=0)
{
printf("Length error!\n");
exit(EXIT_FAILURE);
}
// 1 创建头尾两个指针
Node *phead, *pear;
phead = pear = NULL;
// 2 申请内存,做头节点(哑节点)
phead = (Node*)malloc(sizeof(Node));
// 3 处理异常情况
if (phead == NULL)
{
perror("malloc failed!\n");
exit(EXIT_FAILURE);
}
/*
首先介绍一下:
exit(0): 正常执行程序并退出程序
exit(1): 非正常执行导致退出程序
其次介绍:
stdlib.h头文件中 定义了两个变量:
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
最后介绍:
exit(EXIT_SUCCESS) : 代表安全退出
exit(EXIT_FAILURE) : 代表异常退出
C 库函数 void perror(const char *str)
把一个描述性错误消息输出到标准错误 stderr。
首先输出字符串 str,后跟一个冒号,然后是一个空格。
*/
// 4 初始化头节点
phead->pnext = NULL;
phead->key = 0;
// 5 初始化尾节点(一个节点,头即是尾)
pear = phead;
// 6 通过循环添加节点
Node* pNewNode = NULL;
for (size_t i = 0; i < Length; i++)
{
// 1 申请一个节点,检测,给值
pNewNode = (Node*)malloc(sizeof(Node));
if (pNewNode == NULL)
{
perror("malloc failed!\n");
exit(EXIT_FAILURE);
}
int num = 0;
printf("输入节点数据: ");
scanf("%d", &num);
pNewNode->key = num;
pNewNode->pnext = NULL;
// 2 将新节点添加到链表中
// 添加过程中,头指针不动,尾指针改变
pear->pnext = pNewNode;
pear = pNewNode;
}
return phead;
}
遍历单链表
// **遍历链表
// 参数:链表头指针
// 返回值:无
void TraverseList(Node* const pList)
{
// pList->pnext 避开哑节点
Node* ptemp = pList->pnext;
if (ptemp == NULL)
{
printf("链表为空!");
}
while (ptemp)
{
printf("%d ", ptemp->key);
ptemp = ptemp->pnext;
}
printf("\n");
}
这段代码根据链表表尾结点的 next 指针指向 NULL 来遍历整个链表。
插入一个元素
// **插入元素(在位置后插入元素)
// 参数:头指针,插入位置指针,值
// 返回值:头指针
void InsertElement(Node* List, Node* Position, int val)
{
Node* tmpNode = (Node*)malloc(sizeof(Node));
if (tmpNode == NULL)
{
perror("malloc failed!\n");
exit(EXIT_FAILURE);
}
tmpNode->key = val;
tmpNode->pnext = Position->pnext;
Position->pnext = tmpNode;
}
这段代码将元素 val 插入到链表中指定结点的后面
删除全部元素
// **删除全部
// 参数:头指针
// 返回值:无
void DeleteList(Node* List)
{
Node* position, *tmpNode;
position = List->pnext;
List->pnext = NULL; // 头节点还在
while (position != NULL)
{
tmpNode = position->pnext; // 先将当前结点的 next 指针赋给临时结点保存
free(position); // 然后释放当前结点
position = tmpNode; // 再将以保存的 next 指针赋给 position, 即为下一个要删除的结点
}
}
删除整个链表时,需要注意一点,要提前将要删除结点的 next 指针保存下来,再释放该结点。而 不能在释放了一个结点后再去利用已释放结点的 next 指针去释放下一个结点,因为此时上一个结点已 经被释放了,故而找不到 next 指针。此外,由于在创建链表时,每插入一个新的结点都会用 malloc 来 给结点分配一块内存,故而在删除链表时,每释放一个结点也应该使用 free 来释放一次内存
删除指定元素
// **删除指定元素(第一个特定值的元素)
// 参数:头指针,值
// 返回值:头指针
void DeleteElement(Node* List, int val)
{
Node* tmpNode;
Node* ptemp = List;
while (ptemp->pnext != NULL && ptemp->pnext->key != val)
{
ptemp = ptemp->pnext;
}
if (ptemp->pnext == NULL)
{
printf("要删除的元素不存在!\n");
}
else
{
tmpNode = ptemp->pnext;
ptemp->pnext = tmpNode->pnext;
free(tmpNode);
}
}
删除一个元素时,需要先找到该元素的前驱结点
查找一个元素
// **查找元素(按照值查找)
// 参数:头指针,值
// 返回值:目标节点指针或NULL
Node* FindElement(Node* List, int val)
{
Node* ptr = List->pnext;
if (ptr == NULL)
{
printf("链表为空\n");
return NULL;
}
// 循环查找
while (ptr != NULL && ptr->key != val)
{
ptr = ptr->pnext;
}
if (ptr != NULL)
{
printf("找到 %d 了\n", val);
}
else
{
printf("没有找到 %d\n", val);
}
return ptr;
}
这段代码查找元素 val 是否在链表中,如果在,则打印元素已找到的信息,并返回该元素在链表中所在的结点;如果不在链表中,则打印没找到的信息,并返回一个空指针
注意
C库函数:
void perror(const char* str)
exit(EXIT_FAILURE);
exit(0); 正常执行 退出程序
exit(1); 非正常执行 退出程序
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1