A Linked List is a very core linear data structure. Many other data structures can be thought of as an extension of the Linked List. A Linked List is simply a list of elements that are not saved sequentially in memory (unlike Basic Array(s)). Instead, elements are saved dynamically (randomly) on the Heap(s) using Pointers.


Pros & Cons (Time & Space Complexity)

Pros: Linked Lists are dynamically allocated to the heap (as opposed to Basic Array(s)). This avoids the main issue with Arrays, their fixed size. Linked Lists can be also very efficient for insertion or deletion making them well suited for basing other Data Structures from (i.e. Stack(s) , Queue(s)). Time Complexity for Inserting/deleting to head/tail is independent from the size of the list, or .

Cons: Linked Lists struggle when it comes to Random Access (unlike Basic Array(s)) and Cache Locality (due to non-sequential memory).


Types of Linked Lists

Singly Linked List

struct Node {
char[32] data;
struct Node* next;
};
graph LR
A-->B-->C-->D-->NULL

Doubly Linked List

struct Node {
char[32] data;
struct Node* next;
struct Node* prev;
};
graph LR
A<-->B<-->C<-->D<-->NULL

Circular List A Circular Linked-List simply means instead of the last node pointing to NULL, it points back to the first element. Circular Linked Lists can be singly or doubly linked.

graph LR
A-->B-->C-->D-->A

Operations on Linked Lists (in C)

Create Node:

struct Node* createNode*(char *name) {
    struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
    if (!newNode) {
        return head;
    }
    strcpy(newNode->data, name);
    newNode->next = NULL;
    return newNode;
}

Traversal (Print(), Free()):

void printList(struct Node *head){
	struct Node *p;
	p=head;
	if (!p) return;
	while(p){
		printf("%s\n",p->name);
		p=p->next;
	}
}
 
void freeList(struct Node *head) {
    struct Node *p;
    while (head) {
        p = head;
        head = head->next;
        free(p);
    }
}

Append (Insert at Tail):

struct Node* appendNode(struct Node *head, char *name) {
    struct Node *newNode = createNode(name);
	
	//CASE: Empty List
    if (!head) {
        return newNode;
    }
	
	//CASE: Traverse to end of list
    struct Node *p = head;
    while (p->next) {
        p = p->next;
    }
    p->next = newNode;
    return head;
}

Insert Alphabetically

struct Node* insertAB(struct Node *head, char *name){
    struct Node *p=head;
    struct Node *prev=NULL;
 
    struct Node *newNode = createNode(name);
 
    //CASE: Empty List
    if(!head){
        return newNode;
    }
    
	//CASE: Needs to become the new head node
    if(strcmp(p->name,name)>0){
        newNode->next=p;
        return newNode;
    }
 
	//CASE: Standard (Linear Search)
    while(p!=NULL && strcmp(p->name,name)<0){
        prev=p;
        p=p->next;
    }
    prev->next=newNode;
    newNode->next=p;  
    
  return head;
}

Removal:

struct Node* removeNode(struct Node *head, char *target){
    struct Node *p=head;
    struct Node *prev=NULL;
    
    //CASE: target == head->name
    if(strcmp(p->data,target)==0){ 
        head=p->next;
        free(p);
        return head;
    }
    
	//CASE: Standard (Linear Search)
    while(p && strcmp(p->data,target)!=0){
        prev=p;
        p=p->next;
    }
	
	//CASE: Not Found
    if (!p){
        return head;
    }
    
	//CASE: Found
    prev->next=p->next; 
    free(p);
    return head;
}