跳过正文

数据结构与算法-8-排序

·617 字· loading · loading ·
Masterlong
作者
Masterlong
熬夜,但是世界之夜
目录
数据结构与算法 - 这篇文章属于一个选集。
§ 11: 本文

排序
#

概念

稳定 不稳定

时间开销

  • 关键字比较次数KCN
  • 记录交换次数RSN

一般按照平均情况进行大略估算。

  • 对于那些受对象初始排列&对象个数影响较大的,需要按最好情况最坏情况估算。

算法执行时所需的附加存储:

评价算法好坏的另一标准

静态排序
#

物理重排

动态排序
#

链接指针 逻辑结构先后更改

内部排序
#

全部在内存中完成

外部排序
#

整个排序过程不可能只在内存中完成

  • IO访问对时间的影响

基础排序
#

选择 冒泡 插入

希尔排序
#

对序列预处理,使得序列尽可能接近正序,再调用插入排序算法?

分为若干(d组)子序列,对每个子序列进行插入排序,多趟完成整个排序

缩小增量排序

不稳定, 无附加存储

Shell: d=n/2 d=d/2

Knuth: d=n/3+1 d=d/3+1

Knuth: n super 大时,KSN RSN n^1.25 , 1.6n^1.25

归并排序
#

merge
#

将两个(及以上)有序子序列“归并”为一个有序序列。两路/多路归并。

template <typename E, typename Comp>
void mergesort(E A[], E temp[], int left, int right) {
    if (left == right) return; // List of one element
    int mid = (left+right)/2;
    mergesort<E,Comp>(A, temp, left, mid);
    mergesort<E,Comp>(A, temp, mid+1, right);
    
    // 两路合并
    
    for (int i=left; i<=right; i++) // Copy subarray to temp
    temp[i] = A[i];
    // Do the merge operation back to A
    int i1 = left; int i2 = mid + 1;
    for (int curr=left; curr<=right; curr++) {
        if (i1 == mid+1) // Left sublist exhausted
        	A[curr] = temp[i2++];
        else if (i2 > right) // Right sublist exhausted
        	A[curr] = temp[i1++];
        else if (Comp::prior(temp[i1], temp[i2]))
        	A[curr] = temp[i1++];
        else A[curr] = temp[i2++];
    }
}
  1. 原始序列A分为子序列A[0~N/2-1] A[N/2~N-1]
  2. 两边归并排序
  3. 合并
  • $$O(nlog_2n)$$
  • 附加空间

  • 稳定的

优化1

  • 临时数组两边向中间走 不会出现子序列耗尽情况
  • 1234|9876

优化2

  • 初始插入排序 + 2路归并 一趟一次
  • 数据量较小时better

堆排序
#

利用堆的特性

建大堆->重复removeFirst直到堆空

template <typename E, typename Comp>
void heapsort(E A[], int n) { // Heapsort
    E maxval;
    heap<E,Comp> H(A, n, n); // Build the heap
    for (int i=0; i<n; i++) // Now sort
    	maxval = H.removefirst(); // Place maxval at end
}
  • $$O(nlog_2n)$$
  • 不稳定
  • 不需要附加存储
  • 找K个最大元素$$O(n+klogn)$$

快速排序
#

目标:找一个记录,以它的关键字作为枢轴

行为:凡key小于枢轴的记录均移动至该记录之前,反之,移至它之后

结果:处理之后,无序的记录序列R[s…t]以R[i]为界分割成两部分

$$R[s..i-1]\leq R[i] \leq R[i+1..t]$$
template <typename E, typename Comp>
void qsort(E A[], int i, int j) { // Quicksort
    if (j <= i) return; // Don’t sort 0 or 1 element
    int pivotindex = findpivot(A, i, j);
    swap(A, pivotindex, j); // Put pivot at end
    // k will be the first position in the right subarray
    int k = partition<E,Comp>(A, i-1, j, A[j]);
    swap(A, k, j); // Put pivot in place
    qsort<E,Comp>(A, i, k-1);
    qsort<E,Comp>(A, k+1, j);
}
// 划分
template <typename E, typename Comp>
inline int partition(E A[], int l, int r, E& pivot) {
    do { // Move the bounds inward until they meet
        while (Comp::prior(A[++l], pivot)); // Move l right and
        while ((l < r) && Comp::prior(pivot, A[--r])); // r left
        swap(A, l, r); // Swap out-of-place values
    } while (l < r); // Stop when they cross
    return l; // Return first position in right partition
}

e.g. 一次划分

1 7 2 6 5 3 4

1 7 2 4 5 3 6

1 3 2 4 5 7 6

快速排序 继续对左右划分

1 2 3 | 4 | 5 6 7

  • 最理想 左右侧子序列长度相等
    • $$O(nlog_2n)$$
  • 平均也是$$O(nlog_2n)$$
  • 就平均计算时间而言,n很大时,快排是我们所讨论算法中最好的
  • 最坏情况
    • 排序序列已经有序,总比较次数将达到$$n^2/2$$,退化到简单排序,比直接排序还慢
  • 不需要附加存储
  • 不稳定

优化1 pivot

  • 选择基准对象使得每次划分所得子序列对象个数尽可能接近
    • 难以做到
  • 其它思路 选择中间元素,选择最左边元素
  • 简单实用 取每个待排序列第一个对象、最后一个对象、位置接近正中的3个选项中大小居中者

优化2

  • 小规模全用插入排序

分配(桶)排序 基数排序
#

bin
#

排序记录关键字在A[n],结果在B[n]

  • 迭代进行B[A[i]]=A[i]

    • 关键字取值不能重复
    • 最大取值<=待排序数组尺寸-1
    • 关键字为非负整数
    • $$O(n)$$
  • 加强版

    • 确定最大取值Max
    • 定义数组LList B[Max+1]
    • 关键字为K的记录放在链表B[K]中
    • 从B[0]开始,顺序访问每个Bin的每个记录,放入A[]中
    • $$O(n+max(Max,n))$$
  • 需要额外空间

  • 稳定的

基数排序
#

  • 按位排序
  • 确定最大取值Max和位数k
  • 从低位到高位,进行k趟,第i趟根据第i位上一趟结果binsort,原始序列记作0趟结果
  • $$O(kn)$$
  • $$O(nlog_rn)$$大
  • $$O(n)$$小
  • 额外空间
    • 链表:nE + (n + r)P
    • 数组:(n + r) * E
template <typename E, typename getKey>
void radix(E A[], E B[],
int n, int k, int r, int cnt[]) {
    // cnt[i] stores number of records in bin[i]
    int j;
    for (int i=0, rtoi=1; i<k; i++, rtoi*=r) { // For k digits
        for (j=0; j<r; j++) cnt[j] = 0; // Initialize cnt
        // Count the number of records for each bin on this pass
        for (j=0; j<n; j++) cnt[(getKey::key(A[j])/rtoi)%r]++;
        // Index B: cnt[j] will be index for last slot of bin j.
        for (j=1; j<r; j++) cnt[j] = cnt[j-1] + cnt[j];
        // Put records into bins, work from bottom of each bin.
        // Since bins fill from bottom, j counts downwards
        for (j=n-1; j>=0; j--)
        B[--cnt[(getKey::key(A[j])/rtoi)%r]] = A[j];
        for (j=0; j<n; j++) A[j] = B[j]; // Copy B back to A
    }
}

小总结
#

快排yyds

数据结构与算法 - 这篇文章属于一个选集。
§ 11: 本文

相关文章

数据结构与算法-7-树作业2
·64 字· loading · loading
数据结构与算法-7-通用树
·324 字· loading · loading
数据结构与算法-5-哈夫曼树
·85 字· loading · loading