我正在学习链接列表,并且想知道我在列表末尾插入元素所做的以下程序(基本上是InsertAtEnd函数)是否正确。
基本思想是* HEAD指向列表的第一个元素,* LAST指向最后一个元素。这样可以节省遍历列表最后一个元素的时间和计算,然后添加元素。
#include<stdio.h>
#include<stdlib.h>
// Structure for the list element/node
struct node
{
int data; // Stores the data
struct node *next; // Points to the next element in the list.
};
int InsertAtEnd(struct node **, struct node **, int); /*Declaration of the function which
inserts elements at the end.*/
int main()
{
struct node *HEAD=NULL; //Points to the first element in the list.
struct node *LAST=NULL; //Points to the last element in the list.
int i=1;
for(i=1;i<11;i++)
{
InsertAtEnd(&HEAD,&LAST,i);
}
}
// Function to insert element at the end.
int InsertAtEnd(struct node **headref,struct node **lastref,int i)
{
struct node *newnode=malloc(sizeof(struct node)); /*Allocates memory for the newnode
and store the address in pointer
newnode*/
newnode->data=i; // Assign value to the data variable of the newnode.
newnode->next=NULL; // Assign NULL to the next pointer of the newnode.
if(*headref==NULL) //Checks if the list is empty.
{
*headref=newnode; // Places the address of the new node in HEAD pointer.
*lastref=newnode; // Places the address of the new node in LAST pointer.
return 0; //Exit function
}
/* If the list is not empty, then make the next pointer of the present last node point to the new node*/
(*lastref)->next=newnode;
*lastref=(*lastref)->next; // Increment LAST to point to the new last node.
return 0;
}
我想特别提出的问题是:
a)上面添加元素的代码(即InsertAtEnd函数)是否正确? (注意:我在我的机器上进行了测试,它按预期工作。但我仍想向您确认)
b)代码(InsertAtEnd函数)是否有效?
c)如果我尝试制作更长的列表,是否会影响代码(InsertAtEnd函数)的效率。
d)是否有更有效和简单的算法来插入元素?你能指导我吗?
答案 0 :(得分:4)
a)看来是正确的
b)确实如此。您可以使函数返回void,因为您不需要该int返回值
c)否。换句话说,执行时间是不变的。
d)malloc需要时间。您可以使用缓冲技术,预先对一大块节点进行malloc,然后从缓冲区中获取下一个节点。当缓冲区变为empy时,分配另一个块。可以使用统计算法配置或甚至(复杂)计算块大小。
此外,您不检查malloc的NULL返回,但它可能会失败。
答案 1 :(得分:2)
此代码与列表的大小不变(除了空的特殊情况),即它是一个恒定时间算法。因此,提出更有效的插入算法可能非常困难。
但是,唯一可以改进的部分是使用malloc()
。看起来您正在单独分配每个节点。如果您准备使用比实际需要更多的内存,可以考虑一次分配多个节点,这样您对malloc()
的调用就会减少。因此,当您尝试创建节点时,它首先检查池中是否还有任何空闲节点。如果是,那么简单地连接指针。如果没有,您将需要分配一个新池。显然,这将需要更复杂的代码。
答案 2 :(得分:2)
a)未检查malloc的结果。它可以在内存不足的情况下返回NULL,从而导致崩溃。我相信算法的其余部分是正确的。
B + C + D)效率很高; O(1)指出插入LAST元素所花费的时间是恒定的。事实上,我想不出更简单,更有效的算法。
答案 3 :(得分:2)
a)是的,它应该有效(假设你的列表永远不会被破坏并且有headref!= NULL但是lastref == NULL)。
如果返回值始终为0,那么它的重点是什么?
b)除了为每个节点分配内存之外,当然。这是你最终的设计决定,但是一个不同的解决方案会更复杂,超出了本练习的范围。
至于函数本身 - 关于链表的好处是它们是O(1)。现在删除一个元素是另一回事,除非你有一个双链表,否则速度会慢一些。
c)不,这是O(1)。如您所见,没有循环或任何涉及的内容,无论列表中是否有5个或5000个元素,您都会执行完全相同的步骤。
d)您可以通过使用标记,即代替headref的虚拟节点来避免检查列表是否为空。您只需将节点放在内存中的某个位置并将lastref设置为它。现在您不需要处理具有空列表的特殊情况。
答案 4 :(得分:1)
a)我认为你的代码是正确的,因为它完成了你期望它做的事情。您可能想要检查malloc
的返回值。
b)我认为你的代码也很有效率。我唯一能想到的是*lastref=(*lastref)->next;
行*lastref=newnode;
,我将用insertAtEnd
代替。但我认为几乎每个编译器都会自动优化它。
c)您的方法具有常量运行时(O(1)),因此添加到更大的列表不会改变运行时。但是,如果元素连续存储在内存中,遍历列表可能会更快。
d)我认为malloc
不能更快地实施。您可以尝试将元素连续存储在内存中,并检查size
的返回值。
我个人在实施这些内容时唯一能做的就是:
为整个链表数据结构创建一个自己的结构(持有head
和lastElement
- 和void*
- 指针)
我不会限制列表保持整数而是任意元素(因此{{1}} s)
答案 5 :(得分:1)
抱歉,这不回答问题,但我认为您可能会觉得它很有用。我略微改变了方法:
int InsertAtEnd(struct node ***, int); /*Declaration of the function which
inserts elements at the end.*/
struct node *HEAD=NULL; //Points to the first element in the list.
struct node **LAST=&HEAD; //Points to the last (NULL) *pointer* in the list.
// Function to insert element at the end.
int InsertAtEnd(struct node ***lastrefref,int i) // no need to pass HEAD
{
struct node *newnode=malloc(sizeof(struct node)); /*Allocates memory for the newnode
and store the address in pointer
newnode*/
// And here we should be checking for errors...
newnode->data=i; // Assign value to the data variable of the newnode.
newnode->next=NULL; // Assign NULL to the next pointer of the newnode.
**lastrefref = newnode; // Put new element at end of list
*lastrefref=&(newnode->next); // Update last pointer location
}
调用约定失去了一个参数,并且没有条件要求。如果你想快速访问最后一个列表元素,这会失去一点,因为它不是那么简单。
struct <anything> ***
开始变得愚蠢,记住。