赞
踩
(图像由AI生成)
在计算机科学中,数据结构是存储和组织数据的一种方式,它不仅影响数据的存储,也影响数据的检索和更新效率。C语言,作为一种经典的编程语言,提供了灵活的方式来处理数据结构,其中链表是最基本且重要的一种。
链表(Linked List)是一种在物理上非连续、非顺序的数据结构,由一系列节点(Node)组成。链表的每个节点由两部分构成:一是存储数据元素的数据域,二是存储下一个节点地址的指针域。这种结构允许在不重新整理整个数据结构的情况下,有效地插入和删除节点。
按照单双向性、是否循环、以及是否带头节点,链表可以被分为以下八种主要类型:
单向不循环不带头链表
单向不循环带头链表
单向循环不带头链表
单向循环带头链表
双向不循环不带头链表
双向不循环带头链表
双向循环不带头链表
双向循环带头链表
在深入探讨链表的不同分类后,我们将先集中讨论单链表的实现与操作。单链表,作为链表家族中最简单的成员,提供了对链表基本概念的清晰理解。我们将详细介绍单链表(单向不带头不循环链表)的实现方法和各种操作函数。
单链表是一种基础的数据结构,由一系列节点(Node)组成。每个节点包含一个数据域和一个指向下一个节点的指针域。
我们先来看一下单向不带头不循环链表的具体实现,包括3个文件:头文件SList.h、源文件SList.c和test.c.
- //SList.h
- #pragma once
- #include<stdio.h>
- #include<stdlib.h>
- #include<assert.h>
-
- typedef int SLTDataType;
- typedef struct SListNode {
- SLTDataType data;
- struct SListNode* next;
- }SLTNode;
-
- //打印
- void SLTPrint(SLTNode* phead);
- //获取新节点
- SLTNode* BuyNewNode(SLTDataType x);
- //尾插
- void SLTPushBack(SLTNode** pphead, SLTDataType x);
- //头插
- void SLTPushFront(SLTNode** pphead, SLTDataType x);
- //尾删
- void SLTPopBack(SLTNode** pphead);
- //头删
- void SLTPopFront(SLTNode** pphead);
- //查找
- SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
- //在pos位置之后插入x
- void SLTInsertAfter(SLTNode* pos, SLTDataType x);
- //删除pos位置之后的节点
- void SLTEraseAfter(SLTNode* pos);
- //删除pos位置的节点
- void SLTErase(SLTNode** pphead, SLTNode* pos);
- //在pos位置之前插入x
- void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);
- //销毁
- void SLTDestroy(SLTNode** pphead);
-
-
-
- //SList.c
- #define _CRT_SECURE_NO_WARNINGS 1
-
- #include"SList.h"
-
- //打印
- void SLTPrint(SLTNode* phead)
- {
- //assert(phead);
- SLTNode* pcur = phead;
- while (pcur)
- {
- printf("%d->", pcur->data);
- pcur = pcur->next;
- }
- printf("NULL\n");
- }
- //获取新节点
- SLTNode* BuyNewNode(SLTDataType x)
- {
- SLTNode* pnewnode = (SLTNode*)malloc(sizeof(SLTNode));
- if (pnewnode == NULL)
- {
- assert(0);
- return NULL;
- }
- pnewnode->data = x;
- pnewnode->next = NULL;
- return pnewnode;
-
- }
- //尾插
- void SLTPushBack(SLTNode** pphead, SLTDataType x)
- {
- assert(pphead);
- SLTNode* pnewnode = BuyNewNode(x);
- if (*pphead == NULL)
- {
- *pphead = pnewnode;
- }
- else
- {
- SLTNode* pcur = *pphead;
- while (pcur->next != NULL)
- {
- pcur = pcur->next;
- }
- pcur->next = pnewnode;
- }
- }
- //头插
- void SLTPushFront(SLTNode** pphead, SLTDataType x)
- {
- assert(pphead);
- SLTNode* pnewnode = BuyNewNode(x);
- if (*pphead == NULL)
- {
- *pphead = pnewnode;
- }
- else
- {
- pnewnode->next = *pphead;
- *pphead = pnewnode;
- }
- }
- //尾删
- void SLTPopBack(SLTNode** pphead)
- {
- assert(pphead);
- SLTNode* pcur = *pphead;
- //链表为空
- if (*pphead == NULL)
- {
- return;
- }
- //链表只有一个节点
- else if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- //链表有多个节点
- else
- {
- while (pcur->next->next != NULL)
- {
- pcur = pcur->next;
- }
- free(pcur->next);
- pcur->next = NULL;
- }
- }
- //头删
- void SLTPopFront(SLTNode** pphead)
- {
- assert(pphead);
- SLTNode* pcur = *pphead;
- if (pcur == NULL)
- {
- return;
- }
- else
- {
- *pphead = pcur->next;
- free(pcur);
- }
- }
- //查找
- SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
- {
- assert(phead);
- SLTNode* pcur = phead;
- while (pcur)
- {
- if (pcur->data == x)
- {
- return pcur;
- }
- pcur = pcur->next;
- }
- //没找到
- return NULL;
- }
- //在pos位置之后插入x
- void SLTInsertAfter(SLTNode* pos, SLTDataType x)
- {
- assert(pos);
- SLTNode* pnewnode = BuyNewNode(x);
- pnewnode->next = pos->next;
- pos->next = pnewnode;
-
- }
- //删除pos位置之后的节点
- void SLTEraseAfter(SLTNode* pos)
- {
- assert(pos);
- SLTNode* pcur = pos->next;
- //pos之后没有节点
- if (pcur == NULL)
- {
- return;
- }
- //pos之后有节点
- else
- {
- pos->next = pcur->next;
- free(pcur);
- }
- }
- //删除pos位置的节点
- void SLTErase(SLTNode** pphead, SLTNode* pos)
- {
- assert(pphead);
- assert(*pphead);
- assert(pos);
- if(pos==*pphead)
- {
- SLTPopFront(pphead);
- }
- else
- {
- SLTNode* pcur = *pphead;
- while (pcur->next != pos)
- {
- pcur = pcur->next;
- }
- pcur->next = pos->next;
- free(pos);
- }
- }
- //在pos位置之前插入x
- void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
- {
- assert(pphead);
- assert(pos);
- assert(*pphead);//链表不为空
- SLTNode* pnewnode = BuyNewNode(x);
- //pos为头节点
- if (*pphead == pos)
- {
- /* pnewnode->next = *pphead;
- *pphead = pnewnode;*/
- SLTPushFront(pphead, x);
- }
- //pos不为头结点
- else
- {
- SLTNode* pcur = *pphead;
- while (pcur->next != pos)
- {
- pcur = pcur->next;
- }
- pnewnode->next = pos;
- pcur->next = pnewnode;
- }
- }
- //销毁
- void SLTDestroy(SLTNode** pphead)
- {
- assert(pphead);
- SLTNode* pcur = *pphead;
- while (pcur)
- {
- *pphead = pcur->next;
- free(pcur);
- pcur = *pphead;
- }
- }
-
-
-
- //test.c
- #define _CRT_SECURE_NO_WARNINGS 1
-
- #include"SList.h"
-
- void SListTest01()
- {
- printf("SListTest01()\n");
- SLTNode* node1 = BuyNewNode(1);
- SLTPushBack(&node1, 2);
- SLTPushBack(&node1, 3);
- SLTPushBack(&node1, 4);
- SLTPushBack(&node1, 5);
- SLTPrint(node1);
- SLTPushFront(&node1, 0);
- SLTPrint(node1);
- SLTNode* ToFind = SLTFind(node1, 0);
- if(ToFind)
- {
- printf("Find it!\n");
- }
- else
- {
- printf("Not Find!\n");
- }
- SLTInsertAfter(ToFind, 500);
- SLTPrint(node1);
- SLTInsertBefore(&node1, ToFind, 100);
- SLTPrint(node1);
- SLTErase(&node1, ToFind);
- SLTPrint(node1);
- }
- void SListTest02()
- {
- printf("SListTest02()\n");
- SLTNode* node1 = BuyNewNode(1);
- SLTPushBack(&node1, 2);
- SLTPushBack(&node1, 3);
- SLTPushBack(&node1, 4);
- SLTPushBack(&node1, 5);
- SLTPrint(node1);
- SLTPopBack(&node1);
- SLTPrint(node1);
- SLTPopFront(&node1);
- SLTPrint(node1);
- SLTNode* ToFind = SLTFind(node1, 3);
- SLTEraseAfter(ToFind);
- SLTPrint(node1);
- SLTDestroy(&node1);
- SLTPrint(node1);
-
- }
- int main()
- {
- SListTest01();
- SListTest02();
- return 0;
- }
在单链表的实现中,以下是关键的数据类型定义和各个函数的实现原理与作用:
typedef int SLTDataType;
SLTDataType
作为链表节点存储的数据类型,这里设为整型(int
)。typedef struct SListNode { SLTDataType data; struct SListNode* next; } SLTNode;
SLTNode
,包含数据(data
)和指向下一个节点的指针(next
)。打印链表(SLTPrint
)
phead
。NULL
结束。获取新节点(BuyNewNode
)
x
。尾插(SLTPushBack
)
pphead
,节点数据 x
。next
指向新节点。头插(SLTPushFront
)
pphead
,节点数据 x
。next
指向原头节点,然后更新头节点。尾删(SLTPopBack
)
pphead
。next
设置为 NULL
。头删(SLTPopFront
)
pphead
。查找(SLTFind
)
phead
,要查找的数据 x
。x
的节点。指定位置后插入(SLTInsertAfter
)
pos
,节点数据 x
。next
指向 pos
的 next
,然后 pos
的 next
指向新节点。指定位置后删除(SLTEraseAfter
)
pos
。pos
的 next
指向要删除节点的 next
。指定位置删除(SLTErase
)
pphead
,要删除的节点指针 pos
。pos
的前一个节点,更新其 next
指针。指定位置前插入(SLTInsertBefore
)
pphead
,目标节点指针 pos
,节点数据 x
。pos
的前一个节点,执行插入操作。销毁链表(SLTDestroy
)
pphead
。继单链表之后,我们将转向更为复杂的双向链表。双向链表(双向带头循环链表)不仅在节点结构上比单链表复杂,其操作也更为多样。通过比较这两种链表类型,我们可以更好地理解链表作为一种数据结构的灵活性和多功能性。
双向链表是一种常见的线性数据结构,与单链表不同,它每个节点有两个指针域,一个指向前一个节点,一个指向后一个节点,因此可以从前往后或从后往前遍历链表。本文将详细介绍双向带头循环链表的实现以及双向链表的各种常用操作函数。
我们先来看一下双向带头循环链表的具体实现,包括3个文件:头文件List.h、源文件List.c和test.c.
- //List.h
- #pragma once
- #include<stdio.h>
- #include<stdlib.h>
- #include<assert.h>
-
- typedef int LTDataType;
- typedef struct ListNode {
- LTDataType data;
- struct ListNode* prev;
- struct ListNode* next;
- }LTNode;
-
- void LTPrint(LTNode* phead);//打印
- void LTInit(LTNode** pphead);//初始化(通过传参)
- LTNode* LTInit2();//初始化(通过返回值)
- void LTDestroy(LTNode** pphead);//销毁
- LTNode* BuyLTNode(LTDataType x);//创建新节点
- void LTPushBack(LTNode* phead, LTDataType x);//尾插
- void LTPopBack(LTNode* phead);//尾删
- void LTPushFront(LTNode* phead, LTDataType x);//头插
- void LTPopFront(LTNode* phead);//头删
- LTNode* LTFind(LTNode* phead, LTDataType x);//查找
- void LTInsertBefore(LTNode* pos, LTDataType x);//在pos位置之前插入x
- void LTInsertAfter(LTNode* pos, LTDataType x);//在pos位置之后插入x
- void LTRemove(LTNode* pos);//删除pos位置的节点
- void LTCheckNode(LTNode* phead);//打印有效节点个数
-
-
-
- //List.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"List.h"
-
- //打印
- void LTPrint(LTNode* phead)
- {
- assert(phead);
- LTNode* cur = phead->next;
- printf("head->");
- while(cur != phead)
- {
- printf("%d->", cur->data);
- cur = cur->next;
- }
- printf("NULL\n");
- }
- //初始化(通过传参的方式)
- void LTInit(LTNode** pphead)
- {
- *pphead = (LTNode*)malloc(sizeof(LTNode));
- if(*pphead == NULL)
- {
- assert(0);
- return;
- }
- (*pphead)->data = -1;
- (*pphead)->next = *pphead;
- (*pphead)->prev = *pphead;
- }
- //初始化(通过返回值的方式)
- LTNode* LTInit2()
- {
- LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
- if(phead == NULL)
- {
- assert(0);
- return NULL;
- }
- phead->data = -1;
- phead->next = phead;
- phead->prev = phead;
- return phead;
- }
- //销毁
- void LTDestroy(LTNode** pphead)
- {
- LTNode* cur = (*pphead)->next;
- while(cur != *pphead)
- {
- LTNode* next = cur->next;
- free(cur);
- cur = next;
- }
- free(*pphead);
- *pphead = NULL;
- }
- //创建新节点
- LTNode* BuyLTNode(LTDataType x)
- {
- LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
- if(newnode == NULL)
- {
- assert(0);
- return NULL;
- }
- newnode->data = x;
- newnode->next = NULL;
- newnode->prev = NULL;
- return newnode;
- }
- //尾插
- void LTPushBack(LTNode* phead, LTDataType x)
- {
- assert(phead);
- LTNode* ptail = phead->prev;
- LTNode* newnode = BuyLTNode(x);
- newnode->next = phead;
- newnode->prev = ptail;
- ptail->next = newnode;
- phead->prev = newnode;
- }
- //尾删
- void LTPopBack(LTNode* phead)
- {
- assert(phead);
- LTNode* ptail = phead->prev;
- if(ptail == phead)
- {
- return;
- }
- LTNode* prev = ptail->prev;
- prev->next = phead;
- phead->prev = prev;
- free(ptail);
- }
- //头插
- void LTPushFront(LTNode* phead, LTDataType x)
- {
- assert(phead);
- LTNode* newnode = BuyLTNode(x);
- LTNode* next = phead->next;
- newnode->next = next;
- newnode->prev = phead;
- next->prev = newnode;
- phead->next = newnode;
-
- }
- //头删
- void LTPopFront(LTNode* phead)
- {
- assert(phead);
- LTNode* next = phead->next;
- if(next == phead)
- {
- return;
- }
- LTNode* nextnext = next->next;
- phead->next = nextnext;
- nextnext->prev = phead;
- free(next);
-
- }
- //查找
- LTNode* LTFind(LTNode* phead, LTDataType x)
- {
- assert(phead);
- LTNode* cur = phead->next;
- while(cur != phead)
- {
- if(cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;
-
- }
- //在pos位置之前插入x
- void LTInsertBefore(LTNode* pos, LTDataType x)
- {
- assert(pos);
- LTNode* prev = pos->prev;
- LTNode* newnode = BuyLTNode(x);
- newnode->next = pos;
- newnode->prev = prev;
- prev->next = newnode;
- pos->prev = newnode;
- }
- //在pos位置之后插入x
- void LTInsertAfter(LTNode* pos, LTDataType x)
- {
- assert(pos);
- LTNode* next = pos->next;
- LTNode* newnode = BuyLTNode(x);
- newnode->next = next;
- newnode->prev = pos;
- next->prev = newnode;
- pos->next = newnode;
- }
- //删除pos位置的节点
- void LTRemove(LTNode* pos)
- {
- assert(pos);
- LTNode* prev = pos->prev;
- LTNode* next = pos->next;
- prev->next = next;
- next->prev = prev;
- free(pos);
- }
- //打印有效节点个数
- void LTCheckNode(LTNode* phead)
- {
- assert(phead);
- LTNode* cur = phead->next;
- int count = 0;
- while(cur != phead)
- {
- count++;
- cur = cur->next;
- }
- printf("有效节点个数为:%d\n", count);
- }
-
-
-
- //test.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"List.h"
- void test01()
- {
- printf("test01()\n");
- LTNode* plist = NULL;
- LTInit(&plist);
- LTPushBack(plist, 1);
- LTPushBack(plist, 2);
- LTPushBack(plist, 3);
- LTPushBack(plist, 4);
- LTPrint(plist);
- LTPopBack(plist);
- LTPrint(plist);
- }
- void test02()
- {
- printf("test02()\n");
- LTNode* plist = LTInit2();
- LTPushFront(plist, 1);
- LTPushFront(plist, 2);
- LTPushFront(plist, 3);
- LTPushFront(plist, 4);
- LTPrint(plist);
- LTPopFront(plist);
- LTPrint(plist);
- LTNode* toFind = LTFind(plist, 2);
- if (toFind)
- {
- printf("找到了\n");
- }
- else
- {
- printf("没找到\n");
- }
- LTInsertAfter(toFind, 5);
- LTInsertBefore(toFind, 6);
- LTPrint(plist);
- LTRemove(toFind);
- LTPrint(plist);
- LTCheckNode(plist);
- }
- int main()
- {
- test01();
- test02();
- return 0;
- }
在双向链表的实现中,我们使用了以下数据类型的定义:
- typedef int LTDataType;
- typedef struct ListNode {
- LTDataType data;
- struct ListNode* prev;
- struct ListNode* next;
- } LTNode;
LTDataType
:链表节点存储的数据类型,这里使用整数作为示例。LTNode
:链表节点的结构体,包含数据域 data
,前驱指针 prev
和后继指针 next
。这三个部分组成了链表节点的基本结构。以下是双向链表实现中的各个函数的详细介绍:
1. 初始化链表
void LTInit(LTNode** pphead);
pphead
。next
和 prev
指向自身,形成循环链表。这个函数用于链表的初始化操作。2. 创建新节点
LTNode* BuyLTNode(LTDataType x);
x
。x
,前驱和后继指针为空。这个函数用于创建待插入的新节点。3. 插入节点到尾部
void LTPushBack(LTNode* phead, LTDataType x);
phead
,要插入的数据 x
。4. 删除尾部节点
void LTPopBack(LTNode* phead);
phead
。5. 插入节点到头部
void LTPushFront(LTNode* phead, LTDataType x);
phead
,要插入的数据 x
。6. 删除头部节点
void LTPopFront(LTNode* phead);
phead
。7. 查找节点
LTNode* LTFind(LTNode* phead, LTDataType x);
phead
,要查找的数据 x
。NULL
。x
的节点。这个函数用于查找链表中的元素。8. 在指定节点之前插入新节点
void LTInsertBefore(LTNode* pos, LTDataType x);
pos
,要插入的数据 x
。pos
之前插入一个新节点,更新前驱和后继节点的指针。这个函数用于在指定位置之前9. 在指定节点之后插入新节点
void LTInsertAfter(LTNode* pos, LTDataType x);
pos
,要插入的数据 x
。pos
之后插入一个新节点,更新前驱和后继节点的指针。这个函数用于在指定位置之后插入新元素。10. 删除指定节点
void LTRemove(LTNode* pos);
pos
。pos
,更新前驱和后继节点的指针,并释放内存。这个函数用于删除链表中的特定元素。在本篇博客中,我们详细介绍了C语言中的链表数据结构,包括单向链表和双向链表。我们从链表的基本概念和结构开始,然后分别讨论了不同类型的链表,包括单向不带头不循环链表和双向带头循环链表。通过学习链表的实现和操作,我们可以更好地理解数据结构和算法的基本原理,为编程和问题解决提供强大的工具。希望本篇博客能够帮助你更深入地理解链表,并在编程中应用它们。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。