Bifurcated Insertion Sort
Updated on: November 22, 2025
This algorithm performs sorting by splitting the input into two linked lists: asc_array (ascending)
and dsc_array (descending).
Background
I wanted to design sorting algorithm. Since I can't solve everything in one attempt, I allowed myself to ignore time and space complexity in this version, and I used a few known techniques inside the flow when needed.
At a high level, this algorithm takes a different approach from traditional sorts: it begins by identifying the smallest and largest values and builds two linked lists from opposite ends - one in ascending order and one in descending order. Values that don't fit directly into either list are collected in a pending buffer, which is later inserted using a BST-guided strategy. Finally, both lists are merged to form the fully sorted output.
How It Works
1. Identify Min and Max
The algorithm first finds the minimum and maximum values in the input array. These two values become the starting points of the two linked lists:
asc_arraystarts with the minimum value.dsc_arraystarts with the maximum value.
Then it loops through the remaining items. If the current index is the min or max index, it skips that item.
2. Distributing Elements into the Two Lists
For each item:
- If the last item in
asc_arrayis less than or equal to the current value, it belongs in the ascending list. - If the last item in
dsc_arrayis greater than or equal to the current value, it belongs in the descending list.
If the item fits both conditions, it is appended to the list that is currently smaller.
If both lists have the same size, it goes into asc_array.
If the item fits neither list, it is placed into pending_array.
3. The Pending Array
pending_array holds items that cannot be directly inserted into either linked list.
It uses a parameter called pending_item_percentage. This determines when the algorithm should
process the pending items.
Formula:
pending_item_size = max(int(input_arr_len Ć pending_item_percentage), 6)
This ensures the pending list has a minimum size of 6, but can go up to a percentage of the total input size.
When pending_array reaches this size, the algorithm processes all those items.
4. Processing Pending Items
When pending_array reaches the limit:
- The algorithm selects the smaller of the two linked lists (
asc_arrayordsc_array). - It creates a temporary BST (binary search tree).
- For each item in
pending_array:- It looks for the nearest value inside the BST.
- If that is not available, it starts from the tail of the chosen linked list and moves backward until it reaches the correct insertion point.
- The item is then inserted in order into the linked list.
- The BST stores the value along with a reference to the inserted linked-list node so that consecutive items can start from a closer position.
After all pending items are processed, the pending list becomes empty. If there are still leftover items at the end of the full array scan, the same process repeats.
5. Final Merge
Once all items are inserted, we have:
- One sorted list in ascending order
- One sorted list in descending order
The final step is similar to merge sort: Iterate through both lists simultaneously and merge them into one final sorted array.
Detailed Example: Step-by-Step Execution
Input Array:
[15, 3, 8, 1, 12, 6, 20, 4, 18, 2, 14, 10]
Configuration:
pending_item_percentage = 0.55
Phase 1: Initialization
Step 1.1 - Find Min and Max
Scanning the array:
min_value = 1 (index 3)
max_value = 20 (index 6)
Step 1.2 - Initialize Data Structures
asc_array = [1] // ascending linked list
dsc_array = [20] // descending linked list
pending_items = []
pending_item_size = max(int(12 Ć 0.55), 6) = 6
Initial State:
asc_array: [1]
dsc_array: [20]
pending_items: []
Phase 2: Three-Way Streaming Classification
We iterate through the array, skipping indices of min (3) and max (6).
Index 0 ā value = 15
Checks:
- asc.last() = 1
- dsc.last() = 20
- 1 ⤠15 ⤠20 ā YES
Decision: fits middle band ā append to smaller list
Sizes equal ā append to asc_array
asc_array: [1, 15]
dsc_array: [20]
pending: []
Index 1 ā value = 3
Checks:
- 1 ⤠3 ⤠20 ā NO
- 1 ⤠3 ā NO
- 20 ā„ 3 ā YES
Decision: fits descending side only ā append to dsc_array
asc_array: [1, 15]
dsc_array: [20, 3]
pending: []
Index 2 ā value = 8
Checks fail for both ends ā add to pending
pending: [8]
Index 3 ā value = 1 ā SKIP (min)
Index 4 ā value = 12
Fails all checks ā pending
pending: [8, 12]
Index 5 ā value = 6
Fails all checks ā pending
pending: [8, 12, 6]
Index 6 ā value = 20 ā SKIP (max)
Index 7 ā value = 4
Fails all checks ā pending
pending: [8, 12, 6, 4]
Index 8 ā value = 18
Checks:
- 15 ⤠18 ā YES
- 3 ā„ 18 ā NO
Decision: fits ascending ā append to asc_array
asc_array: [1, 15, 18]
pending: [8, 12, 6, 4]
Index 9 ā value = 2
Fits descending only ā append to dsc_array
dsc_array: [20, 3, 2]
Index 10 ā value = 14
Fails all checks ā pending
pending: [8, 12, 6, 4, 14]
Index 11 ā value = 10
Fails all checks ā pending
pending: [8, 12, 6, 4, 14, 10]
Threshold reached!
len(pending) = 6 ā processing required
Phase 3: BST-Accelerated Batch Insertion
Step 3.1 - Choose Target List
len(asc_array) = 3
len(dsc_array) = 3
ā equal ā choose asc_array
Step 3.2 - Process pending items: [8, 12, 6, 4, 14, 10]
asc_array currently:
[1, 15, 18]
Important:
The BST is built incrementally during insertion. It stores the DATA VALUE as the key and the LINKED LIST NODE REFERENCE as the value. This helps find good starting points for subsequent insertions.
Insert 8
BST is currently empty
ā No hint available, start from tail (node containing 18)
Walk backward through linked list:
Node(18): Is 18 ā„ 8? YES ā move to prev
Node(15): Is 15 ā„ 8? YES ā move to prev
Node(1): Is 1 ā„ 8? NO ā STOP
Insert 8 between Node(1) and Node(15)
Add to BST: BST[8] = reference to Node(8)
Result: [1, 8, 15, 18]
BST state: {8: Node(8)}
Insert 12
BST.find_nearest(12, find_smaller=False)
ā Searches BST for smallest value ā„ 12
ā BST currently has: {8}
ā 8 < 12, so no value ā„ 12 exists yet
ā Returns None
Since no hint, start from tail (node containing 18)
Walk backward:
Node(18): Is 18 ā„ 12? YES ā move to prev
Node(15): Is 15 ā„ 12? YES ā move to prev
Node(8): Is 8 ā„ 12? NO ā STOP
Insert 12 between Node(8) and Node(15)
Add to BST: BST[12] = reference to Node(12)
Result: [1, 8, 12, 15, 18]
BST state: {8: Node(8), 12: Node(12)}
Insert 6
BST.find_nearest(6, find_smaller=False)
ā Searches for smallest value ā„ 6
ā BST has: {8, 12}
ā Smallest value ā„ 6 is 8
ā Returns Node(8) reference
Start from Node(8)
Walk backward:
Node(8): Is 8 ā„ 6? YES ā move to prev
Node(1): Is 1 ā„ 6? NO ā STOP
Insert 6 between Node(1) and Node(8)
Add to BST: BST[6] = reference to Node(6)
Result: [1, 6, 8, 12, 15, 18]
BST state: {6: Node(6), 8: Node(8), 12: Node(12)}
Insert 4
BST.find_nearest(4, find_smaller=False)
ā Smallest value ā„ 4 is 6
ā Returns Node(6) reference
Start from Node(6):
Node(6): Is 6 ā„ 4? YES ā move to prev
Node(1): Is 1 ā„ 4? NO ā STOP
Insert 4 between Node(1) and Node(6)
Add to BST: BST[4] = reference to Node(4)
Result: [1, 4, 6, 8, 12, 15, 18]
BST state: {4, 6, 8, 12: Node refs}
Insert 14
BST.find_nearest(14, find_smaller=False)
ā Searches for smallest value ā„ 14
ā BST has: {4, 6, 8, 12}
ā All values < 14, no value ā„ 14 found
ā Returns None
Start from tail (Node 18):
Node(18): Is 18 ā„ 14? YES ā move to prev
Node(15): Is 15 ā„ 14? YES ā move to prev
Node(12): Is 12 ā„ 14? NO ā STOP
Insert 14 between Node(12) and Node(15)
Add to BST: BST[14] = reference to Node(14)
Result: [1, 4, 6, 8, 12, 14, 15, 18]
BST state: {4, 6, 8, 12, 14: Node refs}
Insert 10
BST.find_nearest(10, find_smaller=False)
ā Smallest value ā„ 10 is 12
ā Returns Node(12) reference
Start from Node(12) :
Node(12): Is 12 ā„ 10? YES ā move to prev
Node(8): Is 8 ā„ 10? NO ā STOP
Insert 10 between Node(8) and Node(12)
Add to BST: BST[10] = reference to Node(10)
Result: [1, 4, 6, 8, 10, 12, 14, 15, 18]
BST state: {4, 6, 8, 10, 12, 14: Node refs}
How BST Optimization Helps:
| Insert | Without BST | With BST Hint | Savings |
|---|---|---|---|
| 8 | Walk 3 nodes | Walk 3 nodes | 0 (BST empty) |
| 12 | Walk 3 nodes | Walk 3 nodes | 0 (no hint ā„12) |
| 6 | Walk 4 nodes | Walk 1 node | Saved 3 walks |
| 4 | Walk 5 nodes | Walk 1 node | Saved 4 walks |
| 14 | Walk 2 nodes | Walk 2 nodes | 0 (no hint ā„14) |
| 10 | Walk 5 nodes | Walk 1 node | Saved 4 walks |
Total walks: 22 nodes without BST ā 11 nodes with BST = 50% reduction!
Step 3.3 - Clear pending buffer
pending_items = []
State after batch insertion:
asc_array: [1, 4, 6, 8, 10, 12, 14, 15, 18]
dsc_array: [20, 3, 2]
Phase 4: Final Processing
No pending items left.
Phase 5: Bidirectional Merge
Iterators:
asc_arrayā 1, 4, 6, 8, 10, 12, 14, 15, 18dsc_array(reverse) ā 2, 3, 20
Merge:
1 vs 2 ā 1
4 vs 2 ā 2
4 vs 3 ā 3
4 vs 20 ā 4
6 vs 20 ā 6
8 vs 20 ā 8
10 vs 20 ā 10
12 vs 20 ā 12
14 vs 20 ā 14
15 vs 20 ā 15
18 vs 20 ā 18
ASC done ā take 20
Final Output
[1, 2, 3, 4, 6, 8, 10, 12, 14, 15, 18, 20]
Summary: What This Example Demonstrated
| Phase | Example Showed |
|---|---|
| Initialization | Finding min=1, max=20 and creating dual sequences |
| Classification | Element 15 fitting middle zone (load balancing) |
| Element 18 fitting ascending end only | |
| Element 2 fitting descending end only | |
| Elements 8,12,6,4,14,10 fitting neither (pending) | |
| Batch Insertion | BST providing hints to reduce linked list walks |
| Inserting 6 pending items into asc_array | |
| BST optimization: Insert 10 started from node(12), not tail | |
| Merge | Bidirectional merge reading dsc_array backwards (2ā3ā20) |
Time & Space Complexity
Time Complexity
| Case | Complexity |
|---|---|
| Worst Case | O(n²) |
| Average Case | O(n log n) |
| Best Case | O(n) |
Space Complexity
| Metric | Complexity |
|---|---|
| Space | O(n) |