Singly linked list Tail - algorithm

if you want create a singly linked list like this:
struct Node {
int data;
Node *next;
};
struct List{
Node *head;
// Node *tail; --> necessary?
Node *last;
};
And this list has the methods "append", "remove", "printList" and "findElement".
Is it necessary to have a tail? Because with "last" you can address the last node.
So when it is necessary to have all three Nodes "head", "tail" and "last"? When you want to insert the node sorted into the list for example?

No, it's not necessary. The tail is equal to head->next and thus it would be redundant and add bookkeeping overhead to keep this field updated.
Also note that the field last is kind of unusual. In most use cases, you add elements to the head of a singly linked list and use a different data structure when you really need to add to the end.

Actually, you can implement enqueue (append at tail), push (prepend at head), dequeue (remove from head), and of course find and print with with a one-pointer header. The trick is to make the list circular and have the header point to the tail. Then tail->next is the head.
#include <stdio.h>
#include <stdlib.h>
typedef struct node_s {
struct node_s *next;
int data;
} Node;
typedef struct list_s {
Node *tail;
} List;
Node *new_node(int data) {
Node *node = malloc(sizeof *node);
node->data = data;
node->next = node;
return node;
}
void init_list(List *list) {
list->tail = NULL;
}
int is_empty(List *list) {
return list->tail == NULL;
}
void enqueue(List *list, Node *node) {
if (list->tail) {
Node *head = list->tail->next;
node->next = head;
list->tail->next = node;
list->tail = node;
} else list->tail = node->next = node;
}
void push(List *list, Node *node) {
if (list->tail) {
Node *head = list->tail->next;
node->next = head;
list->tail->next = node;
} else list->tail = node->next = node;
}
Node *dequeue(List *list) {
Node *head = list->tail->next;
if (head == list->tail)
list->tail = NULL;
else
list->tail->next = head->next;
return head;
}
void print_list(List *list) {
printf("The list:\n");
if (list->tail) {
Node *head = list->tail->next;
Node *p = head;
do {
printf("%d\n", p->data);
p = p->next;
} while (p != head);
}
}
int main(int argc, char *argv[]) {
List list[1];
init_list(list);
// Build the list in order and print it.
for (int i = 0; i < 4; i++) enqueue(list, new_node(i));
print_list(list);
// Remove elements from head until empty.
printf("Dequeueing:\n");
while (!is_empty(list)) {
Node *node = dequeue(list);
printf("%d\n", node->data);
free(node);
}
// Build the list in reverse order and print it.
for (int i = 0; i < 4; i++) push(list, new_node(i));
print_list(list);
return 0;
}

I think it depends on what operations you want to use.
Assuming you want to insert and delete nodes at the tail of a list, it is certainly a wise choice to keep a last node in your list.
Otherwise, if you want to do operations at the beginning of the list, a last node is unnecessary.

It's not necessary but a tail can be useful if you're working with the linked list in a queue-like FIFO fashion rather than a stack-like LIFO fashion or want to be able to transfer entire lists of elements from one head to another's tail without disrupting the relative order of the elements.
Note that I'm referring to 'tail' as a reference to the last node in the list which I believe is safe to assume that the question is about.
A lot of very micro-optimized SLL implementations often are tail-less and work like a stack while backed by an efficient fixed allocator for locality of reference (cache-friendliness) and faster node allocations/deallocations. There the primary benefit of the SLL over a variable-sized array-based sequence is the ability to start moving things around by just changing the value of the next pointer/reference and the lack of invalidation on inserting/removing elements if you're working in native, lower-level languages that involve pointers. The lack of a tail can boost performance quite a bit by reducing the amount of branching instructions required in operations to push and pop from the stack.
For the needs you listed, whether the tail is going to help or just add unnecessary complexity and overhead is if your append and remove operations can work strictly from the front in a LIFO stack fashion or if you want to be able to append to the back but remove from the front in a FIFO fashion without any iteration involved, e.g. If you don't have a tail in the latter case, one of these operations are going to go from constant-time complexity to linear-time complexity, and you might improve your use cases by exchanging that linear-time algorithmic complexity for the relatively smaller micro-level overhead of maintaining a tail.

Related

TLE when solving "Removing duplicates from unsorted linked list" using maps in c++

I am trying to solve the removing duplicates from unsorted linked list problem on GFG using maps. I have figured out the accepted solution using insert and find commands:
Node * removeDuplicates( Node *head)
{
// your code goes here
map <int, int> duplicates;
Node* curr=head;
Node* prev=NULL;
while(curr){
if(duplicates.find(curr->data)==duplicates.end()){
duplicates.insert({curr->data,1});
prev=curr;
}
else{
prev->next=curr->next;
delete(curr);
}
curr=prev->next;
}
return head;
}
But another approach I tried earlier is giving TLE for submission, even though it works fine for example test case. I have tried to implement the same idea as above but in a slightly different way. Can anyone help me out with this?
Node * removeDuplicates( Node *head)
{
map <int, int> duplicates;
Node* temp=head;
while(temp){
duplicates[temp->data]=1;
temp=temp->next;
}
Node* curr=head;
Node* prev=NULL;
while(curr){
if(duplicates[curr->data]==1){
duplicates[curr->data]=0;
prev=curr;
}
else{
prev->next=curr->next;
delete(curr);
}
curr=prev->next;
}
return head;
}
There are multiple problems with the 2nd solution.
your running through a linked list twice, don't do this if possible.
unordered_map might be much faster as already mentioned in the commented
your looking more in the duplicates in the 2nd solution, 3 vs. 2.
Either of the above could further cause cache congestion slowing things down further.
Take the bool data type far value instead of int so you don't have to initialize the value of a map.
Instead of taking an ordered map use the unordered map which is relatively faster.
Here is the optimized code.
Node *removeDuplicates(Node *head)
{
Node *curr = head;
Node *prev = NULL;
if (curr == NULL)
return NULL;
unordered_map visited;
while (curr != NULL)
{
if (visited[curr->data])
{
Node *temp = curr;
curr = curr->next;
delete (temp);
prev->next = curr;
}
else
{
visited[curr->data] = true;
prev = curr;
curr = curr->next;
}
}
return head;
}

Tree and graphs problems in Julia using struct data types, pointers and this

I want to write solve some graph/trees problems using Julia language.
Here is some good example. In C it was done this way:
Recursive C program for level order traversal of Binary Tree
#include <stdio.h>
#include <stdlib.h>
/* A binary tree node has data, pointer to left child
and a pointer to right child */
struct node
{
int data;
struct node *left, *right;
};
/* Function prototypes */
void printGivenLevel(struct node* root, int level);
int height(struct node* node);
struct node* newNode(int data);
/* Function to print level order traversal a tree*/
void printLevelOrder(struct node* root)
{
int h = height(root);
int i;
for (i=1; i<=h; i++)
printGivenLevel(root, i);
}
/* Print nodes at a given level */
void printGivenLevel(struct node* root, int level)
{
if (root == NULL)
return;
if (level == 1)
printf("%d ", root->data);
else if (level > 1)
{
printGivenLevel(root->left, level-1);
printGivenLevel(root->right, level-1);
}
}
/* Compute the "height" of a tree -- the number of
nodes along the longest path from the root node
down to the farthest leaf node.*/
int height(struct node* node)
{
if (node==NULL)
return 0;
else
{
/* compute the height of each subtree */
int lheight = height(node->left);
int rheight = height(node->right);
/* use the larger one */
if (lheight > rheight)
return(lheight+1);
else return(rheight+1);
}
}
/* Helper function that allocates a new node with the
given data and NULL left and right pointers. */
struct node* newNode(int data)
{
struct node* node = (struct node*)
malloc(sizeof(struct node));
node->data = data;
node->left = NULL;
node->right = NULL;
return(node);
}
/* Driver program to test above functions*/
int main()
{
struct node *root = newNode(1);
root->left = newNode(2);
root->right = newNode(3);
root->left->left = newNode(4);
root->left->right = newNode(5);
printf("Level Order traversal of binary tree is \n");
printLevelOrder(root);
return 0;
}
I have tried to do it in Julia in similar way, but there are some problems, especially with accessing to struct elements.(like node->right and node->left).
Or some way to create Self-referential struct and function to allocate nodes.
struct node
data::Int
left::Ptr{node}
right::Ptr{node}
end
# Compute the "height" of a tree -- the number of
# nodes along the longest path from the root node
# down to the farthest leaf node.
function height = (node::Ptr{node})
if node === nothing
return 0
else
# compute the height of each subtree
lheight = height(node->left)
rheight = height(node->right)
# use the larger one
if lheight > rheight
return lheight+1
else return rheight+1
end
end
end
From what I've seen trying to recreate a problem solution in C way isn't the way, however this struct type should be useful. I just have to know how to create self-referent struct, how to allocate elements in this node and how to get them.
First of all, I would strongly suggest reading through https://docs.julialang.org/en/v1/, since Julia is different from C in quite a few ways. There are multiple things to point out here:
Per default structs in Julia are immutable, which means you cannot modify fields after it is created. It is also always passed by copy instead of by reference, since it doesn't have a specific memory address and usually gets allocated on the stack. This actually has multiple benefits for the compiler and is part of the reason why Julia can be so fast. In your example you probably want a mutable struct, which is more similar a struct in C.
In Julia, you should never have to use pointers (Ptr) directly, unless you are calling C code. Since Julia uses a garbage collector, raw pointers have a lot of gotchas when it comes to memory management and should generally just be avoided. You usually just work with objects directly or, if you want to pass immutable objects by reference, you can wrap them in Ref.
In Julia, fields are always accessed either just with a dot x.field (equivalent to getproperty(x, :field)), or in some cases getfield(x, :field). (The latter can't be overloaded by the user, which is sometimes useful). -> actually creates an anonymous function.
For your example the following should work instead:
mutable struct Node
data::Int
left::Node
right::Node
Node(data::Int) = new(data)
end
function height(node::Node, field::Symbol)
isdefined(node, field) || return 0
return height(getproperty(node, field))
end
function height(node::Node)
lheight = height(node, :left)
rheight = height(node, :right)
# use the larger one
if lheight > rheight
return lheight+1
else
return rheight+1
end
end
What the first part is doing is creating a mutable struct Node, with self-referential fields like your C example. The line Node(data::Int) = new(data) is actually an inner constructor taking just the data and if you call new directly in a mutable struct, you can leave trailing fields undefined. You can define these fields afterwards with x.field = y. If these fields are themselves mutable, you can also check if they are undefined with isdefined(x, :field). Here, I am adding another method to height, which also takes a field name, which returns the height of the field if it's defined and 0 otherwise.
You would then construct nodes and calculate their height like this:
julia> n = Node(1)
Node(1, #undef, #undef)
julia> n.left=Node(2)
Node(2, #undef, #undef)
julia> n
Node(1, Node(2, #undef, #undef), #undef)
julia> height(n)
2
Hope that helps! If you want to learn more, the documentation I linked above is usually quite good.

working with pointers and linkedLists: how to iterate over a linked list, change and compare keys

I am asked to implement an algorithm based upon the data structure of a linkedList in the form of pseudocode.
Unfortunately I have a Python/Java background and thus no experience with pointers.
Could someone explain me how I would iterate over a doublyLinkedList, change and compare values of elements.
From what I have understood so far, i would do something like this.: to have an iteration over each element.
for L.head to L.tail
But how would I then access the current object in the list analogous to A[i] for i to L.length?
As the order of a linkedList is determined by pointers rather than indices in a linkedList can I simply do things like currentObj.prev.key = someVal or currentObj.key < currentObj.prev.key or is there some other wokflow to work with individual elements?
Again, I am obviously stuck as I lack an basic understanding on how to deal with pointers.
Cheers,
Andrew
So basically the data structures are:
Node:
node {
node next;//"single link"
node prev;//for "doubly"...
}
and List:
list {
node head;//for singly linked, this'd be enough
node tail;//..for "doubly" you "point" also to "tail"
int/*long*/ size; //can be of practical use
}
The (common) operations of a list:
Creation/Initialization:
list:list() {
head = null;
tail = null;
size = 0;
}
Add a node at the first position:
void:addFirst(node) {
if(isEmpty()) {
head = node;
tail = node;
size = 1;
} else {
head.prev = node;
node.next = head;
head = node;
size++;
}
}
// ..."add last" is quite analogous...
"is empty", can be implemented in different ways..as long as you keep the invariant
bool:isEmpty() {
return size==0;
//or return head==null ... or tail==null
}
"add a node at position i":
void:add(i, node) {
assert(i>=0 && i <=size);//!
if(isEmpty() || i == 0) {
addFirst(node);
} else if (i == size) {
addLast(node);
} else {//here we could decide, whether i is closer to head or tail, but for simplicity, we iterate "from head to tail".
int j = 1;
node cur = head.next; //<- a "cursor" node, we insert "before" it ;)
while(j < i) {//1 .. i-1
cur = cur.next;// move the cursor
j++;//count
}
//j == i, cur.next is not null, curr.prev is not null, cur is the node at currently i/j position
node.prev = cur.prev;
cur.prev.next = node;
cur.prev = node;
node.next = cur;
}
//don't forget the size:
size++;
}
Delete(node) is easy!
"Delete at position", "find node", "get node by position", etc. should use a similar loop (as add(i, node))...to find the correct index or node.
The strength/advantage of a doubly (comparing to a singly) linked list, is that it can iterate as "forth" as "back". To use this advantage (it is only advantageous on index-based operations, for "find(node)" e.g. you still don't know where to start/iterate best), you determine whether pos is closer to head(0) or to tail(size-1), and start&route your iteration accordingly.
...What else operations are you intereseted in (detail)?

Swap the head of the linked list

I had heard this question from my friend who attended an interview recently:
Given the head of the linked list, Write a function to swap the head with the next element in the linked list and return the pointer to the new head.
Ex:
i/p: 1->2,3,4,5 (the given head is 1)
o/p: 2->1,3,4,5
Assuming
struct node {
struct node *next;
};
struct node *head;
then the solution might look something like
struct node *next = head->next;
if(next == NULL) return head; // nothing to swap
head->next = next->next;
next->next = head;
head = next;
return next;
struct node* head;
struct node *tmp1,*tmp2;
tmp1=head; // save first node pointer
tmp2=head->next->next; // save third node pointer
head=head->next; // Move Head to the second node
head->next=tmp1; // swap
head->next->next=tmp2; // Restore the link to third node

Does the singularly LinkedList give output In LIFO?

I was working on a singularly-linked list. While creating my own linked list I got confused on printing the collection of nodes in my custom linked list.
I want to know, does a singularly-linked list display its collection in a LIFO manner like a stack?
below is my Own LinkedList AND node is A Class can anyone tell me Does Singular LinkedList Prints The Collection In Lifo Manner.
class MYlinklist
{
Node header;
public void Add(int a)
{
Node n = new Node();
n.element = a;
n.Next = header;
header = n;
}
public void Print()
{
Node n = new Node();
n = header;
while (n != null)
{
Console.WriteLine(n.element.ToString());
n = n.Next;
}
}
}
If you are referring to LinkedList<T>, the answer depends on how you add new members.
If you want to make the linked list iterate in LIFO, you can do so by always using AddFirst to add, and RemoveFirst to remove. This will cause it behave very much like a stack.
The nice thing about LinkedList<T>, however, is that you can add anywhere inside of the list as an O(1) operation.
Edit:
If you want this to be FIFO instead, you'll need to change how to add your nodes, and add them at the end of the list, not the start:
class MyLinkedList
{
Node header;
Node last;
public void Add(int a)
{
Node n = new Node();
n.element = a;
n.Next = null; // We'll put this at the end...
if (last == null)
{
header = n;
last = n;
}
else
{
last.Next = n;
last = n;
}
}
public void Print()
{
Node n = new Node();
n = header;
while (n != null)
{
Console.WriteLine(n.element.ToString());
n = n.Next;
}
}
}
You're adding nodes at the head of the list (note how you are always setting node.Next to the head of the list).
Then you're iterating through from the head (which is the last element inserted) to the tail.
If you want to iterate in FIFO order, you should do the following:
Maintain a reference to the tail of the list (as well as the head, which you've put in header).
When you add a node, set tail.Next to the new node, and then set tail to point to the new node.
Your iteration function can be unchanged.
Your other option is, instead of maintaining a reference to the tail, just do an iteration through the list each time. But this comes with the tradeoff of needing to go through n-1 elements to add the nth element every time, which means adding many elements is an O(n^2) operation. I would not recommend doing this, but it might be fine for a beginning if you're learning the basics and you're not sure about the tail reference manipulation. In production code, though, you should always have a head and a tail reference for linked lists.

Resources