链表:使用最后一个指针插入到末尾
我正在学习链表,并且想知道我为在列表末尾插入元素而编写的以下程序(基本上是 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)是否有更高效、更简单的算法来在末尾插入元素?你能指引我去找他们吗?
I'm learning linked lists, and want to know whether the following program (basically InsertAtEnd function) I've made for inserting elements at the end of the list is correct.
The basic idea is that *HEAD points to the first element of the list and *LAST points to the last element. This saves the time and calculations in traversing to the last element of the list and then adding elements.
#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;
}
The questions that I want to specifically ask are:
a) Is the above code for adding elements at the end (i.e InsertAtEnd function) correct? (Note: I tested it on my machine and it works as expected. But I still want to confirm from you people)
b)Is the code (InsertAtEnd function) efficient?
c)Will the efficiency of the code (InsertAtEnd function) be affected if I try to make a longer list.
d)Are there more efficient and simple algorithms to insert elements at the end? Can you direct me to them?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
a) 看起来正确
b) 确实如此。您可以使函数返回 void,因为您不需要 int 返回值
c) 否。换句话说,执行时间是恒定的。
d) malloc 需要时间。您可以使用缓冲技术,提前分配一块节点,然后从缓冲区中获取下一个节点。当缓冲区变空时,分配另一个块。可以使用统计算法配置甚至(复杂)计算块尺寸。
此外,您不检查 malloc 的 NULL 返回,但它可能会失败。
a) It seems correct
b) It is. You could make the function return void, because you don't need that int return value
c) No. In other words the execution time is constant.
d) malloc takes time. You could use a buffering technique, to malloc a chunk of nodes in advance and then take the next node from the buffer. When the buffer becomes empy, allocate another chunk. The chunk dimensions could be configured or even (complex) computed using statistical algorithms.
Moreover, you don't check for malloc's NULL return, but it could fail.
该代码与列表的大小无关(除了空的特殊情况),即它是一个恒定时间算法。因此可能很难想出更有效的插入算法。
然而,唯一可以改进的部分是 malloc() 的使用。看起来您正在单独分配每个节点。如果您准备使用比实际需要更多的内存,则可以考虑一次分配多个节点,这样就可以减少对
malloc()
的调用。因此,当您尝试创建节点时,它首先检查池中是否还有空闲节点。如果是,则只需连接指针即可。如果没有,您将需要分配一个新的池。显然,这将需要更复杂的代码。This code is invariant with the size of the list (other than the special case for empty), i.e. it's a constant-time algorithm. So it's probably pretty hard to come up with a more-efficient insertion algorithm.
However, the only part that might be improved is the use of
malloc()
. It looks like you're allocating each node individually. If you're prepared to use more memory than you really need, you might consider allocating multiple nodes at once, so you have fewer calls tomalloc()
. So when you try to create a node, it first checks whether you have any free nodes left in the pool. If yes, then simply hook up the pointers. If not, you'll need to allocate a new pool. Clearly, this will require more complex code.a) 不检查malloc的结果。它可能在内存不足的情况下返回 NULL,从而导致崩溃。我相信算法的其余部分是正确的。
B+C+D)非常高效; O(1)意味着插入最后一个元素所需的时间是恒定的。事实上我想不出更简单、更高效的算法。
a) The result of malloc is not checked. It can return NULL under low-memory conditions, causing a crash. The rest of the algorithm is correct, I believe.
B+C+D) It is very efficient; it is O(1) mening that the time it takes to insert the LAST element is constant. In fact I can not think of an simpler and more efficient algorithm.
a) 是的,它应该可以工作(假设您的列表永远不会损坏并且 headref != NULL 但 lastref == NULL)。
如果返回值始终为 0,那它还有什么意义呢?
b) 当然,除了为每个节点分配内存之外。这是您的设计决定,但不同的解决方案会复杂得多,并且超出了本练习的范围。
至于函数本身 - 链表的好处是它们的复杂度为 O(1)。现在删除元素是另一回事,除非您有双链表,否则会更慢。
c) 不,它的复杂度是 O(1)。正如您所看到的,没有循环或任何涉及的内容,无论列表中有 5 个还是 5000 个元素,您都执行完全相同的步骤。
d) 您可以通过使用哨兵(即代替 headref 的虚拟节点)来避免检查列表是否为空。您只需将一个节点放置在内存中的某个位置并将 lastref 设置为它即可。现在您不需要将空列表作为特殊情况来处理。
a) Yes, it should work (assuming that your list never gets corrupted and has headref != NULL but lastref == NULL).
What's the point of the return value if it's always 0?
b) Other than it allocating memory for every node, sure. That's a design decision on your end, but a different solution would be a lot more complex and beyond the scope of this exercise.
As for the function itself - the nice thing about linked lists is that they're O(1). Now removing an element is a different matter, that will be slower unless you have a double-linked list.
c) No. It's O(1). As you see, there are no loops or anything involved, you do the exact same steps regardless of whether there's 5 or 5000 elements in the list.
d) You can avoid the check for the list being empty by using a sentinel, i.e. a dummy node in lieu of the headref. You just place a node somewhere in memory and set lastref to it. Now you don't need to have to handle having an empty list as a special case.
a)我认为你的代码是正确的,因为它做了你期望它做的事情。您可能想检查
malloc
的返回值。b)我认为你的代码也很高效。我唯一能想到的是
*lastref=(*lastref)->next;
行,我将其替换为*lastref=newnode;
。但我认为几乎每个编译器都会自动优化这一点。c) 您的方法具有恒定的运行时间 (O(1)),因此添加到更大的列表不会改变运行时间。但是,如果元素连续存储在内存中,则遍历列表可能会更快。
d) 我不认为
insertAtEnd
可以实现得更快。您可以尝试在内存中连续存储元素,并检查malloc
的返回。我个人在实现这些东西时唯一做的事情是:
为整个链接列表数据结构创建一个自己的结构(保存
size
和head
- 和lastElement
-pointers)我不会将列表限制为保存整数,而是保存任意元素(因此
void*
s)a) I think your code is correct in the sense that it does what you expect it to do. You might want to check the return value of
malloc
.b) Your code is in my opinion also efficient. The only thing I could think of is the line
*lastref=(*lastref)->next;
which I would replace by*lastref=newnode;
. But I think this will be optimized by almost every compiler automatically.c) Your method has constant runtime (O(1)), so adding to a bigger list will not change runtime. However, traversing the list might be faster if the elements are stored continuously in memory.
d) I don't think that
insertAtEnd
can be implemented seriously faster. You could try to store elements continously in memory, and check for return ofmalloc
.The only thing I personally do when implementing such stuff is:
create an own struct for the whole linked-list-data-structure (holding
size
andhead
- andlastElement
-pointers)I would not restrict the list to hold integers but arbitrary elements (thus
void*
s)抱歉,这并不能回答问题,但我认为您可能会发现它有用。我提供了一个稍微改变的方法:
调用约定失去了一个参数,并且不需要任何条件。如果您想快速访问最后一个列表元素,这会有所损失,因为它并不那么简单。
请注意,开始变得愚蠢了。
Sorry, this doesn't answer the question but I thought you might find it useful. I offer a slight change of approach:
The calling convention loses an argument, and there's no conditional required. If you want quick access to the last list element this loses out a little because it's not as simple.
is starting to get silly, mind.