Microsoft Interview Question
Country: India
Interview Type: In-Person
The good news about using mergesort for linked lists is that it doesn't really require extra space like it does for arrays.
I believe this is the best answer since the merge operation works well in a singly linked list
void mergesort_linkedlist(Node* before_start, Node* start, Node* end) {
if (start == end) return; // sorted
// Find middle node
Node* before_middle = NULL;
Node* middle = start;
Node* curr = start;
while (curr && curr != end) {
curr = curr->next;
if (curr) {
curr = curr->next;
before_middle = middle;
middle = middle->next;
}
}
// Sort both ranges
mergesort_linkedlist(before_start, start, middle)
mergesort_linekdlist(before_middle, middle, end);
// Merge ranges
Node* before_insert = before_start;
Node* after_insert = start;
Node* p2 = middle;
while (!(p1 == middle && p2 == end)) {
if (p2->val < after_insert->val || after_insert == middle) {
Node* tmp = p2->next;
before_insert->next = p2;
p2->next = after_insert;
before_insert = p2;
p2 = tmp;
}
else {
before_insert = after_insert;
after_insert = after_insert->next;
}
}
}
Most people think you can't use quicksort. That is wrong. Quicksort works just fine. Simply scan through the list moving nodes with data value less than the pivot to a new list and nodes with data value greater than the pivot to another new list. Recursively sort the two new lists and then splice them together with the remains of the original list.
I dont understand why merge sort is cheaper than the insertion or selection or bubble sort in this situation. Since finding the mid of the list requires O(n)
int condition=0,count1=0,count2=0;
node *q;
node *p;
node*r;
node* merge(node* l1,node *l2,int n,int m)
{
if(l1!=NULL&&((count1<n&&count2==m)||((l1->data<=l2->data)&&count1<n)))
{
// cout<<"fdnhrn";
if(condition==0)
{
condition=1;
p=l1;
l1=l1->next;
q=p;
count1++;
}
else
{
// cout<<l1->data<<" "<<q->data;
q->next=l1;
l1=l1->next;
q=q->next;
count1++;
}
}
else if(l2!=NULL&&((count2<m&&count1==n)||(l1->data>l2->data&&count2<m)))
{
// cout<<"what";
if(condition==0)
{
condition=1;
p=l2;
l2=l2->next;
q=p;
count2++;
}
else
{
q->next=l2;
l2=l2->next;
q=q->next;
count2++;
}
}
if(count1==n&&count2==m)
{
q->next=NULL;
node*a1=p;
return p;
}
else
{
node*a1=p;
p=merge(l1,l2,n,m);
}
return p;
}
node* mergesort(node* q[],int start,int end,int n)
{
//cout<<q[0]->data<< " "<<q[2]->data;
node* l1=NULL;
node *l2=NULL;
if((n/2)>1)
l1=mergesort(q,start,(n/2),(n/2));
if((n+1)/2>1)
l2=mergesort(q,start+(n)/2,end,(n+1)/2);
if(n/2==1)
l1= q[start-1];
if((n+1)/2==1)
l2= q[start+(n)/2-1];
condition=0;
count1=0;count2=0;
node *a=merge(l1,l2,n/2,(n+1)/2);
node*a1=a;
return a;
}
void MergeSort(Node **before, Node **start, Node **end)
{
if(!*start || !*end) return; // in case of NULL
if(*start == *end) return; // there is only one element in the list
if((*start)->pNext == *end) // there are two elements in the list
{
Node *tmp = NULL;
if((*start)->iData > (*end)->iData) // exchange them if needed
{
tmp = *start; // it's important to change data by the given addresses
(*before)->pNext = (*end);
*start = *end;
*end = tmp;
(*end)->pNext = (*start)->pNext;
(*start)->pNext = *end;
}
return;
}
Node *pFast = *start, *middle = *start;
if(!*start || !(*start)->pNext || !(*start)->pNext->pNext)
{
middle = *start;
}
else
{
while(pFast && pFast != (*end)) // finding the middle
{
pFast = pFast->pNext;
if(pFast)
{
pFast = pFast->pNext;
if(pFast)
{
middle = middle->pNext;
}
}
}
}
MergeSort(before, start, &middle); // recursive call for the first part of the list
MergeSort(&middle, &(middle->pNext), end); // recursive call for the last part of the list
Node *pNewLast = NULL, *pLefLast = *start, *pRightLast = middle->pNext;
if(pLefLast->iData <= pRightLast->iData) // set the last elemnt for the new merged list
{ // At the moment it is the fisrt elemnt for the
pNewLast = pLefLast; // new merged list
pLefLast = pLefLast->pNext;
}
else
{
middle->pNext = pRightLast->pNext;
pNewLast = pRightLast;
(*before)->pNext = pRightLast;
pRightLast = pRightLast->pNext;
}
(*start) = (*before)->pNext; // it's important to set new data by the adress of the 'start' element
while(pLefLast != middle->pNext && pRightLast != NULL && pRightLast != (*end)->pNext)
{ // cycle to merge two sorted lists
if(pLefLast->iData <= pRightLast->iData)
{
pNewLast->pNext = pLefLast;
pNewLast = pNewLast->pNext;
pLefLast = pLefLast->pNext;
}
else
{
middle->pNext = pRightLast->pNext;
pNewLast->pNext = pRightLast;
pNewLast = pNewLast->pNext;
pRightLast = pRightLast->pNext;
}
}
if(pLefLast == middle->pNext) // merging the remainders if needed
{
pNewLast->pNext = pRightLast;
}
else if((pRightLast == (*end)->pNext)||(pRightLast == NULL))
{ // merging the remainders if needed
pNewLast->pNext = pLefLast;
(*end) = middle; //it's important to set new data by the adress of the 'end' element
}
}
void sort(){
Node *pEnd = NULL, *pAxPtr = new Node;
pEnd = LList->pHead;
while(pEnd->pNext)pEnd = pEnd->pNext;
pAxPtr->pNext = LList->pHead;
LList->MergeSort(&pAxPtr, &(LList->pHead), &pEnd);
LList->pHead = pAxPtr->pNext;
delete pAxPtr;
}
/* sorts the linked list by changing next pointers (not data) */
void MergeSort(struct node** headRef)
{
struct node* head = *headRef;
struct node* a;
struct node* b;
/* Base case -- length 0 or 1 */
if ((head == NULL) || (head->next == NULL))
{
return;
}
/* Split head into 'a' and 'b' sublists */
FrontBackSplit(head, &a, &b);
/* Recursively sort the sublists */
MergeSort(&a);
MergeSort(&b);
/* answer = merge the two sorted lists together */
*headRef = SortedMerge(a, b);
}
struct node* SortedMerge(struct node* a, struct node* b)
{
struct node* result = NULL;
/* Base cases */
if (a == NULL)
return(b);
else if (b==NULL)
return(a);
/* Pick either a or b, and recur */
if (a->data <= b->data)
{
result = a;
result->next = SortedMerge(a->next, b);
}
else
{
result = b;
result->next = SortedMerge(a, b->next);
}
return(result);
}
/* UTILITY FUNCTIONS */
/* Split the nodes of the given list into front and back halves,
and return the two lists using the reference parameters.
If the length is odd, the extra node should go in the front list.
Uses the fast/slow pointer strategy. */
void FrontBackSplit(struct node* source,
struct node** frontRef, struct node** backRef)
{
struct node* fast;
struct node* slow;
if (source==NULL || source->next==NULL)
{
/* length < 2 cases */
*frontRef = source;
*backRef = NULL;
}
else
{
slow = source;
fast = source->next;
/* Advance 'fast' two nodes, and advance 'slow' one node */
while (fast != NULL)
{
fast = fast->next;
if (fast != NULL)
{
slow = slow->next;
fast = fast->next;
}
}
/* 'slow' is before the midpoint in the list, so split it in two
at that point. */
*frontRef = source;
*backRef = slow->next;
slow->next = NULL;
}
}
Time Complexity: O(nLogn)
Yes the answer is Insertion Sort... For people who think otherwise, please take a look a MIT open course ware Algorithm videos on YouTube...
HeapSort maybe... First form the heap of the link-list using bubble_down() method for elements i = 1 to N (done is O(NLog(N)) time).
Then, extract mean (done in O(1) time) and use bubble_down() again to restore heap.
Runtime complexity is O(NLog(N)) and memory required is O(N). HeapSort is best, as far as Sorting is considered.
Heapsort is difficult to do on something that doesn't have random access like a linked list. And even if you could do it, why would it be the best?
Merge Sort
- Anonymous September 02, 2012