Sort List(四种算法)

Sort a linked list in O(n log n) time using constant space complexity.

算法一:快速排序。 因为链表是单向,所以有很多细节要注意,比如quick_sort(p,r)排序的区间其实是[p->next,r->next],因为单向链表不能回头。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    //p,r分别为欲排序的子链表的首尾节点的前驱
    void quick_sort(ListNode *p,ListNode *r){
        
        if(p==r ||r == NULL || p==r->next) return;
        ListNode *k = p,*pk = NULL; //pk为k的前驱
        ListNode *q,*tail = r->next;
        
        //注意r在迭代过程中可能会被改变,因此控制条件不能为q!=r,tail是迭代过程中是不会被改变的
        for(q=p;q->next!=tail;){
            if(q->next->val <= tail->val){ 
                bool flag = (q==k->next);
                Swap(k,q);  //交换k->next与q->next节点
                pk = k; k = k->next; 
                if(!flag) q = q->next; //若k,q相邻则swap后q会后移一位,此时无需令q=q->next,注意k,q都是欲交换节点的前驱,非节点本身
            }
            else q = q->next;
        }
        
        bool flag = (k->next==q || k==q);
        Swap(k,q); 
        quick_sort(p,pk);
        if(!flag) quick_sort(k->next,q);
    }
    
    //当交换相邻的节点时要特别考虑
    void Swap(ListNode *p, ListNode *r){
        if(p==r) return;
        else if(p==r->next) Swap(r,p);
        else if(r==p->next){
            ListNode *ssr = r->next->next;
            p->next = r->next;
            r->next->next = r;
            r->next = ssr;
        }
        else{
            ListNode *sp = p->next, *ssp = sp->next, *ssr = r->next->next;
            p->next = r->next; r->next->next = ssp;
            r->next = sp; sp->next = ssr;
        }
    }
    
    ListNode *sortList(ListNode *head) {
        
        if(head==NULL || head->next==NULL) return head;
        
        ListNode* tmp_head = new ListNode(0);
        tmp_head->next = head;   //构造头节点的前驱
        ListNode* ptail = tmp_head;   //ptail为尾节点的前驱
        while(ptail->next->next) ptail = ptail->next; 
        quick_sort(tmp_head,ptail);
        
        ListNode* new_head = tmp_head->next;
        delete tmp_head;
        return new_head;
    }
};

算法二: 改进版快速排序。  快速排序在数据随机分布情况下速度较快,但在本题中会超时。查看超时的test case,发现有大量重复值。于是想到改进的算法,抛弃传统的两段划分,采用三段划分。{x:x < tail->val}, {y:y == tail->val}, {z:z > tail->val}. 每次分割后对中间段不再调用quick_sort。改进后可以AC

改进后的快排算法:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
//p,r分别为欲排序的子链表的首尾节点的前驱
//每轮递归后都保持格局{x:x->val < tail->val},{y:y->val == tail->val}, {z:z->val > tail->val}
void quick_sort(ListNode *p,ListNode *r){

	if(p==r ||r == NULL || p==r->next) return;
	//k1指向等于tail->val的连续节点段首的前驱,若无此连续段,则k1与q同
	//k2指向大于tail->val的连续节点段首的前驱,若无此连续段,则k2与q同
	ListNode *k1 = p, *k2 = p, *pk1 = NULL, *pk2 = NULL; //pk为k的前驱
	ListNode *q,*tail = r->next,*pq = NULL;

	//注意r在迭代过程中可能会被改变,因此控制条件不能为q!=r,tail是迭代过程中是不会被改变的
	for(q=p;q->next!=tail;){
		if(q->next->val < tail->val){ 
			bool flag1 = (q==k1->next);
			
			if(!flag1 && k1->next == k2) k2 = q->next;  //若k2是k1的后继,且大于tail->val的节点真实存在

			Swap(k1,q);  //交换k1->next与q->next节点,将小于tail->val的节点移到等于tail->val的节点的前面,或保持不变(交换相同节点)
			pk1 = k1; k1 = k1->next; 

			if(pk1==k2 || k2==q){ //大于、等于tail->val的节点只现其一					
				if(k2!=q){ //即k1==k2,只出现大于tail->val的节点,没出现过等于tail->val的
					k2 = k1; 
					if(!flag1) q = q->next; //若flag1==true说明Swap时q已经向后移了一位,不用再右移
				}  
				else{ //k1<k2的情况,只出现过等于tail->val的节点,没出现过大于tail->val的
					if(!flag1){  q = q->next; k2 = q;} 
				}
			} 

			//若在前面同时出现了大于、等于tail->val的两种节点,则还要再将等于tail->val的节点与大于tail->val的节点交换	
			else{ 		
				bool flag2 = (q==k2->next);  
				Swap(k2,q);  
				k2 = k2->next; 
				if(!flag2) q = q->next;
			}	
		}

		else if(q->next->val == tail->val){ //将等于tail->val的节点移到大于tail->val的节点的前面
			bool flag2 = (q==k2->next);
			Swap(k2,q);
			k2 = k2->next;
			if(!flag2) q = q->next;
		}

		else q = q->next;
	}

	bool flag = (k2->next==q || k2==q);
	Swap(k2,q); 
	quick_sort(p,pk1);
	if(!flag) quick_sort(k2->next,q);
}

//当交换相邻的节点时要特别考虑
void Swap(ListNode *p, ListNode *r){
	if(p==r) return;
	else if(p==r->next) Swap(r,p);
	else if(r==p->next){
		ListNode *ssr = r->next->next;
		p->next = r->next;
		r->next->next = r;
		r->next = ssr;
	}
	else{
		ListNode *sp = p->next, *ssp = sp->next, *ssr = r->next->next;
		p->next = r->next; r->next->next = ssp;
		r->next = sp; sp->next = ssr;
	}
}

ListNode *sortList(ListNode *head) {

	if(head==NULL || head->next==NULL) return head;

	ListNode* tmp_head = new ListNode(0);
	tmp_head->next = head;   //构造头节点的前驱
	ListNode* ptail = tmp_head;   //ptail为尾节点的前驱
	while(ptail->next->next) ptail = ptail->next; 
	quick_sort(tmp_head,ptail);

	ListNode* new_head = tmp_head->next;
	delete tmp_head;
	return new_head;
}
};


算法三:递归版归并排序。 除了快速排序,当然也可以用归并排序。链表的归并排序与数组的归并排序唯一不同是数组可以在O(1)时间取到中点,但链表要花费O(n),有人因此就认为归并排序不适合链表了,其实不然。即使找中点要花费O(n)又怎样呢?事实是不管是数组还是链表,都要有个合并过程,而合并的时间复杂度就是O(n),因此取中点的复杂度是O(1)还是O(n)都不会影响到整个排序的时间复杂度,链表和数组的归并排序时间复杂度上仅在于系数的差别而已。都是O(n*log n)


/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:

typedef pair<ListNode*,ListNode*> pair_node;

pair_node merge_sort(ListNode* first,ListNode* last){
    if(first==last || first->next==last) return make_pair(first,last);
    ListNode *fast = first, *slow = first;
    while(fast!=last && fast->next!=last){
        fast = fast->next->next;
        slow = slow->next;
    }
    
    pair_node list1 = merge_sort(first,slow);
    pair_node list2 = merge_sort(slow,last);
    return merge(list1,list2);
}

pair_node merge(pair_node list1,pair_node list2){
    ListNode *first;
    ListNode *cur,*p = list1.first, *q = list2.first;
    if(p->val <= q->val){
        cur = first = p;
        p = p->next;
    }
    else{
        cur = first = q;
        q = q->next;
    }
    while(p!=list1.second && q!=list2.second){
        if(p->val <= q->val){
            cur->next = p; cur = p;
            p = p->next;
        }
        else{
            cur->next = q; cur = q;
            q = q->next;
        }
    }
    if(p!=list1.second){ 
        cur->next = p;
        while(p->next!=list1.second) p = p->next;
        p->next = list2.second;
    }
    else cur->next = q;
    return make_pair(first,list2.second);
}

ListNode *sortList(ListNode *head) {
    if(head==NULL || head->next==NULL) return head;
    return merge_sort(head,NULL).first;
}
};

因为用归并排序并不像快排在找分割点的过程中需要交换节点,所以在处理上要比快排容易,只需用一个首节点和尾节点的后继表示链表就可以了。


算法四:非递归归并排序。上面的归并排序是采用传统的递归实现。归并排序也可以用迭代实现,实质是递归的逆向实现。其思路是这样,假设有n个节点,从第一个节点1开始,发现此前没有已经排好的长度为1的链表(即单个节点),到2时发现此前有单节点1,则把(1,2)合并成长度为2的已排序链表,再看此前有没有长度为2的已排序的子链表,没有。再取3,发现此前无单节点(1,2已经合并),再取4,发现有单节点3,合并(3,4),合并后长度为2,再查找此前有没有长度为2的已排序子链表,刚好有!于是将子链表(1,2)与子链表(3,4)合并,合并后长度为4......


/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:

ListNode* merge(ListNode *list1,int len1,ListNode *list2,int len2){
    if(len1==0) return list2;
    if(len2==0) return list1;
    int i=0,j=0;
    
    ListNode *res,*cur,*p=list1,*q=list2;
    if(p->val <= q->val){ res = cur = p; p = p->next; i++; }
    else{ res = cur = q; q = q->next; j++; }
    
    while(i<len1 && j<len2){
        if(p->val <= q->val){
            cur->next = p; cur = p;
            i++;  p = p->next; 
        }
        else{
            cur->next = q; cur = q;
            j++;  q = q->next;
        }
    }
    if(i<len1){
        cur->next = p; 
        while(++i<len1) p = p->next;
        p->next = q;
    }
    else cur->next = q;
    return res;
}

ListNode *sortList(ListNode *head) {
    if(head==NULL || head->next==NULL) return head;
    
    ListNode* tab[64];  //tab[i]表示从tab[i]开始的连续2^i个节点已经排好序,各tab[i]之间没有交集
    for(int i=0;i<64;i++) tab[i] = NULL;
    
    int k = 0;   //k表示tab[k-1]是最后一个非空的节点指针
    ListNode *cur = head;
    ListNode *carry;
    while(cur!=NULL){
        carry = cur;  
        cur = cur->next;
        for(int i=0;i<=k;i++){
            if(tab[i]==NULL){ //第一次出现的为NULL的tab[i],则将其指向为之前合并的链表的首节点
                tab[i] = carry;
                if(i==k) k++;
                break;
            }
            else{
                int len = 1<<i;
                carry = merge(tab[i],len,carry,len); //合并长度相同的已排序链表 
                tab[i] = NULL; //清空tab[i]
            }
        }
    }
    
    carry = NULL;
    int curLen = 0;
    //合并tab中的零散子链表
    for(int i=0;i<k;i++){
        if(tab[i]!=NULL){
            carry = merge(tab[i],1<<i,carry,curLen);
            tab[i] = NULL;
            curLen += (1<<i);
        }
    }
    
    return carry;
}
};




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值