在计算机编程领域,数据结构是非常重要的基础知识之一。顺序表和单链表作为常见的线性数据结构,在实际的软件开发中被广泛应用。本文将介绍了C语言中顺序表和单链表的实现方式,并对其进行了增删查改操作的详细讲解。通过阅读本文,读者将能够深入了解顺序表和单链表的底层实现原理,掌握它们的基本操作及相关的代码编写技巧。
当今计算机编程领域中最重要的数据结构之一是顺序表。顺序表是一种线性表,其中的元素在内存中按照顺序存储,可以通过下标来访问。本文将介绍一个简单的顺序表实现,并对其进行增删查改操作。
首先,我们需要定义一个结构体来表示顺序表,并声明一些相关的变量和函数。顺序表的结构体包含一个动态数组 a
,用于存储数据;size
表示当前顺序表中元素的个数;capacity
表示顺序表的容量,即当前数组 a
的大小。
typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;
int size;
int capacity;
}SeqList;
void SeqListInit(SeqList* ps);
void SeqListDestroy(SeqList* ps);
void SeqListPrint(SeqList* ps);
void SeqListPushBack(SeqList* ps, SLDateType x);
void SeqListPushFront(SeqList* ps, SLDateType x);
void SeqListPopFront(SeqList* ps);
void SeqListPopBack(SeqList* ps);
int SeqListFind(SeqList* ps, SLDateType x);
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
void SeqListErase(SeqList* ps, int pos);
在初始化顺序表之前,我们需要实现一个辅助函数 SLCheckCapacity
,用于检查顺序表的容量,并在需要时进行扩容。
void SLCheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDateType* tmp = realloc(ps->a, newCapacity * sizeof(SLDateType));
if (tmp == NULL)
{
printf("realloc fail!\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
接下来,我们实现顺序表的初始化函数 SeqListInit
和销毁函数 SeqListDestroy
。
void SeqListInit(SeqList* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void SeqListDestroy(SeqList* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
}
顺序表的增删查改操作是对数据进行管理的核心部分。我们实现了以下函数来完成这些操作:
SeqListPrint
:打印顺序表中的所有元素。SeqListPushBack
:在顺序表的末尾插入一个元素。SeqListPushFront
:在顺序表的头部插入一个元素。SeqListPopFront
:从顺序表的头部删除一个元素。SeqListPopBack
:从顺序表的末尾删除一个元素。SeqListFind
:在顺序表中查找指定元素,并返回其索引位置。SeqListInsert
:在顺序表的指定位置插入一个元素。SeqListErase
:从顺序表的指定位置删除一个元素。void SeqListPrint(SeqList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListPushBack(SeqList* ps, SLDateType x)
{
SeqListInsert(ps, ps->size, x);
}
void SeqListPushFront(SeqList* ps, SLDateType x)
{
SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SeqList* ps)
{
SeqListErase(ps, 0);
}
void SeqListPopBack(SeqList* ps)
{
SeqListErase(ps, ps->size - 1);
}
int SeqListFind(SeqList* ps, SLDateType x)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x) return i;
}
return -1;
}
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
memmove(ps->a + pos + 1, ps->a + pos, (ps->size - pos) * sizeof(SLDateType));
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SeqList* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
memmove(ps->a + pos, ps->a + pos + 1, (ps->size - pos - 1) * sizeof(SLDateType));
ps->size--;
}
为了验证顺序表的正确性,我们编写了一个简单的测试代码。在测试代码中,我们创建了一个顺序表,并进行了一系列的增删查改操作。
int main()
{
SeqList sl;
SeqListInit(&sl);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPushBack(&sl, 5);
SeqListPushBack(&sl, 6);
SeqListPushBack(&sl, 7);
SeqListPushBack(&sl, 8);
printf("PushBack 测试(尾插8个数):\n");
SeqListPrint(&sl);
SeqListPushFront(&sl, 9);
SeqListPushFront(&sl, 9);
SeqListPushFront(&sl, 9);
printf("PushFront 测试(头插三个9):\n");
SeqListPrint(&sl);
SeqListPopFront(&sl);
SeqListPopFront(&sl);
SeqListPopFront(&sl);
printf("PopFront 测试(头删三个数):\n");
SeqListPrint(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
printf("PopBack 测试(尾删两个数):\n");
SeqListPrint(&sl);
printf("Find 测试(找5和8):\n");
int pos = SeqListFind(&sl, 5);
if (pos != -1) printf("5 在下标为%d的位置\n", pos);
else printf("%d没找到!\n", 5);
pos = SeqListFind(&sl, 8);
if (pos != -1) printf("8 在下标为%d的位置\n", pos);
else printf("%d 没找到!\n", 8);
SeqListDestroy(&sl);
return 0;
}
//SeqList.h
#include
#include
#include
#include
typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;
int size;
int capacity;
}SeqList;
void SLCheckCapacity(SeqList* ps);
// 对数据的管理:增删查改
void SeqListInit(SeqList* ps);
void SeqListDestroy(SeqList* ps);
void SeqListPrint(SeqList* ps);
void SeqListPushBack(SeqList* ps, SLDateType x);
void SeqListPushFront(SeqList* ps, SLDateType x);
void SeqListPopFront(SeqList* ps);
void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
//Seqlist.c
#include "SeqList.h"
void SLCheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDateType* tmp = realloc(ps->a, newCapacity * sizeof(SLDateType));
if (tmp == NULL)
{
printf("realloc fail!\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
// 对数据的管理:增删查改
void SeqListInit(SeqList* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void SeqListDestroy(SeqList* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
}
void SeqListPrint(SeqList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListPushBack(SeqList* ps, SLDateType x)
{
SeqListInsert(ps, ps->size, x);
}
void SeqListPushFront(SeqList* ps, SLDateType x)
{
SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SeqList* ps)
{
SeqListErase(ps, 0);
}
void SeqListPopBack(SeqList* ps)
{
SeqListErase(ps, ps->size);
}
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x) return i;
}
return -1;
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
memmove(ps->a + pos + 1, ps->a + pos, (ps->size - pos) * sizeof(SLDateType));
ps->a[pos] = x;
ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
memmove(ps->a + pos, ps->a + pos + 1, (ps->size - pos) * sizeof(SLDateType));
ps->size--;
}
//test.c
#include"SeqList.h"
int main()
{
SeqList sl;
SeqListInit(&sl);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPushBack(&sl, 5);
SeqListPushBack(&sl, 6);
SeqListPushBack(&sl, 7);
SeqListPushBack(&sl, 8);
printf("PushBack 测试(尾插8个数):\n");
SeqListPrint(&sl);
SeqListPushFront(&sl, 9);
SeqListPushFront(&sl, 9);
SeqListPushFront(&sl, 9);
printf("PushFront 测试(头插三个9):\n");
SeqListPrint(&sl);
SeqListPopFront(&sl);
SeqListPopFront(&sl);
SeqListPopFront(&sl);
printf("PopFront 测试(头删三个数):\n");
SeqListPrint(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
printf("PopBack 测试(尾删两个数):\n");
SeqListPrint(&sl);
printf("Find 测试(找5和8):\n");
int pos = SeqListFind(&sl, 5);
if (pos != -1) printf("5 在下标为%d的位置\n", pos);
else printf("%d没找到!\n", 5);
pos = SeqListFind(&sl, 8);
if (pos != -1) printf("8 在下标为%d的位置\n", pos);
else printf("%d 没找到!\n", 8);
SeqListDestroy(&sl);
return 0;
}
通过上述代码,我们实现了一个简单的顺序表,并对其进行了增删查改操作的测试。顺序表是一种非常常用的数据结构,能够高效地管理线性数据。
单链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和指向下一个节点的指针。我将介绍如何使用C语言实现单链表的增删查改操作。
首先,我们需要定义单链表的数据结构。在给出的代码中,使用了SListNode
结构体表示每个节点,其中包含一个整型数据 data
和一个指向下一个节点的指针 next
。另外,定义了一个类型别名 SLTDateType
来表示节点数据的类型。
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
} SListNode;
为了创建一个新的节点,我们需要动态申请内存空间,并将数据和指针赋值。在给出的代码中,定义了一个 BuySListNode
函数来完成这个任务。
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("malloc fail!");
exit(-1);
}
newNode->next = NULL;
newNode->data = x;
return newNode;
}
尾插操作是将新节点插入到链表末尾。给出的代码中,定义了 SListPushBack
函数来实现尾插操作。它首先创建一个新节点,并找到链表的尾部,然后将新节点连接到尾节点的后面。
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newNode = BuySListNode(x);
SListNode* tail = *pplist;
if (tail == NULL) {
*pplist = newNode;
return;
}
else {
while (tail->next) {
tail = tail->next;
}
}
tail->next = newNode;
}
头插操作是将新节点插入到链表的头部。给出的代码中,定义了 SListPushFront
函数来实现头插操作。它首先创建一个新节点,并将新节点的指针指向原链表的头节点,然后更新链表的头节点为新节点。
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newNode = BuySListNode(x);
newNode->next = *pplist;
*pplist = newNode;
}
尾删操作是删除链表的最后一个节点。给出的代码中,定义了 SListPopBack
函数来实现尾删操作。它首先找到链表的倒数第二个节点,然后释放最后一个节点的内存,并将倒数第二个节点的 next
指针置空。
void SListPopBack(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode* tail = *pplist;
SListNode* prev = NULL;
if (tail->next == NULL) {
*pplist = NULL;
}
else {
while (tail->next) {
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
头删操作是删除链表的第一个节点。给出的代码中,定义了 SListPopFront
函数来实现头删操作。它首先将链表的头节点保存起来,然后更新链表的头节点为原头节点的下一个节点,并释放保存的头节点的内存。
void SListPopFront(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode* front = *pplist;
*pplist = (*pplist)->next;
free(front);
}
查找操作是在链表中寻找特定的数据。给出的代码中,定义了 SListFind
函数来实现查找操作。它遍历链表,直到找到数据等于目标值的节点,然后返回该节点的指针。
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode* cur = plist;
while (cur->next && cur->data != x) {
cur = cur->next;
}
if (cur->next) {
return cur;
}
return NULL;
}
在给出的代码中,还定义了 SListInsertAfter、SListEraseAfter、SListInsert
和 SListErase
函数来实现在指定位置之后插入节点、删除指定位置之后的节点、在指定位置之前插入节点以及删除指定位置的节点。
//具体的实现细节请参考给出的代码。
为了释放链表占用的内存空间,给出的代码中,定义了 SListDestroy
函数来销毁链表。它遍历链表的每个节点,依次释放内存,并将链表的头节点置为NULL
。
void SListDestroy(SListNode** pplist)
{
assert(pplist);
SListNode* cur = *pplist;
while (cur)
{
SListNode* next = cur->next;
free(cur);
cur = next;
}
*pplist = NULL;
}
给出的代码还包括了一个 main
函数来测试上述函数的功能。它首先创建一个空链表,然后进行了一系列的增删查改操作,并输出每一步操作后的链表结果。
int main()
{
SListNode* plist = NULL;
// 测试代码
return 0;
}
以上就是关于C语言实现单链表的增删查改的代码解析和使用方法。
//slist.h
#include
#include
#include
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 在pos的前面插入
void SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x);
// 删除pos位置
void SListErase(SListNode** pplist, SListNode* pos);
//销毁链表
void SListDestroy(SListNode** pplist);
//slist.c
#include "slist.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("malloc fail!");
exit(-1);
}
newNode->next = NULL;
newNode->data = x;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newNode = BuySListNode(x);
SListNode* tail = *pplist;
//找尾
if (tail == NULL) {
*pplist = newNode;
return;
}
else {
while (tail->next) {
tail = tail->next;
}
}
tail->next = newNode;
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newNode = BuySListNode(x);
newNode->next = *pplist;
*pplist = newNode;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode* tail = *pplist;
SListNode* prev = NULL; //尾的前一个
//找尾
if (tail->next == NULL) {
*pplist = NULL;
}
else
{
while (tail->next) {
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode* front = *pplist;
*pplist = (*pplist)->next;
free (front);
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode* cur = plist;
while (cur->next && cur->data != x) {
cur = cur->next;
}
if (cur->next) {
return cur;
}
return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?如果在pos之前经行插入需要遍历一遍前面的链表,效率低
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* newNode = BuySListNode(x);
SListNode* tmp = pos->next;
pos->next = newNode;
newNode->next = tmp;
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?同上
void SListEraseAfter(SListNode* pos)
{
assert(pos);
SListNode* del = pos->next;
pos->next = pos->next->next;
free(del);
}
// 在pos的前面插入
void SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
assert(pplist);
assert(pos);
if (pos == *pplist)
{
SListPushFront(pplist, x);
return;
}
SListNode* newNode = BuySListNode(x);
SListNode* prev = *pplist;
while (prev->next != pos)
{
prev = prev->next;
}
newNode->next = pos;
prev->next = newNode;
}
// 删除pos位置
void SListErase(SListNode** pplist, SListNode* pos)
{
assert(pplist);
assert(*pplist);
assert(pos);
SListNode* prev = *pplist;
SListNode* del = pos;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(del);
}
//销毁链表
void SListDestroy(SListNode** pplist)
{
assert(pplist);
SListNode* cur = *pplist;
while (cur)
{
SListNode* next = cur->next;
free(cur);
cur = next;
}
*pplist = NULL;
}
//test.c
#include"slist.h"
int main()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushBack(&plist, 5);
SListPushBack(&plist, 6);
SListPushBack(&plist, 7);
SListPushBack(&plist, 8);
printf("PushBack 测试(尾插8个数):\n");
SListPrint(plist);
SListPushFront(&plist, 0);
SListPushFront(&plist, 0);
SListPushFront(&plist, 0);
printf("PushFront 测试(头插三个0):\n");
SListPrint(plist);
SListPopBack(&plist);
SListPopBack(&plist);
printf("PopBack 测试(尾删两个数):\n");
SListPrint(plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
printf("PopFront 测试(头删三个数):\n");
SListPrint(plist);
printf("Find 测试(找4):\n");
SListPrint(SListFind(plist, 4));
printf("InsertAfter 测试(找4后插):\n");
SListInsertAfter(SListFind(plist, 4), 999);
SListPrint(plist);
printf("EraseAfter 测试(找4后删):\n");
SListEraseAfter(SListFind(plist, 4));
SListPrint(plist);
printf("Insert 测试(找4前插):\n");
SListInsert(&plist, SListFind(plist, 4), 999);
SListPrint(plist);
printf("Erase 测试(找4删4):\n");
SListErase(&plist, SListFind(plist, 4));
SListPrint(plist);
SListDestroy(&plist);
printf("Destroy!\n");
SListPrint(plist);
return 0;
}
本文首先介绍了顺序表的定义与初始化,包括动态数组的使用、容量检查和扩容操作等内容。然后详细讲解了顺序表的增删查改操作,并给出了相应的代码示例。接着,文章转向单链表的实现,包括节点的定义、动态申请节点、尾插和头插操作、尾删和头删操作、查找、插入和删除等操作的具体实现。最后,通过测试代码验证了顺序表和单链表的功能正常运行。
通过本文的学习,读者可以加深对顺序表和单链表的理解,掌握这两种数据结构的增删查改的具体实现方法。同时,对于初学者来说,也能够更好地理解数据结构和算法在实际编程中的应用,为日后的软件开发打下良好的基础。
希望本文能为您对顺序表和单链表的理解和应用提供帮助,欢迎大家在实践中不断探索和应用这些知识,提升自己的编程技能。