单链表排序

本文介绍了如何在O(n log n)的时间复杂度内对单链表进行排序,详细解析了使用归并排序的递归和非递归解法,包括快慢指针找中间节点、链表合并等关键步骤。

问题:给定一串单链表,在O(n log n)的时间对链表进行排序。

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

解法

  1. 使用归并排序,时间复杂度 nlogn。
  2. 逐渐分层排序,譬如单链表(3,2,1,4)

在这里插入图片描述

先底层合并使其有序

在这里插入图片描述

然后同层两两合并

在这里插入图片描述

递归解法

递归的关键是要找出中间的位置,这里可以使用快慢指针
快指针*quick和慢指针*slow,快指针一次走两步,慢指针一次走一步,当快指针到尾的时候,慢指针所指的位置就是链表的中间位置。

首先是合并的方法ListNode *merge(ListNode *left, ListNode *right)

先定义一个头节点headNode方便操作链表,通过改变节点的next指向,合并成一条有序的链。这里需要用到三个指针leftrightp

在这里插入图片描述

先指向最小值
在这里插入图片描述

改变next指向
在这里插入图片描述
改变next指向
在这里插入图片描述

ListNode *merge(ListNode *left, ListNode *right) {
    //头节点,方便操作链表
    ListNode headNode(0);
    ListNode *p = &headNode;
    
    while(left && right) {
        if(left->val < right->val) {
            p->next = left;
            left = left->next;
        }
        else {
            p->next = right;
            right = right->next;
        }
        p = p->next;
    }
    p->next = (left==NULL) ? right : left;
    return headNode.next;
}

主函数:

ListNode *sortList(ListNode *head) {
        if (head!=NULL || !head->next!=NULL) 
            return head;
            
        // 计算中间指针位置
        ListNode *slow = head;
        ListNode *quick = head->next;
        while(slow!=NULL && quick->next!=NULL) {
            slow = slow->next;
            quick = quick->next->next;
        }
        
        //这里需要把链切断,merge再重新合并
        ListNode* right = sortList(head);
        ListNode* right = sortList(slow->next);
        slow->next = NULL;
        
        return merge(left, right);
}

非递归解法

递归解法的空间复杂度并不是常数级,可以使用非递归的解法达到空间复杂度的常数级。

遍历链表,通过不同的跳数来分割链,再把分割出来的链两两合并。

step1: (3),(2),(1),(4)

step2: (2, 3),(1, 4)

setp4: (1, 2, 3, 4)

这里需要增加两个方法,一个是ListNode *cutList(ListNode *head, int step),一个是ListNode *merge(ListNode *left, ListNode *right)

  1. cutList的作用是根据跳数来分割链并返回下一条链的头指针,譬如一个指向头结点3的指针head,跳数1,则从该链的head后1位开始切断,并且返回最后位的下一个结点。
    在这里插入图片描述

step = 1

在这里插入图片描述

  1. merge的作用和递归解法的作用相同:
ListNode *cutList(ListNode *head, int step)
{
    //返回head之后size个node的指针位置,并且把前链切断
    ListNode *p = head;
    while(--step && p!=NULL)
    {
        p = p->next;
    }
    
    if(p == NULL)
        return NULL;
    
    ListNode *q = p->next;
    p->next = NULL;
    return q;
}

ListNode *merge(ListNode *left, ListNode *right)
{
    ListNode headNode(0);
    ListNode *p = &headNode;
    while(left!=NULL && right!=NULL)
    {
        if(left->val > right->val)
        {
            p->next = right;
            p = right;
            right = right->next;
        }
        else
        {
            p->next = left;
            p = left;
            left = left->next;
        }
    }
    p->next = (left==NULL) ? right : left;
    return headNode.next;
}

主函数主要也是使用3个指针:leftrightflagNode。其中leftright分别代表左链和右链,而flagNode则是记录下一次分割链的位置。

譬如当step=2时,left指向1,right指向3,而flagNode则指向5,在leftright合并完成之后直接可以赋值到left链中进行下一轮的合并。

ListNode *sortList(ListNode *head) 
{
    if(!head || !head->next)
        return head;
    
    //设定一个头结点方便操作
    ListNode headNode(0);
    headNode.next = head;
    
    //计算出链表的长度
    int length=0;
    ListNode *p = head;
    while(p != NULL)
    {
        length++;
        p = p->next;
    }
    
    //根据跳数分段合并
    for(int step=1; step<length; step *= 2)
    {
        ListNode* flagNode = headNode.next;
        ListNode* tailNode = &headNode;
        
        while(flagNode != NULL)
        {
            ListNode* left = flagNode;
            ListNode* right = cutList(flagNode, step);
            flagNode = cutList(right, step);
            
            tailNode->next = merge(left, right);
            while(tailNode->next != NULL)
                tailNode = tailNode->next;
        }
    }
    return headNode.next;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值