面试题精选:单链表排序也能玩出花来

今天国庆节,祝你们中秋节快乐,顺便给你们拜个早年[狗头]。不过最近还在准备面试的同窗们不要浪太狠,仍是要好好学习的鸭。html

单链表的排序在数据结构类的面试题中简直是集大成者,什么排序、链表、链表删除、添加…… 都能体如今单链表排序上,也很是考验候选者的编程基本功,思路提及来很简单,但能不能写出来又是另一回事了。java

有些人以为面试面这种题意义不大,谁实际工做中会写过单链表的排序,不都是直接调Collections.sort()吗? 是,没错 是这样,也许对某些人而言,他会这道题和不会这道题对未来的工做产生不了任何影响,这就须要很是长的时间去验证了,显然招聘者等不了那么久,他们只想花最少的时间找到你的上限,摸到你的上限后他们就能够简单假设这条线下面的其余东西你都会了,虽然这种假设有局限性,会让那种恰巧准备了的人占了便宜,但这种方法却不失为成本最低的方法。这就比如高考同样,高考所考的内容大多数人一生都用不上,但高考仍有存在的意义。面试

扯远了,回到正题,单链表排序设计到的知识点都是大学本科数据结构里讲过的,因此对应届生而言这题彻底不会超纲。对面试官而言,你能解释清楚思路 说明你在校数据结构学的还能够,你再能把你思路写出来,就能向面试官证实你编程能力能够。 (这里有个面试小技巧:知道思路不会写,先把思路给面试官讲一遍,你考数学写个解:还能得0.5分呢)算法

单链表排序能够用哪些排序算法? 个人回答是全部排序算法均可以用,但有些排序会相对简单些,本文我给出三种(选择、快排、归并)方法,剩余的几种排序算法有兴趣你能够本身实现下,固然有些可能会比较繁琐,是时候挑战下本身了[狗头]。这里我简化下题目,节点值为int整数,而后链表按增序排列。编程

这里先给出单链表节点类数据结构

public class LinkedNode {
    public int val;
    public LinkedNode next;
    public LinkedNode() {
        this(-1);
    }
    public LinkedNode(int val) {
        this.val = val;
    }
}

选择排序

选择排序的思路也很简单,每次从原链表中摘掉最小的一个节点,拼接到新链表中,直到原链表摘干净。ide

public class SelectSort implements SortStrategy {
    @Override
    public LinkedNode sort(LinkedNode head) {
        LinkedNode vHead = new LinkedNode(-1);
        vHead.next = head;
        // 增长虚拟头节点,方便操做,不然就须要用一堆if来判断了,代码会比较啰嗦 
        LinkedNode newHead = new LinkedNode(-1); 
        LinkedNode tail = newHead;  // tail指向新链表的末尾 
        // 每次从链表中摘出来最小的节点,拼接到新链表末尾
        while (vHead.next != null) {
            LinkedNode pre = vHead;
            LinkedNode cur = head;
            LinkedNode min = head;
            LinkedNode minPre = vHead;
            // 先遍历找最小的节点,记录下最小节点和它前面一个节点
            while (cur != null) {
                if (cur.val < min.val) {
                    minPre = pre;
                    min = cur;
                }
                pre = cur;
                cur = cur.next;
            }
            // 把min节点从原链表中摘除,并拼接到新链表中  
            tail.next = min;
            tail = tail.next;
            minPre.next = min.next;
        }
        return newHead.next; 
    }
}

归并

我我的感受归并实际上是最适合作单链表排序的算法,虽然代码稍微长有些,但思路清晰、好理解,并且时间复杂度只有O(nlogn)。归并的思路能够分为3个部分。学习

  1. 把链表分红两个链表;
  2. 分别对两个链表排序(能够递归作归并);
  3. 合并两个有序的单链表;
    在这里插入图片描述
    如图所示,红色为未排序链表,蓝色为排序后的链表,红色部分从上往下是拆分的过程,蓝色部分从上往下是合并的过程。 代码实现以下:
public class MergeSort implements SortStrategy {
    @Override
    public LinkedNode sort(LinkedNode head) {
        // 递归边界,若是有链表只有一个节点就不必排序了  
        if (head == null || head.next == null) {
            return head;
        }
        // 新建了个头节点方便处理,不然就须要不少if来判断了
        LinkedNode l1 = new LinkedNode();
        LinkedNode l2 = new LinkedNode();
        LinkedNode p1 = l1;
        LinkedNode p2 = l2;
        LinkedNode p = head;
        // 将原链表一分为二,奇数编号节点在l1,偶数编号在l2
        while (p != null) {
            LinkedNode pnn = null;
            if (p.next != null) {
                pnn = p.next.next;
            }
            p1.next = p;
            p1 = p1.next;
            if (p.next != null) {
                p2.next = p.next;
                p2 = p2.next;
                p2.next = null;
            }
            p1.next = null;
            p = pnn;
        }
        // 递归将两个链表作归并排序.
        l1 = sort(l1.next);
        l2 = sort(l2.next);
        // 合并两个排序好的有序链表
        return merge(l1, l2);
    }

    // 合并两个有序链表 
    private LinkedNode merge(LinkedNode l1, LinkedNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        LinkedNode newHead = new LinkedNode();
        LinkedNode p = newHead;
        LinkedNode p1 = l1;
        LinkedNode p2 = l2;

        while (p1 != null && p2 != null) {
            if (p1.val < p2.val) {
                p.next = p1;
                p1 = p1.next;
            } else {
                p.next = p2;
                p2 = p2.next;
            }
            p = p.next;
        }
        while (p1 != null) {
            p.next = p1;
            p1 = p1.next;
            p = p.next;
        }
        while (p2 != null) {
            p.next = p2;
            p2 = p2.next;
            p = p.next;
        }
        return newHead.next;
    }
}

快排

快排总体的思路和归并差很少,都是拆分、递归、合并,但其拆分就要比归并的拆分策略复杂些。在上文归并算法中,咱们只是简单将链表按奇偶变化拆分红了两个链表,但快排的拆分须要选择一个节点做为基准值,比它小的拆到左链表,反之的拆到右链表,而后递归对左右两个链表排序,最后合并。但它的合并就简单了,只须要 左链表+基准节点+又链表简单拼接在一块儿就能够了。
在这里插入图片描述ui

如图所示,黄色为我选中的基准节点(链表的头节点),红色为未排序链表,蓝色为排序后的链表,红色部分从上往下是拆分的过程,蓝色部分从上往下是合并的过程。具体代码实现以下:this

public class QuickSort implements SortStrategy {
    @Override
    public LinkedNode sort(LinkedNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        LinkedNode left = new LinkedNode();
        LinkedNode right = new LinkedNode();
        LinkedNode p1 = left;
        LinkedNode p2 = right;
        LinkedNode p = head.next;
        LinkedNode base = head;  // 选取头节点为基准节点
        base.next = null;
        // 剩余节点中比基准值小就放left里,不然放right里,按照大小拆分为两条链表
        while (p != null) {
            LinkedNode pn = p.next;
            p.next = null;
            if (p.val < base.val) {
                p1.next = p;
                p1 = p1.next;
            } else {
                p2.next = p;
                p2 = p2.next;
            }
            p = pn;
        }
        // 递归对两条链表进行排序
        left.next = sort(left.next);
        right.next = sort(right.next);
        // 先把又链表拼到base后面 
        base.next = right.next;
        // 左链表+基准节点+右链表拼接,左链表有多是空,因此须要特殊处理下
        if (left.next != null) {
            p = left.next;
            // 找到左链表的最后一个节点  
            while (p.next != null) {
                p = p.next;
            }
            // 把base拼接到左链表的末尾  
            p.next = base;
            return left.next;
        } else {
            return base;
        }
    }
}

面试题扩展

面试官也是要偷懒的,他们也懒得想题,再加上人的思惟是具备连续性的,这就意味着大几率下一道面试题(若有)会和这道题相关,我总结这道题能够扩展的3个关键词单链表、排序、归并,基本上下一题都是这三个词的发散,这里我说下我能够发散出的题目。

  1. 单链相关的题,已经烂大街了,具体参考leetcode top100 链表题
  2. 排序相关:第k大的数,上文中快排可能出现的问题以及如何解决?(提示下,若是输入数据全为降序会怎么样)
  3. 归并:用一台2g内存的机器排序10个1g的文件。

欢迎关注个人面试专栏面试题精选永久免费 持续更新,本专栏会收集我遇到的比较经典面试题,除了提供详尽的解法外还会从面试官的角度提供扩展题,但愿能帮助你们找到更好的工做。另外,也征集面试题,若是你遇到了不会的题 私信告诉我,有价值的题我会给你出一篇博客。
本文来自https://blog.csdn.net/xindoo