简单(炫酷)的单链表快速排序写法

昨天在复习快排的时候,在B站看到一个小哥哥说某大厂的面试让写一个单链表的快速排序. 我们见的最多的快排写法都是从两端向中间扫描,这种写法在单链表上不能实现. 哥们分析道:快排的核心思想是每次扫描后,所有pivot左侧的元素都比pivot小, 右侧的元素都比pivot大, 然后递归就可以了. 不过这位小哥哥写的代码稍微有点复杂, 而且在数组上的写法leetcode过不了. 于是我找了找博客,找到一个简单版本的. 原文来自:分享一种比较简单易懂的快速排序思路,图解.

简单版数组快排: leetcode912 AC

class Solution{
public:
	vector<int> sortArray(vector<int>& arr) {
		quickSort(arr, 0, arr.size()-1);
		return arr;
	}
    void quickSort(vector<int>& arr, int low, int high) {
       if (low >= high) return; 
       int slow = low; 
       int pivot = arr[high];
       for (int fast = low; fast < high; fast++) {
           if (arr[fast] <= pivot) {
               swap(arr[fast], arr[slow]);
               slow++; 
           }
       }
       swap(arr[slow], arr[high]); 
       quickSort(arr, low, slow - 1);
       quickSort(arr, slow + 1, high);
   }
};

原文是java代码,这里我改成C++的代码了。这种写法思路明确, 实现简单. 主要思想就是每次递归将数组的最后一个元素作为pivot, 用两个指针辅助遍历. fast指针指向待遍历的元素, slow指针指向下一个待交换的位置,这个位置的左边所有元素都小于pivot. 因为slow指针指向的元素一定大于pivot, 所以一次遍历完成之后把high位置的元素和pivot元素交换即可完成一次递归.

把上面的代码简化下:

class Solution{
public:
	vector<int> sortArray(vector<int>& arr) {
		quickSort(arr, 0, arr.size()-1);
		return arr;
	}
    void quickSort(vector<int>& arr, int low, int high) {
       if (low >= high) return; 
       int slow = low;
       for (int fast = low; fast < high; fast++) if (arr[fast] <= arr[high]) swap(arr[fast], arr[slow++]);
       swap(arr[slow], arr[high]); 
       quickSort(arr, low, slow - 1);
       quickSort(arr, slow + 1, high);
   }
};

简化后的代码quickSort函数只有6行, 很简单, 但是不建议这么些, 代码写太简化和太复杂,个人认为可读性都比较差,哈哈.
OK, 数组的简单快排写好了,那就可以写链表的了. 不过仔细一分析, 问题就出现了. 如果拿最后一个元素做每次迭代的pivot, 那么起始的参数需要指向链表最后一个元素的指针, 也就是说, 第一次迭代之前还要遍历一次数组, 这种写法显然不够炫酷. 除此之外,每次向下迭代的的时候还需要一个pre指针记录slow指针的前驱指针, emmmm, 还是比较麻烦的.
那就来尝试修改代码, 让每次递归的pivot为数组的第一个元素.如果直接修改代码那问题又来了. 因为slow指针在遍历结束之后指向的元素一定是第一个大于pivot的, 那跟pivot交换之后, pivot左边的子数组的第一个元素还是大于pivot,不满足我们的要求. 那我们的思路就是让每次遍历完之后slow指针都指向最后一个小于等于pivot的元素, 之后跟pivot交换,就能实现一次递归了. 为了达到这个目的, 需要对slow的初始位置和前进条件做点修改. 我们把待遍历的数组看成两部分: pivot部分和剩余部分. 划分开就是arr[0] 和 arr[1]-arr[n]两部分(n为数组最后一个元素下标). 那为了实现slow最后指向最后一个小于等于pivot的数, 我们就要slow指针的元素与fast指针的元素修改后, slow指针不移动. 但是slow不移动的话, 不就原地打转了吗. 我们可以把slow的移动条件修改为,

if (arr[fast] <= pivot){
slow++;
swap(arr[slow], arr[fast]);
}

初始条件为:

slow = low;
fast = low + 1;

也就是指针指向带交换位置的前一个元素, fast指针发现有小于pivot的元素后, slow指针向后移动. 那代码实现起来就成了:

class Solution{
public:
	vector<int> sortArray(vector<int>& arr) {
		quickSort(arr, 0, arr.size()-1);
		return arr;
	}
    void quickSort(vector<int>& arr, int low, int high) {
       if (low >= high) return;
       int slow = low; 
       int pivot = arr[low];
       for (int fast = low+1; fast <= high; fast++) {
           if (arr[fast] <= pivot) {
               slow++; 
               swap(arr[fast], arr[slow]);
           }
       }
       swap(arr[slow], arr[low]); 
       quickSort(arr, low, slow-1);
       quickSort(arr, slow + 1, high);
   }
};

OK, 这样的代码修改成链表的就很简单了. 直接上代码:

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

    void quickSort(ListNode* head, ListNode* high){
		if(head == high || head->next == high) return;
		// head==high 也可以, 但是会判断长为1的子链表, 加上 head->next == high 就可以跳过长为1的子链表
		
        int pivot = head->val;
        
        ListNode* slow = head;
        ListNode* fast = head->next; 
        while(fast!=high){
            if(fast->val <= pivot){
                slow = slow->next;
                swap(slow->val, fast->val);
            }
            fast = fast->next;
        }
        
        swap(head->val, slow->val);
        quickSort(head, slow);
        quickSort(slow->next, high);
    }
};

到此, 简单(炫酷)的单链表快排写法就完成了. 本文所写的顺向数组在leetcode912题通过测试. 链表排序在leetcode148中, 测试样例通过,但是超时. 我把leetcode148题官方解答的归并排序和我写的代码在牛客上对比之后, 该代码比牛客148的归并反而快一点. 可能原因是来自leetcode的测试用例, 因为快排时间复杂度最坏为O(N^2), 而归并是O(logN). 快排在leetcode的测试用力中遇到了较坏的情况. 牛客的题号为NC70.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值