我正在使用链表进行练习,其中我需要根据条件删除一些节点。条件是''删除存储的数据小于或等于'avg''的节点。
我应该考虑三种情况: - 删除头节点 - 删除中间的节点 - 删除最后一个节点
我使用了三个指针,但这似乎不起作用。我究竟做错了什么?我不小心错过了一些指针吗?提前谢谢!
void checkAvg(int avg, struct list* head_node){
struct list* prev;
struct list* curr;
struct list* next;
prev = head;
curr = prev->link;
next = curr->link;
while (curr!=NULL) {
if (prev->data <= avg) {
head = curr;
}
if (curr->data <= avg) {
prev->link = curr->link;
}
if (next->data <= avg) {
curr->link = next->link;
}
prev = prev->link;
curr = curr->link;
next = next->link;
}
}
编辑: 对不起,正如你告诉我的那样,我并不具体。该计划必须这样做: 1)阅读链表 2)生成我读取的值的平均值(值的总和/值) 3)从列表中删除具有&lt; =平均值的数据的节点 释放那些节点的内存。 关于free()部分,我遇到了一些困难,所以我想我首先要学习如何正确使用指针。你可能已经注意到了,我并不擅长这一点,我是首发。
感谢所有回复的人,我现在正在查看回复
答案 0 :(得分:5)
如果您的链接列表不有一些标记头节点(我强烈建议它没有,因为NULL
是一个非常好的值,表明&#34;我& #39; m empty&#34;),删除遍历不是太复杂。代码中您似乎误入歧途的地方是:
您可以使用指针值,也可以使用实际指针本身(按地址)。我更喜欢后者。在 的情况下,头节点指针必须通过地址传递以允许可能修改调用者的变量(就像C中的其他所有内容一样)或者该函数可以返回潜在的新头节点地址。我更喜欢这些方法的前者,因为它让您可以选择使用函数返回值将错误状态传递给调用者。你现在没有这样做,但你应该(提示)。
void checkAvg(int avg, struct list** pp)
{
while (*pp)
{
if ((*pp)->data <= avg)
{
struct list *victim = *pp;
*pp = victim->link;
free(victim);
}
else
{ // advance to address of next "link" pointer
pp = &(*pp)->link;
}
}
}
值得注意的是,如果列表按升序排列为排序,则可以显着简化。如果是这种情况,则整个checkAvg
函数变得非常简单。一旦检测到不再符合条件的值,就可以退出循环:
void checkAvg(int avg, struct list** pp)
{
while (*pp && (*pp)->data <= avg)
{
struct list *victim = *pp;
*pp = victim->link;
free(victim)
}
}
在任何一种情况下,通过将头指针传递给调用方 by-address 来调用该函数:
struct list *head = NULL;
//... populate list....
checkAvg(value, &head);
工作原理
链接列表就像这样:
-------- -------- --------
head ---> | link | ---> | link | ---> | link | ---> NULL
| val1 | | val2 | | val3 |
-------- -------- --------
使用发布的方法,遍历列表使用指向指针的指针,其执行如下操作:
pp --:
: -------- -------- --------
head ---> | link | ---> | link | ---> | link | ---> NULL
| val1 | | val2 | | val3 |
-------- -------- --------
pp
指向指针; 不一个节点。最初pp
保存head
指针的地址(作为参数传入by-address)。
那么如果第一个节点符合您的标准会发生什么?最终,这是结果
pp --:
: -------- --------
head ---> | link | ---> | link | ---> NULL
| val2 | | val3 |
-------- --------
--------
victim ---> | link |
| val1 |
--------
并且受害者节点被立即丢弃(受害者的链接实际上仍然引用第二个节点,但在分离发生后在此上下文中没有意义。
那么,如果第二个节点是需要删除的节点(即我们跳过第一个节点),那该怎么办呢?这有点复杂:
pp -----:
:
---:---- --------
head ---> | link | ---> | link | ---> NULL
| val1 | | val3 |
-------- --------
--------
victim ---> | link |
| val2 |
--------
这是尝试显示(确实很差)的是pp
指向指针总是保存我们可能指针的地址正在修改。如果我们不需要修改该指针,我们会将pp
中的地址更改为指向列表中的下一个link
指针(通过pp = &(*pp)->link
获取
当列表已经排序时,后一种情况不会发生,因此其迭代循环更简单的原因。我们只是枚举列表,抛出节点,直到找到一个不再符合我们条件的节点。
但无论如何,pp
始终保存指向我们正在使用的节点的指针的地址。。最初,该地址是呼叫者head
指针的地址。
确定。我只能希望让它更清晰。在循环中使用printf("pp=%p, *pp=%p\n", pp, *pp)
进行一些调试可以实现最具教育意义的实际情况。在调试器中手动遍历算法将高度提供信息。
答案 1 :(得分:1)
比你想象的容易得多。
您正在步行和修改链接列表,因此请设置当前和之前的链接。
void checkAvg(int avg, struct list** head_node){ //when calling, use checkAvg(avg, &head_node);
struct list* prev = NULL;
struct list* curr = *head_node;
从头开始......
while(curr != NULL){
if(curr->data <= avg){
if(prev == NULL){
*head_node = curr->next; //updates the head node
} else {
prev->next = curr->next; //removes the unwanted node
}
}
curr = curr->next;
}
你真的不需要特殊的结束案例,因为当curr为NULL时,while循环终止;对于列表中的最后一项,curr-&gt; next为NULL,因此当它被设置时它将结束循环。您也不需要检查列表是否为空,因为如果curr == NULL
,则循环将结束,并且您首先将头部分配给curr
。
答案 2 :(得分:0)
不是检查prev,curr和next的数据,而是检查循环中的单个数据 如果更改,您还需要返回新头 我建议这样的(测试过的):
#include <stdio.h>
#include <malloc.h>
typedef struct list_ {
struct list_ *link;
int data;
} list;
list* RemoveElementsBelow(int avg, list* head_node) {
while (head_node != NULL && head_node->data <= avg) {
// Remove the first.
list* new_head = head_node->link;
free(head_node);
head_node = new_head;
}
if (head_node == NULL) {
return NULL;
}
list* prev;
list* curr;
prev = head_node;
curr = prev->link;
while (curr != NULL) {
if (curr->data <= avg) {
prev->link = curr->link; // Will be NULL at the end of the list.
free(curr);
curr = prev->link;
} else {
prev = curr;
curr = curr->link;
}
}
return head_node;
}
list* AddToHead(int value, list* head_node) {
list* new_node = malloc(sizeof(list));
new_node->link = head_node;
new_node->data = value;
return new_node;
}
list* PrintAndDeleteList(list* head_node) {
while (head_node != NULL) {
list* new_head = head_node->link;
printf("%d ", head_node->data);
free(head_node);
head_node = new_head;
}
printf("\n");
}
int main(int argc, char **argv) {
list* my_list = NULL;
int i;
int sum = 0;
for (i = 1; i < argc; ++i) {
int value = atoi(argv[i]);
sum += value;
my_list = AddToHead(value, my_list);
}
if (argc == 1) {
return 1;
}
int avg = sum / (argc - 1);
printf("Average value: %d\n", avg);
my_list = RemoveElementsBelow(avg, my_list);
PrintAndDeleteList(my_list);
return 0;
}
编译:
gcc -o test test.c
测试: ./test 10 20 30 40 50 平均价值:30 50 40 ./test 50 40 30 20 10 平均价值:30 40 50
答案 3 :(得分:0)
您在每次迭代中检查每个节点三次,这会导致一些奇怪的行为。在进入循环之前检查头部,并且每次只比较当前节点:
void checkAvg(int avg, struct list* head_node){
struct list* prev;
struct list* curr;
while (head!=NULL) {
if (head->data <= avg) {
head = head->link;
} else {
break;
}
}
prev = head;
curr = prev->link;
while (curr!=NULL) {
if (curr->data <= avg) {
prev->link = curr->link;
}
prev = prev->link;
curr = prev->link;
}
}
答案 4 :(得分:0)
我今晚想要一些递归魔法
Node* deleteBad(Node *head, int(*pred)(int)) {
if (head == NULL) {
return NULL;
}
if (pred(head->value)) {
head->next = deleteBad(head->next, pred);
return head;
}
else {
Node *next = head->next;
free(head);
return deleteBad(next, pred);
}
}
和累积器
void deleteBad2(Node *head, int(*pred)(int), Node *out) {
if (head) {
if (pred(head->value)) {
out->next = head;
deleteBad2(head->next, pred, out->next);
} else {
out->next = head->next;
deleteBad2(head->next, pred, out);
}
}
}
对于那些不喜欢递归优化版本的coxcombs
void deleteBad3(Node *head, int(*pred)(int), Node *out) {
begin:
if (head) {
if (pred(head->value)) {
out->next = head;
out = out->next;
head = head->next;
goto begin;
}
else {
out->next = head->next;
head = head->next;
goto begin;
}
}
}
如果有人不喜欢搞笑
void deleteBad3nogoto(Node *head, int(*pred)(int), Node *out) {
while (1) {
if (head) {
if (pred(head->value)) {
out->next = head;
out = out->next;
head = head->next;
}
else {
out->next = head->next;
head = head->next;
}
}
else {
break;
}
}
}