Insert at Position (Doubly Linked List)
Index pos is 0-based. Valid positions for a list of length n are 0 … n (insert after the last element when pos == n). Reuse insert at beginning and insert at end for the ends; in the middle, walk to the predecessor and splice—updating four pointer ends (prev and next on both neighbors).
C program
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *prev;
struct Node *next;
};
static int count_nodes(struct Node *h)
{
int c = 0;
while (h != NULL) {
c++;
h = h->next;
}
return c;
}
void insert_begin(struct Node **head, struct Node **tail, int data);
void insert_end(struct Node **head, struct Node **tail, int data);
void insert_begin(struct Node **head, struct Node **tail, int data)
{
struct Node *n = malloc(sizeof(struct Node));
if (n == NULL)
return;
n->data = data;
n->prev = NULL;
n->next = *head;
if (*head != NULL)
(*head)->prev = n;
else
*tail = n;
*head = n;
}
void insert_end(struct Node **head, struct Node **tail, int data)
{
struct Node *n = malloc(sizeof(struct Node));
if (n == NULL)
return;
n->data = data;
n->next = NULL;
if (*head == NULL) {
n->prev = NULL;
*head = *tail = n;
return;
}
n->prev = *tail;
(*tail)->next = n;
*tail = n;
}
void insert_at(struct Node **head, struct Node **tail, int pos, int data)
{
if (*head == NULL) {
if (pos == 0)
insert_begin(head, tail, data);
return;
}
int n = count_nodes(*head);
if (pos < 0 || pos > n)
return;
if (pos == 0) {
insert_begin(head, tail, data);
return;
}
if (pos == n) {
insert_end(head, tail, data);
return;
}
struct Node *cur = *head;
for (int i = 0; i < pos - 1; i++)
cur = cur->next;
struct Node *nn = malloc(sizeof(struct Node));
if (nn == NULL)
return;
nn->data = data;
nn->next = cur->next;
nn->prev = cur;
cur->next->prev = nn;
cur->next = nn;
}
void print_list(struct Node *head)
{
while (head != NULL) {
printf("%d ", head->data);
head = head->next;
}
printf("\n");
}
void free_list(struct Node *head)
{
while (head != NULL) {
struct Node *next = head->next;
free(head);
head = next;
}
}
int main(void)
{
struct Node *head = NULL;
struct Node *tail = NULL;
insert_at(&head, &tail, 0, 10);
insert_at(&head, &tail, 1, 20);
insert_at(&head, &tail, 2, 30);
print_list(head); /* 10 20 30 */
free_list(head);
return 0;
}
Complexity
| Case | Time |
|---|---|
pos == 0 or pos == n | O(1) |
| Middle | O(pos) + four pointer writes |
Output
After insert_at(&head, &tail, 0, 10), insert_at(..., 1, 20), and insert_at(..., 2, 30), then print_list(head):
10 20 30
Step-by-step execution table
Each call dispatches: empty list → insert_begin; pos == n → insert_end (append). This demo never hits the middle-insert path.
| Step | Operation | Branch | List state (forward) | List state (backward) | head |
tail |
|---|---|---|---|---|---|---|
| 1 | insert_at(0, 10) |
Empty → insert_begin |
10 |
10 |
Node 10 |
Node 10 |
| 2 | insert_at(1, 20) |
pos == n → insert_end |
10 → 20 |
20 → 10 |
Node 10 |
Node 20 |
| 3 | insert_at(2, 30) |
pos == n → insert_end |
10 → 20 → 30 |
30 → 20 → 10 |
Node 10 |
Node 30 |
Memory layout table
| Address | Node | data |
prev |
next |
Points to (prev / next) |
|---|---|---|---|---|---|
0x1000 |
Node A | 10 | NULL |
0x1008 |
prev: NULL, next: Node B |
0x1008 |
Node B | 20 | 0x1000 |
0x1010 |
prev: Node A, next: Node C |
0x1010 |
Node C | 30 | 0x1008 |
NULL |
prev: Node B, next: NULL |
Pointer variables
head = 0x1000— points to Node A (first node).tail = 0x1010— points to Node C (last node).
Traversal path for print_list(head)
Forward walk using next until NULL:
Start at head (0x1000) → Node A [10]
↓ (next)
Node B [20]
↓ (next)
Node C [30]
↓ (next)
NULL → Stop
Output: 10 20 30
Comparison: insert_at vs direct insert_begin / insert_end
| Feature | insert_at (this example) |
Direct insert_begin / insert_end |
|---|---|---|
| API | One function + index pos |
Call the specific helper for front vs back |
This main sequence |
Resolves to insert_begin then twice insert_end |
Would match with insert_begin(10) then insert_end ×2 — same final list |
| Middle insert | Walk + four pointer updates (not exercised here) | Not applicable on ends-only usage |
| Extra work | count_nodes when list non-empty |
None for pure front/back code paths |
Key branches inside insert_at
| Condition | Delegates to | Purpose |
|---|---|---|
*head == NULL && pos == 0 |
insert_begin |
First node in an empty list |
pos == 0 (non-empty) |
insert_begin |
New front |
pos == n |
insert_end |
Append after current tail |
0 < pos < n |
Middle splice in insert_at |
Link nn between cur and cur->next; fix prev on both sides |
Insert at Position (SLL) has the same index idea without prev updates. · Insert at Beginning (DLL) · Insert at End (DLL)