算法链表的快速排序和归并排序

Posted Kant101

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法链表的快速排序和归并排序相关的知识,希望对你有一定的参考价值。

1. 概述

我们在讨论快速排序和归并排序的时候通常是针对数组来进行讨论的,常见的的算法教科书和文档也几乎都是讨论的数组。

快速排序和归并排序对链表是同样的。只要把其思想应用于链表。

 

2. 链表的快速排序

算法步骤

快速排序的思想就是每次选出一个作为划分点,在这个划分点的左边全部是比这个划分点小的,在右边全部是比这个划分点大的。只是在链表的快速排序中,这个划分点是一个结点,而在数组的快速排序中,这个划分点是一个数。

1.pivot选择为链表的第一个结点,small指向比pivot结点值小的最后一个结点,cur指针遍历整个链表。

当cur的值比pivot的值小的话,就将cur结点的值与small的下一个结点交换。这样到最后small结点及之前的结点都是比pivot值小的,small结点之后的都是比pivot值大的;

private ListNode partition(ListNode head, ListNode tail) 
    // mark head node as the pivot node
    int pivot = head.val;
    ListNode small = head, cur = head;
    while (cur != tail) 
        if (cur.val < pivot) 
            small = small.next;
            // swap small node's value with cur node's val
            int tmp = small.val;
            small.val = cur.val;
            cur.val = tmp;
        
        cur = cur.next;
    
    // swap pivot node's value with last small node's value
    head.val = small.val;
    small.val = pivot;
    // return the last small node as the pivot node
    return small;

2.然后再将pivot结点归位,即pivot的值与small最后一个交换,那么pivot归到了本属于它的位置。

3.然后,根据快速排序的 Divide and Conquer 思想再对前半部分和后半部分分别进行排序。

public ListNode quickSortLinkedList(ListNode head) 
    if (head == null || head.next == null) return head;
    return quickSort(head, null);


private ListNode quickSort(ListNode head, ListNode tail) 
    if (head == tail) return head;
    ListNode mid = partition(head, tail);
    quickSort(head, mid);
    quickSort(mid.next, tail);
    return head;

 

测试

package cn.pku.edu.algorithm.leetcode.plus;

import cn.pku.edu.algorithm.leetcode.common.ListNode;

/**
 * @author allen
 * @date 2022/10/11
 */
public class LinkedListQuickSort 

    public ListNode quickSortLinkedList(ListNode head) 
        if (head == null || head.next == null) return head;
        return quickSort(head, null);
    

    private ListNode quickSort(ListNode head, ListNode tail) 
        if (head == tail) return head;
        ListNode mid = partition(head, tail);
        quickSort(head, mid);
        quickSort(mid.next, tail);
        return head;
    

    private ListNode partition(ListNode head, ListNode tail) 
        // mark head node as the pivot node
        int pivot = head.val;
        ListNode small = head, cur = head;
        while (cur != tail) 
            if (cur.val < pivot) 
                small = small.next;
                // swap small node's value with cur node's val
                int tmp = small.val;
                small.val = cur.val;
                cur.val = tmp;
            
            cur = cur.next;
        
        // swap pivot node's value with last small node's value
        head.val = small.val;
        small.val = pivot;
        // return the last small node as the pivot node
        return small;
    

    public static void main(String[] args) 
        ListNode head = new ListNode(4);
        ListNode listNode2 = new ListNode(1);
        ListNode listNode3 = new ListNode(2);
        ListNode listNode4 = new ListNode(7);
        ListNode listNode5 = new ListNode(6);
        ListNode listNode6 = new ListNode(3);
        ListNode listNode7 = new ListNode(5);

        head.next = listNode2;
        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;
        listNode5.next = listNode6;
        listNode6.next = listNode7;

        ListNode.printList(head);
        LinkedListQuickSort linkedListQuickSort = new LinkedListQuickSort();
        ListNode res = linkedListQuickSort.quickSortLinkedList(head);
        ListNode.printList(res);
    

 

3. 链表的归并排序

算法步骤

链表的归并排序跟快速排序虽有一些区别的,但是都是采用的 Divide and Conquer 思想来处理问题的,它每次将一个链表拆分成两段,对两段分别再归并排序,完成后再将merge两个有序的子链表。

1.将原链表拆分成左右两段,通过快慢指针找出链表的中间位置断开为两个子链表;

2.再分别对左右两个子链表采用归并排序;即会递归地调用归并排序,直到子链表为空或者子链表中只有一个结点,就可以直接返回链表本身,因为它已经是有序的了;

public ListNode mergeSort(ListNode head) 
    if (head == null || head.next == null) return head;
    // 1. 拆分链表为左右两个子链表
    ListNode fast = head, slow = head;
    while (fast.next != null && fast.next.next != null) 
        slow = slow.next;
        fast = fast.next.next;
    
    fast = slow.next;
    slow.next = null;
    // 2. 对左右子链表分别递归地进行归并排序,使得两个子链表是有序的
    ListNode left = mergeSort(head);
    ListNode right = mergeSort(fast);
    // 3. 对两个有序的子链表进行merge,得到整体有序的链表
    return mergeTwoLists(left, right);

3.对得到的两个有序的子链表采用merge操作,使得整个链表成为有序链表;

private ListNode mergeTwoLists(ListNode list1, ListNode list2) 
    ListNode dummy = new ListNode(-1), pre = dummy;
    while (list1 != null && list2 != null) 
        if (list1.val <= list2.val) 
            pre.next = list1;
            list1 = list1.next;
         else 
            pre.next = list2;
            list2 = list2.next;
        
        pre = pre.next;
    
    if (list1 != null) pre.next = list1;
    if (list2 != null) pre.next = list2;
    return dummy.next;

 

测试

package cn.pku.edu.algorithm.leetcode.plus;

import cn.pku.edu.algorithm.leetcode.common.ListNode;

/**
 * @author allen
 * @date 2022/10/11
 */
public class LinkedListMergeSort 

    public ListNode mergeSort(ListNode head) 
        if (head == null || head.next == null) return head;
        // 1. 拆分链表为左右两个子链表
        ListNode fast = head, slow = head;
        while (fast.next != null && fast.next.next != null) 
            slow = slow.next;
            fast = fast.next.next;
        
        fast = slow.next;
        slow.next = null;
        // 2. 对左右子链表分别递归地进行归并排序,使得两个子链表是有序的
        ListNode left = mergeSort(head);
        ListNode right = mergeSort(fast);
        // 3. 对两个有序的子链表进行merge,得到整体有序的链表
        return mergeTwoLists(left, right);
    

    private ListNode mergeTwoLists(ListNode list1, ListNode list2) 
        ListNode dummy = new ListNode(-1), pre = dummy;
        while (list1 != null && list2 != null) 
            if (list1.val <= list2.val) 
                pre.next = list1;
                list1 = list1.next;
             else 
                pre.next = list2;
                list2 = list2.next;
            
            pre = pre.next;
        
        if (list1 != null) pre.next = list1;
        if (list2 != null) pre.next = list2;
        return dummy.next;
    

    /**
     * 测试
     */
    public static void main(String[] args) 
        ListNode head = new ListNode(4);
        ListNode listNode2 = new ListNode(1);
        ListNode listNode3 = new ListNode(2);
        ListNode listNode4 = new ListNode(7);
        ListNode listNode5 = new ListNode(6);
        ListNode listNode6 = new ListNode(3);
        ListNode listNode7 = new ListNode(5);

        head.next = listNode2;
        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;
        listNode5.next = listNode6;
        listNode6.next = listNode7;

        ListNode.printList(head);
        LinkedListMergeSort mergeSort = new LinkedListMergeSort();
        ListNode res = mergeSort.mergeSort(head);
        ListNode.printList(res);
    

 

参考文献

[1] https://blog.csdn.net/yueguangmuyu/article/details/119102492
[2] https://iq.opengenus.org/quick-sort-on-linked-list/

以上是关于算法链表的快速排序和归并排序的主要内容,如果未能解决你的问题,请参考以下文章

链表的快速排序和归并排序

排序算法

精益求精单链表归并排序与快速排序

归并排序和快速排序(算法系列)

归并排序

编程-链表之希尔排序堆排序归并排序快速排序