【程序人生】数据结构杂记(五)

说在前面

个人读书笔记

快速排序

分治策略

与归并排序算法一样,快速排序(quicksort)算法也是分治策略的典型应用,但二者之间也有本质区别。

归并排序的计算量主要消耗于有序子向量的归并操作,而子向量的划分却几乎不费时间。快速排序恰好相反,它可以在 O ( 1 ) O(1) O(1)时间内,由子问题的解直接得到原问题的解;但为了将原问题划分为两个子问题,却需要O(n)时间。

快速排序算法虽然能够确保,划分出来的子任务彼此独立,并且其规模总和保持渐进不变,却不能保证两个子任务的规模大体相当——实际上,甚至有可能极不平衡。

因此,该算法并不能保证最坏情况下的O(nlogn)时间复杂度。

尽管如此,它仍然受到人们的青睐,并在实际应用中往往成为首选的排序算法。究其原因在于,快速排序算法易于实现,代码结构紧凑简练,而且对于按通常规律随机分布的输入序列,快速排序算法实际的平均运行时间较之同类算法更少。

快速排序算法

在这里插入图片描述

如上图所示,考查任一向量区间 S [ l o , h i ) S[lo, hi) S[lo,hi)。对于任何 l o < = m i < h i lo <= mi < hi lo<=mi<hi,以元素 S [ m i ] S[mi] S[mi]为界,都可分割出前、后两个子向量 S [ l o , m i ) S[lo, mi) S[lo,mi) S ( m i , h i ) S(mi, hi) S(mi,hi)。若 S [ l o , m i ) S[lo, mi) S[lo,mi)中的元素均不大于 S [ m i ] S[mi] S[mi],且 S ( m i , h i ) S(mi, hi) S(mi,hi)中的元素均不小于 S [ m i ] S[mi] S[mi],则元素 S [ m i ] S[mi] S[mi]称作向量S的一个轴点(pivot)。

设向量 S S S经排序可转化为有序向量 S ′ S' S。不难看出,轴点位置 m i mi mi必然满足如下充要条件:

  • S [ m i ] = S ′ [ m i ] S[mi] = S'[mi] S[mi]=S[mi]
  • S [ l o , m i ) S[lo, mi) S[lo,mi) S ′ [ l o , m i ) S'[lo, mi) S[lo,mi)的成员完全相同
  • S ( m i , h i ) S(mi, hi) S(mi,hi) S ′ ( m i , h i ) S'(mi, hi) S(mi,hi)的成员完全相同

因此,不仅以轴点 S [ m i ] S[mi] S[mi]为界,前、后子向量的排序可各自独立地进行,而且更重要的是,一旦前、后子向量各自完成排序,即可立即(在 O ( 1 ) O(1) O(1)时间内)得到整个向量的排序结果。

采用分治策略,递归地利用轴点的以上特性,便可完成原向量的整体排序。

快速排序算法:
在这里插入图片描述
可见,轴点的位置一旦确定,则只需以轴点为界,分别递归地对前、后子向量实施快速排序;子向量的排序结果就地返回之后,原向量的整体排序即告完成。

算法的核心与关键在于:
轴点构造算法partition()应如何实现?可以达到多高的效率?

轴点构造——快速划分算法

在这里插入图片描述不妨取首元素 m = S [ l o ] m = S[lo] m=S[lo]作为候选,将其从向量中取出并做备份,腾出的空闲单元便于其它元素的位置调整。

然后不断试图移动 l o lo lo h i hi hi,使之相互靠拢。

当然,整个移动过程中,需始终保证 l o ( h i ) lo(hi) lo(hi)左侧(右侧)的元素均不大于(不小于) m m m

最后,当 l o lo lo h i hi hi彼此重合时,只需将原备份的 m m m回填至这一位置,则 S [ l o = h i ] = m S[lo = hi]= m S[lo=hi]=m便成为一个名副其实的轴点。

以上过程在构造出轴点的同时,也按照相对于轴点的大小关系,将原向量划分为左、右两个子向量,故亦称作快速划分(quick partitioning)算法。

在这里插入图片描述
c++实现数组的快速排序
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <iostream>

using namespace std;


template <typename T>
int __partition(T arr[], int l, int r)
{
    // 第一个值拿出来, 假设为轴点
    T v = arr[l];

    int j = l;
    // 从第二个开始向后遍历
    for (int i = l + 1; i <= r; i++)
    {
        if (arr[i] < v)
        {
            swap(arr[j + 1], arr[i]);
            j++;
        }
    }

    swap(arr[l], arr[j]);

    return j;
}

template <typename T>
void __quickSort(T arr[], int l, int r)
{
    if (l>=r)
    {
        return;
    }

    int p = __partition(arr, l, r);
    __quickSort(arr, l, p-1);
    __quickSort(arr, p+1, r);
}


template <typename T>
void quickSort(T arr[], int n)
{
    __quickSort(arr, 0, n-1);
}

int main()
{
    int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
    quickSort(a, 10);
    
    for (int i = 0; i< 10; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;
    
    return 0;
}

当快速排序轴点两侧数量极度不平衡的时候,算法将退化。
如上,每次都选取第一个数值为轴点时。这样当数组完全有序时,算法时间复杂度退化到 O ( n 2 ) O(n^2) O(n2),解决方法是在进行快速划分算法中,每次都随机选取一个点和第一个点交换,再将这个点作为轴点。

但是当重复的值比较多时,由于划分的两部分,其中一部分是小于轴点,另一部分是大于或等于轴点,划分的两部分仍然可能极不平衡,改进的算法如下:

#include <iostream>
#include <stdlib.h>

using namespace std;


template <typename T>
int __partition(T arr[], int l, int r)
{
    swap(arr[l], arr[rand() % (r-l+1) + l]);
    // 第一个值拿出来, 假设为轴点
    T v = arr[l];

    int i = l + 1, j = r;
    while (true)
    {
        while (i <= r && arr[i] < v)
        {
            i++;
        }
        while (j >= l+1 && arr[j] > v)
        {
            j--;
        }
        
        if (i>j)
        {
            break;
        }
        swap(arr[i], arr[j]);
        i++;
        j--;
    }

    swap(arr[l], arr[j]);

    return j;
}

template <typename T>
void __quickSort(T arr[], int l, int r)
{
    if (l>=r)
    {
        return;
    }

    int p = __partition(arr, l, r);
    __quickSort(arr, l, p-1);
    __quickSort(arr, p+1, r);
}


template <typename T>
void quickSort(T arr[], int n)
{
    srand(time(NULL));
    __quickSort(arr, 0, n-1);
}

int main()
{
    int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
    quickSort(a, 10);
    
    for (int i = 0; i< 10; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;
    
    return 0;
}

python实现的快速排序

# -*- coding:utf-8 -*-

import numpy as np
import time


def swap(list0, index1, index2):
    b = list0[index1]
    list0[index1] = list0[index2]
    list0[index2] = b


def partition(list0, l, r):
    swap(list0, l, np.random.randint(0, len(list0)) % (r-l+1) + l)
    v = list0[l]

    p = l+1
    q = r

    while True:
        while (p <= r) and (list0[p] < v):
            p = p + 1
        while (q >= l+1) and (list0[q] > v):
            q = q - 1

        if p >= q:
            break
        else:
            swap(list0, p, q)
            p = p + 1
            q = q - 1

    swap(list0, l, q)

    return q


def quick_sort(list0, l, r):
    if l >= r:
        return

    p = partition(list0, l, r)

    quick_sort(list0, l, p-1)
    quick_sort(list0, p+1, r)


def main(list0, n):
    np.random.seed(int(str(time.time()).split(".")[1]))
    quick_sort(list0, 0, n-1)

    print(list0)


if __name__ == "__main__":
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    main(a, len(a))

三路快速排序算法

c++实现数组的三路快速排序

#include <iostream>
#include <stdlib.h>

using namespace std;


template <typename T>
void __quickSort3Ways(T arr[], int l, int r)
{
    if (l>=r)
    {
        return;
    }
    
    swap(arr[l], arr[rand() % (r-l+1) + l]);
    T v = arr[l];

    int lt = l;
    int gt = r + 1;
    int i = l + 1;
    while (i < gt)
    {
        if (arr[i] < v)
        {
            swap(arr[i], arr[lt+1]);
            i++;
            lt++;
        }
        else if (arr[i]>v)
        {
            swap(arr[i], arr[gt-1]);
            gt--;
        }
        else
        {
            i++;
        }
    }
    swap(arr[l], arr[lt]);

    __quickSort3Ways(arr, l, lt-1);
    __quickSort3Ways(arr, gt, r);
}


template <typename T>
void quickSort3Ways(T arr[], int n)
{
    srand(time(NULL));
    __quickSort3Ways(arr, 0, n-1);
}

int main()
{
    int a[10] = {10, 9, 8, 6, 11, 5, 4, 1, 0, 3};
    quickSort3Ways(a, 10);
    
    for (int i = 0; i< 10; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;
    
    return 0;
}

python实现的三路快速排序

# -*- coding:utf-8 -*-

import numpy as np
import time


def swap(list0, index1, index2):
    b = list0[index1]
    list0[index1] = list0[index2]
    list0[index2] = b


def quick_sort(list0, l, r):
    if l >= r:
        return

    swap(list0, l, np.random.randint(0, len(list0)) % (r - l + 1) + l)
    v = list0[l]

    p = l
    q = r + 1
    i = l + 1

    while i < q:
        if list0[i] < v:
            swap(list0, i, p+1)
            p = p + 1
            i = i + 1
        elif list0[i] > v:
            swap(list0, i, q-1)
            q = q - 1
        else:
            i = i + 1

    swap(list0, l, p)

    quick_sort(list0, l, p-1)
    quick_sort(list0, q, r)


def main(list0, n):
    np.random.seed(int(str(time.time()).split(".")[1]))
    quick_sort(list0, 0, n-1)

    print(list0)


if __name__ == "__main__":
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    main(a, len(a))

求逆序对的数量——使用归并排序

# -*- coding:utf-8 -*-


class MergeSort:
    def __init__(self, list0, n):
        self.list0 = list0
        self.n = n

    @staticmethod
    def _merge(list0, l, mid, r):
        global count
        list1 = list0[:]

        p = l
        q = mid + 1

        for i in range(r-l+1):
            if p > mid:
                list0[i+l] = list1[q]
                q = q + 1
            elif q > r:
                list0[i+l] = list1[p]
                p = p + 1
            elif list1[p] <= list1[q]:
                list0[i+l] = list1[p]
                p = p + 1
            else:
                list0[i+l] = list1[q]
                count = count + mid + 1 - p
                q = q + 1

    def _merge_sort(self, list0, l, r):
        if l >= r:
            return

        mid = int((l+r)/2)
        self._merge_sort(list0, l, mid)
        self._merge_sort(list0, mid+1, r)
        self._merge(list0, l, mid, r)

    def merge_sort(self):
        self._merge_sort(self.list0, 0, self.n - 1)


def main():
    global count
    count = 0

    # -------------------------------------
    x = 0
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    for i in range(len(a)-1):
        for j in range(i+1, len(a)):
            if a[j] < a[i]:
                x = x + 1
    # -------------------------------------

    b = MergeSort(a, len(a))
    b.merge_sort()

    print(a)
    print(count, x)


if __name__ == "__main__":
    main()

求第n大的值——使用快速排序

# -*- coding:utf-8 -*-

import numpy as np
import time
import sys


def swap(list0, index1, index2):
    b = list0[index1]
    list0[index1] = list0[index2]
    list0[index2] = b


def partition(list0, l, r, th):
    swap(list0, l, np.random.randint(0, len(list0)) % (r-l+1) + l)
    v = list0[l]

    p = l+1
    q = r

    while True:
        while (p <= r) and (list0[p] < v):
            p = p + 1
        while (q >= l+1) and (list0[q] > v):
            q = q - 1

        if p >= q:
            break
        else:
            swap(list0, p, q)
            p = p + 1
            q = q - 1

    swap(list0, l, q)

    return q


def quick_sort(list0, l, r, th):
    if l >= r:
        return

    p = partition(list0, l, r, th)
    if p == len(list0) - th:
        print(list0[p])
        sys.exit(0)

    elif p > len(list0) - th:
        quick_sort(list0, l, p-1, th)
    else:
        quick_sort(list0, p+1, r, th)


def main(list0, n, th):
    np.random.seed(int(str(time.time()).split(".")[1]))
    quick_sort(list0, 0, n-1, th)

    # print(list0)


if __name__ == "__main__":
    a = [10, 9, 8, 6, 11, 5, 4, 1, 0, 3]
    # 想找第th大的数的值, th小于等于len(a) + 1
    th = 3
    main(a, len(a), th)

结语

如果您有修改意见或问题,欢迎留言或者通过邮箱和我联系。
手打很辛苦,如果我的文章对您有帮助,转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值