无头结点单向非循环链表的基本操作(c语言实现)

2024-04-14 15:28

本文主要是介绍无头结点单向非循环链表的基本操作(c语言实现),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

结点

在C语言中,单链表的结点通常是一个结构体,它包含一个数据域用于存储数据,以及一个指针域用于指向链表中的下一个结点。

下面是一个简单的单链表结点的定义:

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

在这段代码中,首先定义了一个新的数据类型别名 SLTDataType,它实际上就是 int 类型。这样做的好处是,如果将来需要改变链表中存储的数据类型,只需要修改 SLTDataType 的定义即可,而不需要修改链表中所有使用这种数据类型的地方。

接着定义了一个结构体 SListNode,这个结构体代表单链表中的一个结点。

结构体中包含两个成员:

  1. data:类型为 SLTDataType(也就是 int),用于存储该结点的数据。
  2. next:类型为指向 SListNode 类型的指针,用于指向链表中的下一个结点。

最后,为这个结构体类型定义了一个别名 SLTNode。在后续的代码中,可以使用 SLTNode 来代替 struct SListNode,使得代码更加简洁。

这样定义单链表的结点之后,就可以方便地创建和操作链表了。

头指针

头指针是一个指针,它指向链表或链表中的第一个节点。

在链表中,每个节点包含一个数据项和一个指向下一个节点的指针。

通过头指针,我们可以访问链表中的第一个节点,然后可以通过节点的指针访问下一个节点,以此类推。使用头指针,我们可以对链表进行插入、删除、查找等操作。

头指针在确定线性表中第一个元素对应的存储位置时非常有用,它一般用于处理数组、链表、队列等数据结构。在链式存储中,只要不是循环链表,就一定存在头指针。单链表可以用头指针的名字来命名,头指针指向第一个节点

链表的基本操作

一,创建新结点

SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}

这段代码定义了一个名为 BuySLTNode 的函数,用于创建一个新的单链表节点,并将新节点的数据部分初始化为传入的参数 x,同时将其 next 指针初始化为 NULL

首先,函数通过调用 malloc 函数为新的 SLTNode 分配内存空间。sizeof(SLTNode) 用于计算 SLTNode 类型所需的内存大小,确保分配足够的空间来存储新节点。

接下来,函数检查 malloc 是否成功分配了内存。如果 malloc 返回 NULL,表示内存分配失败(可能是由于内存不足),函数会调用 perror 函数输出错误信息 "malloc fail",并返回 NULL,表示创建新节点失败。

如果内存分配成功,函数将新节点的 data 字段设置为传入的参数 x,即节点所要存储的数据。同时,将新节点的 next 指针初始化为 NULL,表示该节点当前是链表的最后一个节点,没有指向下一个节点的指针。

最后,函数返回新创建的节点的指针,供调用者使用。调用者可以通过这个指针访问和操作新创建的节点。

二,尾部插入一个元素

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

这段代码定义了一个函数 SLTPushBack,用于在单链表的尾部添加一个新的节点。函数接收两个参数:一个指向头节点指针的指针 pphead 和一个要添加到链表中的数据类型 x。

下面是该函数的详细解释:

断言检查:

 assert(pphead); 

使用 assert 来确保 pphead 不为 NULL。

如果 pphead 是 NULL,程序会在调试模式下终止,并输出错误信息。这是为了防止对空指针进行解引用,从而导致程序崩溃。

创建新节点: 

SLTNode* newnode = BuySLTNode(x); 

调用之前定义的 BuySLTNode 函数来创建一个新的节点,并将数据 x 赋给这个新节点的 data 字段。BuySLTNode 会负责分配内存并返回新节点的指针。

处理链表为空的情况:

 if (*pphead == NULL)  {  *pphead = newnode;  } 

如果链表为空(即头节点指针 *pphead 为 NULL),则直接将新节点设置为头节点。

找到链表尾部并添加新节点:

 else  {  // 找尾  SLTNode* tail = *pphead;  while (tail->next != NULL)  {  tail = tail->next;  }  tail->next = newnode;  } 

如果链表不为空,则需要遍历链表找到尾部的节点。

使用 tail 指针从头节点开始,沿着 next 指针逐步向后移动,直到找到 next 指针为 NULL 的节点,即尾节点。

然后,将尾节点的 next 指针指向新创建的节点 newnode,从而将新节点添加到链表的尾部。

错误处理:
如果 BuySLTNode 函数因为内存分配失败而返回 NULL,SLTPushBack 函数并没有包含直接处理这种情况的代码。在实际应用中,你可能希望在 BuySLTNode 返回 NULL 时进行错误处理,比如打印错误信息或者返回一个错误码。

这个函数实现了在单链表尾部添加节点的功能,无论链表是空的还是有元素的,都能正确工作。

需要注意的是,它假设 pphead 指向的是头节点的指针,而不是头节点本身。

这意味着,如果链表为空,*pphead 将会被设置为新节点的地址;如果链表非空,则不会改变头节点的地址,只是链表的长度会增加。

三,头部插入一个元素

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}

这段代码定义了一个函数 SLTPushFront,用于在单链表的头部插入一个新的节点。函数接收两个参数:一个指向头节点指针的指针 pphead 和一个要插入到链表中的数据类型 x。

下面是该函数的详细解释:

断言检查:

assert(pphead); 

使用 assert 来确保 pphead 不为 NULL。这确保了传递给函数的参数是有效的,防止了因为无效的指针而导致的潜在问题。

创建新节点:

 SLTNode* newnode = BuySLTNode(x); 

调用之前定义的 BuySLTNode 函数来创建一个新的节点,并将数据 x 赋给这个新节点的 data 字段。

更新新节点的 next 指针:

 newnode->next = *pphead; 

将新节点的 next 指针指向当前的头节点。这实际上是将新节点插入到链表的开始位置,因为新节点现在指向了原来链表的第一个节点(如果有的话)。

更新头节点指针:

 *pphead = newnode; 

将头节点指针 *pphead 更新为新节点的地址。这意味着现在新节点成为了链表的头节点,而原来的头节点(如果有的话)则成为了新节点的下一个节点。

这个函数实现了在单链表头部插入节点的功能。无论链表是空的还是有元素的,都能正确工作。在链表为空的情况下,新节点将成为唯一的节点,并且头节点指针 *pphead 将指向这个新节点。在链表非空的情况下,新节点将被插入到原来头节点的前面,成为新的头节点。

需要注意的是,SLTPushFront 函数假设 pphead 指向的是头节点的指针的地址,因此它可以直接修改头节点指针的值。如果 pphead 指向的是头节点本身而不是其地址,那么函数将无法修改链表的头节点,因为传递的是头节点值的一个副本。在这个实现中,通过传递指针的指针,我们确保了可以修改头节点指针本身。

四,删除最后一个结点

void SLTPopBack(SLTNode** pphead)
{assert(pphead);assert(*pphead);// 1、只有一个节点// 2、多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}	
}

这段代码定义了一个函数 SLTPopBack,用于删除单链表的尾部节点,并释放其占用的内存。函数接收一个指向头节点指针的指针 pphead 作为参数。

下面是该函数的详细解释:

断言检查:

 assert(pphead);  assert(*pphead); 

使用 assert 来确保 pphead 和 *pphead 都不为 NULL。这确保了传递给函数的参数是有效的,并且链表至少有一个节点。

处理只有一个节点的情况:

 if ((*pphead)->next == NULL)  {  free(*pphead);  *pphead = NULL;  } 

如果链表中只有一个节点(即头节点的 next 指针为 NULL),则直接释放这个节点的内存,并将头节点指针 *pphead 设置为 NULL,表示链表现在为空。

处理有多个节点的情况:

 else  {  SLTNode* tail = *pphead;  while (tail->next->next != NULL)  {  tail = tail->next;  }  free(tail->next);  tail->next = NULL;  } 

如果链表中有多个节点,我们需要找到尾节点的前一个节点(我们称之为 tail),然后释放尾节点的内存,并将 tail 节点的 next 指针设置为 NULL,以断开链表连接。

在循环中,tail 指针逐步移动到尾节点的前一个位置。循环的条件是 tail->next->next != NULL,这意味着 tail 指针当前指向的节点的下一个节点(tail->next)不是尾节点,因为尾节点的 next 指针为 NULL。当循环结束时,tail 指向的就是尾节点的前一个节点。

然后,使用 free(tail->next) 释放尾节点的内存,并将 tail->next 设置为 NULL,完成尾节点的删除操作。

需要注意的是,这个 SLTPopBack 函数假设链表至少有一个节点。如果链表为空(即 *pphead 为 NULL),则函数的行为是未定义的,因为会尝试解引用一个空指针。在实际应用中,你可能希望在函数开始处添加一个检查,以确保链表不为空,或者至少确保 pphead 不为 NULL。此外,如果链表只有一个节点,该函数也能正确工作,因为第一个 if 语句会处理这种情况。

另外,这个函数的实现方式假设了链表至少有两个节点才调用 SLTPopBack。如果链表只有一个节点,应该由调用者确保不会调用这个函数,或者应该在函数内部进行更严格的检查以避免潜在的错误。

五,删除第一个元素

void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}

这段代码定义了一个函数 SLTPopFront,用于删除单链表的头部节点,并释放其占用的内存。函数接收一个指向头节点指针的指针 pphead 作为参数。

下面是该函数的详细解释:

断言检查:

 assert(pphead);  assert(*pphead); 

使用 assert 来确保 pphead 和 *pphead 都不为 NULL。这确保了传递给函数的参数是有效的,并且链表至少有一个节点。

删除头部节点:

SLTNode* first = *pphead; 

将头节点指针 *pphead 的值赋给一个新的指针变量 first。现在 first 指向链表原来的头节点。

更新头节点指针:

*pphead = first->next; 

将头节点指针 *pphead 更新为原来头节点的下一个节点,这样原来的头节点就不再是链表的头节点了。

释放头部节点的内存:

 free(first); 

释放 first 指针指向的头节点的内存。由于 first 现在不再被链表使用,它的内存可以被操作系统回收。

重置 first 指针:

 first = NULL; 

将 first 指针设置为 NULL,以避免悬挂指针。这是一个好的编程习惯,因为它可以防止后续的代码错误地访问已经被释放的内存。

这个函数实现了在单链表头部删除节点的功能。无论链表是只有一个节点还是有多个节点,它都能正确工作。在删除头部节点后,链表的头节点将变为原来的第二个节点(如果存在的话),或者链表将变为空(如果原来只有一个节点)。

需要注意的是,这个函数假设链表至少有一个节点。如果链表为(即 *pphead 为 NULL),则函数行为是未定义的,因为会尝试解引用一个空指针。

在实际应用中,你应该在调用 SLTPopFront 之前确保链表不为空,或者在函数内部添加额外的检查来处理空链表的情况。

六,查找特定值的结点

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

这段代码定义了一个函数 SLTFind,用于在单链表中查找一个具有特定数据值的节点。函数接收两个参数:一个指向链表头节点的指针 phead 和一个要查找的数据值 x。
下面是该函数的详细解释:

初始化当前节点指针:

SLTNode* cur = phead; 

将 cur 指针初始化为链表的头节点 phead。这个指针将用于遍历链表。

遍历链表:

 while (cur) 

使用一个 while 循环遍历链表,直到 cur 指针为 NULL(即链表结束)。

查找节点:

if (cur->data == x)  {  return cur;  } 

在循环内部,检查当前节点 cur 的数据值是否等于要查找的数据值 x。如果相等,说明找到了匹配的节点,函数返回该节点的地址。

移动到下一个节点:

 cur = cur->next; 

如果当前节点的数据值不等于 x,则将 cur 指针移动到下一个节点,继续查找。

返回 NULL:

 return NULL; 

如果循环结束时仍然没有找到匹配的节点(即 cur 为 NULL),则函数返回 NULL,表示未找到。

这个函数通过遍历链表来查找具有特定数据值的节点。

如果找到匹配的节点,则返回该节点的地址;否则返回 NULL。

这个查找过程的时间复杂度是 O(n),其中 n 是链表的长度,因为在最坏的情况下,可能需要遍历整个链表。

七,在pos位置前插入一个元素

// pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pos);assert(pphead);if (pos == *pphead){SLTPushFront(pphead, x);}else{// 找到pos的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}

这段代码定义了一个函数 SLTInsert,用于在单链表的指定位置 pos 之前插入一个新的节点,新节点的数据值为 x。

函数接收三个参数:一个指向头节点指针的指针 pphead,一个指向链表中某个节点的指针 pos,以及要插入的新节点的数据值 x。

下面是该函数的详细解释:

断言检查:

 assert(pos);  assert(pphead); 

使用 assert 来确保 pos 和 pphead 都不为 NULL。这确保了传递给函数的参数是有效的。

检查插入位置:

 if (pos == *pphead)  {  SLTPushFront(pphead, x);  } 

如果 pos 指向的节点是头节点,那么实际上我们需要在链表头部插入新节点。这里调用了 SLTPushFront 函数来完成这一操作。

找到 pos 的前一个节点:

 SLTNode* prev = *pphead;  while (prev->next != pos)  {  prev = prev->next;  } 

如果 pos 不是头节点,那么我们需要找到 pos 的前一个节点 prev。这个循环从头节点开始,逐个遍历节点,直到找到 prev 节点的 next 指针指向 pos。

创建新节点并插入:

 SLTNode* newnode = BuySLTNode(x);  prev->next = newnode;  newnode->next = pos; 

使用 BuySLTNode 函数(这个函数在代码片段中没有给出,但我们可以假设它用于创建并初始化一个新节点)来创建一个包含数据值 x 的新节点 newnode。

然后,更新 prev 节点的 next 指针,使其指向新节点 newnode,并将 newnode 的 next 指针指向原来的 pos 节点。

这样,新节点就被插入到了 pos 节点之前。

这个函数实现了在单链表指定位置插入新节点的功能。它首先检查插入位置是否为链表头部,如果是,则调用专门的头部插入函数。如果不是头部,则找到插入位置的前一个节点,并在该位置插入新节点。

需要注意的是,这个函数假设 pos 指向的节点确实存在于链表中,并且 pos 不是 NULL。如果 pos 不指向链表中的有效节点,则函数的行为是未定义的。

在实际应用中,你应该在调用 SLTInsert 之前确保 pos 指向的节点是链表中的一个有效节点。

八,删除pos位置的元素

// pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);//assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{// 找到pos的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);//pos = NULL;}
}

这段代码定义了一个函数 SLTErase,用于在单链表中删除指定位置的节点 pos。

函数接收两个参数:一个指向头节点指针的指针 pphead 和一个指向要删除节点的指针 pos。

下面是该函数的详细解释:

断言检查: 

assert(pphead);  assert(pos); 

使用 assert 来确保 pphead 和 pos 都不为 NULL。这确保了传递给函数的参数是有效的。

检查删除位置:
 

if (*pphead == pos)  {  SLTPopFront(pphead);  } 

如果 pos 指向的节点是头节点,那么实际上我们需要删除链表头部节点。这里调用了 SLTPopFront 函数来完成这一操作。

找到 pos 的前一个节点:

 SLTNode* prev = *pphead;  while (prev->next != pos)  {  prev = prev->next;  } 

如果 pos 不是头节点,那么我们需要找到 pos 的前一个节点 prev。这个循环从头节点开始,逐个遍历节点,直到找到 prev 节点的 next 指针指向 pos。

删除节点:

 prev->next = pos->next;  free(pos); 

更新 prev 节点的 next 指针,使其跳过 pos 节点,直接指向 pos 的下一个节点。这样,pos 节点就被从链表中移除了。接着,调用 free 函数释放 pos 节点占用的内存。

注释掉的代码:

 pos = NULL; 

这行代码被注释掉了,通常这样的代码用于将 pos 指针设置为 NULL,以避免悬挂指针。但在这种情况下,由于 pos 是作为参数传递进来的,将 pos 设置为 NULL 并不会影响到调用者处的 pos 指针。因此,这行代码在这里是没有必要的,也不会影响到函数的正确性。

这个函数实现了在单链表中删除指定位置节点的功能。它首先检查要删除的节点是否为链表头部,如果是,则调用专门的头部删除函数。如果不是头部,则找到要删除节点的前一个节点,并更新其 next 指针来跳过要删除的节点。最后,释放要删除节点的内存。

需要注意的是,这个函数假设 pos 指向的节点确实存在于链表中,并且 pos 不是 NULL。如果 pos 不指向链表中的有效节点,则函数的行为是未定义的。

在实际应用中,你应该在调用 SLTErase 之前确保 pos 指向的节点是链表中的一个有效节点。

九,在pos位置后插入一个元素

// pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}

这段代码定义了一个函数 SLTInsertAfter,用于在单链表中指定节点 pos 的后面插入一个新的节点,新节点的数据值为 x。函数接收两个参数:一个指向链表中某个节点的指针 pos 和要插入的新节点的数据值 x。

下面是该函数的详细解释:

断言检查:

 assert(pos); 

使用 assert 来确保 pos 不为 NULL。这确保了传递给函数的参数是有效的。

创建新节点:

 SLTNode* newnode = BuySLTNode(x); 

使用 BuySLTNode 函数(这个函数在代码片段中没有给出,但我们可以假设它用于创建并初始化一个新节点)来创建一个包含数据值 x 的新节点 newnode。

插入新节点:

newnode->next = pos->next;  pos->next = newnode; 

首先,将新节点 newnode 的 next 指针设置为 pos 节点的 next 指针所指向的节点。这样,新节点就指向了原来 pos 节点后面的节点。然后,将 pos 节点的 next 指针更新为指向新节点 newnode。这样,新节点就被插入到了 pos 节点的后面。

这个函数实现了在单链表中指定节点后面插入新节点的功能。它假设 pos 指向的节点确实存在于链表中,并且 pos 不是 NULL。如果 pos 不指向链表中的有效节点,则函数的行为是未定义的。在实际应用中,你应该在调用 SLTInsertAfter 之前确保 pos 指向的节点是链表中的一个有效节点。

这个函数没有处理头节点插入的特殊情况,因为从函数参数和逻辑上看,它假设 pos 是链表中的一个非头节点。如果需要处理头节点插入的情况,你需要修改函数逻辑或者在调用这个函数之前先检查 pos 是否为头节点,并相应地处理。

十,在pos位置后面删除一个元素

// pos位置后面删除
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

这段代码定义了一个函数 SLTEraseAfter,用于在单链表中删除指定节点 pos 后面的节点。函数接收一个参数:一个指向链表中某个节点的指针 pos。

下面是该函数的详细解释:

断言检查:

 assert(pos);  assert(pos->next); 

使用 assert 来确保 pos 不为 NULL,并且 pos 指向的节点后面确实有另一个节点(即 pos->next 不为 NULL)。这确保了传递给函数的参数是有效的,并且不会在尝试访问 pos->next 时导致空指针解引用错误。

删除节点:

 SLTNode* del = pos->next;  pos->next = del->next;  free(del);  del = NULL; 

首先,将 pos 节点后面的节点(即 pos->next)的指针赋值给 del。然后,更新 pos 节点的 next 指针,使其跳过 del 节点,直接指向 del 节点的下一个节点。

这样,del 节点就被从链表中移除了。接着,调用 free 函数释放 del 节点占用的内存。

需要注意的是,虽然 del = NULL; 这行代码在函数内部将 del 设置为 NULL,但这并不会影响到函数外部的任何指针。

因为 del 只是一个局部变量,它的改变不会影响到函数外部的变量。这行代码主要是为了在函数内部避免使用已经被释放的 del 指针,从而防止潜在的错误。

这个函数实现了在单链表中删除指定节点后面节点的功能。它假设 pos 指向的节点确实存在于链表中,并且 pos 后面确实有另一个节点。如果 pos 不指向链表中的有效节点,或者 pos 是链表的最后一个节点,则函数的行为是未定义的。在实际应用中,你应该在调用 SLTEraseAfter 之前确保 pos 指向的节点是链表中的一个有效节点,并且它不是链表的最后一个节点。

十一,打印链表

void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//while (cur->next != NULL)//while(cur != NULL)while (cur){printf("%d->", cur->data);cur = cur->next;//cur++;}printf("NULL\n");
}

完整操作

void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//while (cur->next != NULL)//while(cur != NULL)while (cur){printf("%d->", cur->data);cur = cur->next;//cur++;}printf("NULL\n");
}SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{// 暴力检查assert(pphead);assert(*pphead);// 温柔的检查//if (*pphead == NULL)//	return;// 1、只有一个节点// 2、多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{// 找尾//SLTNode* prev = NULL;//SLTNode* tail = *pphead;//while (tail->next != NULL)//{//	prev = tail;//	tail = tail->next;//}//free(tail);//tail = NULL;//prev->next = NULL;SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}	
}void SLTPopFront(SLTNode** pphead)
{// 暴力检查assert(pphead);assert(*pphead);// 温柔的检查//if (*pphead == NULL)//	return;SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}// pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pos);assert(pphead);if (pos == *pphead){SLTPushFront(pphead, x);}else{// 找到pos的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}// pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);//assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{// 找到pos的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);//pos = NULL;}
}// pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}// pos位置后面删除
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//SLTNode* del = pos->next;//pos->next = pos->next->next;//free(del);//del = NULL;SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

这篇关于无头结点单向非循环链表的基本操作(c语言实现)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/903349

相关文章

Redis分片集群的实现

《Redis分片集群的实现》Redis分片集群是一种将Redis数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性,本文主要介绍了Redis分片集群的实现,具有一定的参考价值,感兴趣的可以了解一... 目录1. Redis Cluster的核心概念哈希槽(Hash Slots)主从复制与故障转移2.

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

Docker镜像修改hosts及dockerfile修改hosts文件的实现方式

《Docker镜像修改hosts及dockerfile修改hosts文件的实现方式》:本文主要介绍Docker镜像修改hosts及dockerfile修改hosts文件的实现方式,具有很好的参考价... 目录docker镜像修改hosts及dockerfile修改hosts文件准备 dockerfile 文

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La