单链表的排序问题

单链,排序,问题 · 浏览次数 : 152

小编点评

## 总结 这篇文章介绍了两种链表排序算法的实现: **方法一:转换数组结合快速排序** * 将链表转换成一个大小为 `n` 的数组。 * 使用快速排序算法对数组进行排序。 * 将排序后的数组反转返回链表中。 **方法二:合并两个有序链表** * 两个链表的长度必须一致。 * 创建一个新的链表,用于存储合并后的结果。 * 遍历两个链表,并根据链表中元素的排序关系,将它们合并到新的链表中。 * 返回最终的链表。 **时间复杂度分析** * **方法一:** 时间复杂度为 O(n log n),其中 n 是链表长度。这是因为快速排序算法的平均时间复杂度为 O(log n)。但是,最好情况下时间复杂度可以达到 O(n),如果链表随机地排序,则平均时间复杂度可能略高于 O(log n)。 * **方法二:** 时间复杂度为 O(m),其中 m 是两个链表长度的最小值。这是因为这个算法需要遍历两个链表,并根据链表中元素的排序关系,将它们合并到新的链表中。 **优点和缺点** **方法一:** * 简洁易懂。 * 性能比较良好,特别是当链表非常大时。 **方法二:** * 效率更高,特别是当两个链表长度相等时。 * 更容易理解和维护。 **建议** * 可以根据实际情况选择使用哪一种算法。 * 如果使用方法一,可以考虑使用快速排序的优化技术来提高性能。 * 如果使用方法二,可以考虑使用 merge() 函数的优化版本,例如使用快排算法。

正文

单链表的排序问题

作者:Grey

原文地址:

博客园:单链表的排序问题

CSDN:单链表的排序问题

题目链接

LeetCode 148. Sort List

思路一:转换数组结合快速排序

将链表转换成数组,使用快速排序算法,然后把数组排序后的结果还原成链表。

时间复杂度 O(n*logn),空间复杂度 O(n)。这个思路的核心就是快速排序算法,快速排序算法见如下博客荷兰国旗问题与快速排序算法说明,但是空间复杂度没有达到最优(最优解可以做到空间复杂度O(1)),完整代码如下:

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        int size = 0;
        ListNode cur = head;
        while (cur != null) {
            size++;
            cur = cur.next;
        }
        ListNode[] nodes = new ListNode[size];
        cur = head;
        int i = 0;
        while (cur != null) {
            nodes[i++] = cur;
            cur = cur.next;
        }
        sortArr(nodes);
        return arrToList(nodes);
    }

    private void sortArr(ListNode[] nodes) {
        p(nodes, 0, nodes.length - 1);
    }

    private void p(ListNode[] arr, int L, int R) {
        if (L >= R) {
            return;
        }
        swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
        int[] equalArea = netherlandsFlag(arr, L, R);
        p(arr, L, equalArea[0] - 1);
        p(arr, equalArea[1] + 1, R);
    }

    private int[] netherlandsFlag(ListNode[] nodes, int L, int R) {
        if (L > R) {
            return new int[]{-1, -1};
        }
        if (L == R) {
            return new int[]{L, R};
        }
        int less = L - 1;
        int more = R;
        ListNode num = nodes[R];
        for (int i = L; i < more; i++) {
            if (nodes[i].val < num.val) {
                swap(nodes, ++less, i);
            } else if (nodes[i].val > num.val) {
                swap(nodes, i--, --more);
            }
        }
        swap(nodes, R, more);
        return new int[]{less + 1, more};
    }

    public void swap(ListNode[] nodes, int i, int j) {
        if (i != j) {
            ListNode t = nodes[i];
            nodes[i] = nodes[j];
            nodes[j] = t;
        }
    }

    public ListNode arrToList(ListNode[] nodes) {
        ListNode head = nodes[0];
        ListNode cur = head;
        for (int i = 1; i < nodes.length; i++) {
            cur.next = nodes[i];
            cur = nodes[i];
        }
        cur.next = null;
        return head;
    }
}

思路二:使用归并排序

本题可以利用归并排序算法,在时间复杂度同样为O(n*logn)的情况下,空间复杂度可以达到O(1),本题参考了归并排序的迭代版本实现,归并排序算法的说明见如下博客与归并排序相关的一些问题

归并排序的迭代方法,思路如下

设置一个步长,从 1 开始,1,2,4,8,16……2^n 方式递增

每次处理对应步长的链表区间范围内的排序。

步长超过或者等于链表长度,则整个链表排序完成。

比如原始链表为: 1->3->4->2->5->6->4->6->8

先设置步长为 1,链表分成如下区间

[0……1],[2……3],[4……5],[6……7],[8……8]

注:最后一组不够分,则单独作为一组处理。

将如上区间内部排好序,得到的排序后的链表为

1->3,2->4,5->6,4->6,8

然后设置步长为 2,链表分成如下区间

[0……3],[4……7],[8……8]

然后将上述区间内部先排好序,得到链表为

1->2->3->4,4->5->6->6,8

然后设置步长为 4,链表分成如下区间

[0……7],[8……8]

然后将上述区间内部先排好序,得到链表为

1->2->3->4->4->5->6->6,8

最后设置步长为 8,链表只有一个区间,直接排序,得到最后结果

1->2->3->4->4->5->6->6->8

完整代码如下

class Solution {
    public  ListNode sortList(ListNode head) {
        int N = 0;
        ListNode cur = head;
        while (cur != null) {
            N++;
            cur = cur.next;
        }
        ListNode h = head;
        ListNode teamFirst = head;
        ListNode pre = null;
        int L = 1;
        while (L < N) {
            while (teamFirst != null) {
                ListNode[] hthtn = hthtn(teamFirst, L);
                ListNode[] mhmt = merge(hthtn[0], hthtn[1], hthtn[2], hthtn[3]);
                if (h == teamFirst) {
                    h = mhmt[0];
                    pre = mhmt[1];
                } else {
                    pre.next = mhmt[0];
                    pre = mhmt[1];
                }
                teamFirst = hthtn[4];
            }
            teamFirst = h;
            pre = null;
            L <<= 1;
        }
        return h;
    }

    public  ListNode[] hthtn(ListNode teamFirst, int len) {
        ListNode ls = teamFirst;
        ListNode le = teamFirst;
        ListNode rs = null;
        ListNode re = null;
        ListNode next = null;
        int p = 0;
        while (teamFirst != null) {
            // 之所以这里是小于等于,是因为这里可能不满足分组的个数(不足个数)
            if (p <= len - 1) {
                le = teamFirst;
            }
            if (p == len) {
                rs = teamFirst;
            }
            if (p > len - 1) {
                re = teamFirst;
            }
            if (p == (len << 1) - 1) {
                break;
            }
            p++;
            teamFirst = teamFirst.next;
        }
        if (le != null) {
            le.next = null;
        }
        if (re != null) {
            next = re.next;
            re.next = null;
        }
        return new ListNode[]{ls, le, rs, re, next};
    }

    // 返回merge后的头和尾
    // 注意边界考虑
    public  ListNode[] merge(ListNode h1, ListNode t1, ListNode h2, ListNode t2) {
        if (h2 == null) {
            return new ListNode[]{h1, t1};
        }
        ListNode head = h1;
        ListNode tail = h1;
        ListNode c = null;
        ListNode pre = null;
        while (h1 != t1.next && h2 != t2.next) {
            if (h1.val > h2.val) {
                c = h2;
                h2 = h2.next;
            } else {
                c = h1;
                h1 = h1.next;
            }
            if (pre == null) {
                // 设置merge后的头节点,赋值给head
                // 后续就由pre去往下插入节点
                pre = c;

                head = c;
            } else {
                pre.next = c;
                pre = c;
            }
        }
        // h1节点没越界
        if (h1 != t1.next) {
            while (h1 != t1.next) {
                pre.next = h1;
                pre = pre.next;
                tail = h1;
                h1 = h1.next;
            }
        } else {
            while (h2 != t2.next) {
                pre.next = h2;
                pre = pre.next;
                tail = h2;
                h2 = h2.next;
            }
        }
        return new ListNode[]{head, tail};
    }
}

更多

算法和数据结构笔记

与单链表的排序问题相似的内容:

单链表的排序问题

单链表的排序问题 作者:Grey 原文地址: 博客园:单链表的排序问题 CSDN:单链表的排序问题 题目链接 LeetCode 148. Sort List 思路一:转换数组结合快速排序 将链表转换成数组,使用快速排序算法,然后把数组排序后的结果还原成链表。 时间复杂度 O(n*logn),空间复杂

奇偶链表问题

奇偶链表问题 作者:Grey 原文地址: 博客园:奇偶链表问题 CSDN:奇偶链表问题 题目描述 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 请尝试使用原地算法完成。 你的算法的空间复杂度应为 O(1),

节点加入到单链表时按需求排序

JAVA实现节点加入到单链表时按需求排序 回顾 在上文《带头节点的单链表的思路及代码实现(JAVA)》中我们想要去实现让数据节点不考虑加入顺序实现数据节点排序效果。 那么我们要如何实现这一需求呢? 一、实现思路 ①理论思路 假设我们要根据数据节点的ID进行排序,那么我们可以通过使用待增加的节点id逐

带头节点的单链表的思路及代码实现

带头节点的单链表的思路及代码实现(JAVA) 一、什么是的单链表 ①标准定义 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) +指针(指示后继元素存储位置,元素就是存储数据的存储单元,指针就是连接每

JAVA实现单链表修改和删除数据节点

JAVA实现单链表修改和删除数据节点 一、修改单链表中的一个节点 ①实现思路 因为带头节点的链表中头节点的next域不能发生改变(始终指向单链表的头节点),否则将找不到该链表。所以我们需要先找一个辅助节点temp来进行节点代理操作。 通过遍历链表,使辅助节点temp后移,找到要修改的节点。 然后进行

文心一言 VS 讯飞星火 VS chatgpt (110)-- 算法导论10.2 1题

一、用go语言,单链表上的动态集合操作 INSERT 能否在 O(1)时间内实现?DELETE 操作呢? 文心一言: 在单链表中,INSERT 和 DELETE 操作的时间复杂度通常是 O(n),其中 n 是链表中的元素数量。这主要是因为当你插入或删除元素时,你需要遍历链表来找到正确的插入或删除位置

文心一言 VS 讯飞星火 VS chatgpt (112)-- 算法导论10.2 3题

三、用go语言,用一个单链表L实现一个队列。要求操作 ENQUEUE 和 DEQUEUE 的运行时间仍为O(1)。 文心一言: 在 Go 语言中,使用单链表实现队列的操作,包括入队(ENQUEUE)和出队(DEQUEUE),并保持操作的时间复杂度为 O(1),需要利用两个指针,一个指向队头,另一个指

寻找链表相交结点问题

寻找链表相交结点问题 作者:Grey 原文地址: 博客园:寻找链表相交结点问题 CSDN:寻找链表相交结点问题 题目描述 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回 null。 要求:如果两个链表长度

C++算法之旅、09 力扣篇 | 常见面试笔试题(上)算法小白专用

算法学习笔记,记录容易忘记的知识点和难题。详解时空复杂度、50道常见面试笔试题,包括数组、单链表、栈、队列、字符串、哈希表、二叉树、递归、迭代、分治类型题目,均带思路与C++题解

基于SqlSugar的开发框架循序渐进介绍(31)-- 在查询接口中实现多表联合和单表对象的统一处理

在一些复杂的业务表中间查询数据,有时候操作会比较复杂一些,不过基于SqlSugar的相关操作,处理的代码会比较简单一些,以前我在随笔《基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理》介绍过基于主表和中间表的联合查询,而往往实际会比这个会复杂一些。本篇随笔介绍联合多个表进行查询以及树形列表的条件展示的处理实现,系统能够给大家一些参考思路。