Leetcode21.+Leetcode75. 归并排序和快速排序的实现

Leetcode21. Merge Two Sorted Lists

题目

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

Example:
Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

解题分析

归并排序,顾名思义,就是将两个已排好序的链表合成一个新的排序的链表,其排序的思想也是非常简单的。
先用一个节点dummy来表示生成的新链表的虚拟头节点,用一个指针li来表示新链表指针的移动,默认为虚拟头节点。另外,用一个指针l1来表示链表1指针的移动,l2表示链表2指针的移动;默认l1和l2分别表示链表1和链表2的头节点。
当两个链表l1和l2均不为空的时候,比较指针l1和l2指向的节点的值,如果l1节点的值小于等于l2节点,新链表的指针li指向l1节点并向后移动一位,同时l1也往后移动一位;反之亦然。
当其中有一个链表遍历完成后,比如链表1率先遍历完成,那么此时l1指向为空,只需将链表2后面的元素插入到新链表后面即可,不用再进行比较;同理链表2的情况也是如此。当两个链表全部遍历完成后,排序也就完成了,最后只需返回虚拟头节点dummy指向的节点即可。
很明显,时间复杂度为O(n1+n2),其中n1和n2分别代表链表1和链表2的长度,是不是很简单呢?

源代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode dummy(0);
        ListNode* li = &dummy;
        while (l1 != NULL && l2 != NULL) {
            if (l1->val <= l2->val) {
                li->next = l1;
                l1 = l1->next;
            }
            else {
                li->next = l2;
                l2 = l2->next;
            }
            li = li->next;
        }
        if (l1 != NULL) {
            li->next = l1;
        }
        if (l2 != NULL) {
            li->next = l2;
        }
        return dummy.next;
    }
};

Leetcode75. Sort Colors

题目

Given an array with n objects colored red, white or blue, sort them so that objects of the same color are adjacent, with the colors in the order red, white and blue.
Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.

Note:
You are not suppose to use the library’s sort function for this problem.

解题分析

快速排序,顾名思义,就是排序要快,那么它到底快在哪里呢?我们今天来探讨一下快速排序的实现算法。

快速排序使用分治策略来把一个list分为两个sub-lists。其算法原理是这样子的:
从数列中挑出一个元素,称为 “基准”(pivot),重新排序数列,使所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代中,它至少会把一个元素摆到它最后的位置去。

根据上面的原理,很快我们就会有下面的解题思路~
快速排序是基于分治模式处理的,对一个典型子数组A[p…r]排序的分治过程为三个步骤:
a.分解:A[p..r]被划分为俩个(可能空)的子数组A[p ..q-1]和A[q+1 ..r],使得A[p ..q-1] <= A[q] <= A[q+1 ..r]。
b.解决:通过递归调用快速排序,对子数组A[p ..q-1]和A[q+1 ..r]排序。
c.合并。

算法伪代码如下:

 QUICK_SORT(A,p,r)
    if(pA,p,r)
             QUICK_SORT(A,p,q-1)
             QUICK_SORT(A,q+1,r)

//核心函数,对数组A[p,r]进行就地重排,将小于A[r]的数移到数组前半部分,将大于A[r]的数移到数组后半部分。
PARTITION(A,p,r)
    pivot <—— A[r]
    i <—— p-1
    for j <—— p to r-1
        do if A[j] < pivot
            i <—— i+1
            exchange A[i]<——>A[j]
    exchange A[i+1]<——>A[r]
return i+1

算法复杂度
a.最坏情况下的快排时间复杂度:
最坏情况发生在划分过程产生的俩个区域分别包含n-1个元素和一个0元素的时候,
即假设算法每一次递归调用过程中都出现了,这种划分不对称。那么划分的代价为O(n),
因为对一个大小为0的数组递归调用后,返回T(0)=O(1)。
估算法的运行时间可以递归的表示为:
T(n)=T(n-1)+T(0)+O(n)=T(n-1)+O(n).
可以证明为T(n)=O(n^2)。
因此,如果在算法的每一层递归上,划分都是最大程度不对称的,那么算法的运行时间就是O(n^2)。
b.最快情况下快排时间复杂度:
最快情况下,即PARTITION可能做的最平衡的划分中,得到的每个子问题都不能大于n/2.
因为其中一个子问题的大小为|n/2|。另一个子问题的大小为|n/2|-1.
在这种情况下,快速排序的速度要快得多:
T(n)<=2T(n/2)+O(n).可以证得,T(n)=O(nlgn)。

源代码

class Solution {
public:
    void sortColors(vector<int>& nums) {
        quickSort(nums, 0, nums.size() - 1);
    }

    void quickSort(vector<int>& nums, int low, int high) {
        if (low < high) {
            int mid = partition(nums, low, high);
            quickSort(nums, low, mid - 1);
            quickSort(nums, mid + 1, high);
        }
    }

    int partition(vector<int>& nums, int low, int high) {
        int pivot = nums[high], i = low, j;
        for (j = low; j < high; j++) {
            if (nums[j] < pivot) {
                swap(nums[i], nums[j]);
                i++;
            }
        }
        swap(nums[high], nums[i]);
        return i;
    }
};

以上是我对这两道排序问题的实现的一些想法,有问题还请在评论区讨论留言~

你可能感兴趣的:(leetcode算法)