无头结点单向非循环链表的基本操作(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

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

csu1329(双向链表)

题意:给n个盒子,编号为1到n,四个操作:1、将x盒子移到y的左边;2、将x盒子移到y的右边;3、交换x和y盒子的位置;4、将所有的盒子反过来放。 思路分析:用双向链表解决。每个操作的时间复杂度为O(1),用数组来模拟链表,下面的代码是参考刘老师的标程写的。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验