常用排序算法整合(二)(选择、堆排序、归并、基数)

第一部分内容:常用排序算法整合(一)(插入、希尔、冒泡、快排)

选择排序

算法步骤

  1. 假设待排序列存放在 A r r [ 0 : n ] Arr[0:n] Arr[0:n]当中,第一次从 A r r [ 0 ] Arr[0] Arr[0]开始,比较 n − 1 n-1 n1次得到最小元素 A r r [ k ] Arr[k] Arr[k],交换 A r r [ 0 ] Arr[0] Arr[0] A r r [ k ] Arr[k] Arr[k]
  2. 第二次从 A r r [ 1 ] Arr[1] Arr[1]开始进行 n − 2 n-2 n2次比较找到剩余 n − 1 n-1 n1个元素当中的最小的,将其放到 A r r [ 1 ] Arr[1] Arr[1]的位置
  3. 以此类推,进行 n − 1 n-1 n1次上述操作,得到有序序列

算法描述

void selectSort(vector<int>& arr)
{
    int n = arr.size();
    for(int i = 0;i<n;i++)
    {
        int tmp_idx = i;
        for(int j = i+1;j<n;j++)
        {
            if(arr[j]<arr[tmp_idx]) tmp_idx = j;
        }
        if(tmp_idx!=i)
        {
            int t = arr[tmp_idx];
            arr[tmp_idx] = arr[i];
            arr[i] = t;
        }
    }
}

算法分析

时间复杂度 O ( n 2 ) O(n^2) O(n2);空间复杂度 O ( 1 ) O(1) O(1)

使用“交换元素”的策略这一实现而言,选择排序是不稳定的,但可以通过其他方式实现稳定排序

堆排序

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆的性质:即子结点的值总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大根堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小根堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

算法步骤

  • 将初始待排序列 ( R 1 , R 2 … , R n ) (R_1,R_2…,R_n) (R1,R2,Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素 R [ 1 ] R[1] R[1]与最后一个元素 R [ n ] R[n] R[n]交换,此时得到新的无序区 ( R 1 , R 2 , … … , R n − 1 ) (R_1,R_2,……,R_{n-1}) (R1,R2,,Rn1)和新的有序区 ( R n ) (R_n) (Rn),且满足 R [ 1 , 2 … , n − 1 ] ≤ R [ n ] R[1,2…,n-1]\leq R[n] R[1,2,n1]R[n]
  • 将当前无序区 ( R 1 , R 2 , … … , R n − 1 ) (R_1,R_2,……,R_{n-1}) (R1,R2,,Rn1)调整为新堆,然后再次将 R [ 1 ] R[1] R[1]与无序区最后一个元素交换,得到新的无序区 ( R 1 , R 2 , … … , R n − 2 ) (R_1,R_2,……,R_{n-2}) (R1,R2,,Rn2)和新的有序区 ( R n − 1 , R n ) (R_{n-1},R_n) (Rn1,Rn)。不断重复此过程直到交换完最后两个元素,得到了一个非递减的有序序列。

图片来源:https://www.runoob.com/wp-content/uploads/2019/03/heapSort.gif

上述步骤的两个关键问题是:(1)如何建初堆;(2)如何调整堆

调整堆

在交换之后,有可能较小的元素到了堆顶,根据大根堆的性质,父节点的值要大于两个子节点,所以可以将不符合要求的点逐层下沉:

  1. 从两个子节点中选出值较大的一个,假设是左子节点 R [ 2 s ] R[2s] R[2s]
  2. 比较左子节点和当前节点 R [ s ] R[s] R[s]的值:
    • R [ s ] R[s] R[s]的值大于等于 R [ 2 s ] R[2s] R[2s]的值,说明当前以当前节点 R [ s ] R[s] R[s]为根的子树已经是堆,无需调整
    • 否则交换两个节点的值,交换后如果以 R [ 2 s ] R[2s] R[2s]为根的子树不是堆,重复上述过程,知道进行到叶子节点为止

建初堆

要将一个无序序列调整为堆,就必须将其所对应的完全二叉树中以每一结点为根的子树都调整为堆。叶子节点不用调整,所以从最后一个非叶子节点开始(在完全二叉树中第一个非叶子节点为 n / 2 − 1 n/2 - 1 n/21,从0开始编号)从左到右、从下而上的调整堆即可

算法描述

//调整堆
void adjustHeap(vector<int>& arr,int s,int m)
{//将r[s..m](左闭右闭)调整为大根堆
    int tmp = arr[s]; //要调整的根节点
    for(int i = 2 * s;i<=m;i*=2)
    {
        if(i<m && arr[i]<arr[i+1]) i++;//找较大的子节点
        if(tmp>=arr[i]) break;//如果大于子节点无需调整
        arr[s] = arr[i];//交换子节点与父节点值
        s = i;//交换后子节点作为新的父节点,看是否需要继续调整
    }
    arr[s] = tmp;//调整到合适位置
}

//建初堆
void createHeap(vector<int>& arr)
{
    int n = arr.size();
    for(int i = n/2-1;i>=0;i--)//从最后一个非叶结点开始调整
        adjustHeap(arr,i,n-1);
}
//堆排序
void heapSort(vector<int>& arr)
{
    createHeap(arr);//建堆
    for(int i = arr.size()-1;i>=1;i--)
    {//交换后调整
        int tmp = arr[0];
        arr[0] = arr[i];
        arr[i] = tmp;
        adjustHeap(arr,0,i-1);
    }
}

算法分析

堆排序最坏情况下时间复杂度也为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),实验表明,平均性能接近于最坏性能

空间复杂度为 O ( 1 ) O(1) O(1)

堆排序是不稳定排序,只能用于顺序结构

由于初始建堆比较次数较多,所以记录数较少时不宜采用

归并排序

归井排序就是将两个或两个以上的有序表合并成一个有序表的过程。以下以2-路归并为例介绍

算法步骤

2路归并排序将 R [ l o w . . h i g h ] R[low.. high] R[low..high]中的记录归并排序后放入 T [ l o w . . h i g h ] T[low.. high] T[low..high]中。 当序列长度等千 1 时, 递归结束, 否则:

  1. 将当前序列一分为二, 求出分裂点 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid = \lfloor(low+high)/2\rfloor mid=(low+high)/2;
  2. 对子序列 R [ l o w . . m i d ] R[low.. mid] R[low..mid]递归,进行归并排序, 结果放入 S [ l o w . . m i d ] S[low.. mid] S[low..mid]
  3. 对子序列 R [ m i d + 1.. h i g h ] R[mid + 1..high] R[mid+1..high]递归,进行归并排序, 结果放入 S [ m i d + 1.. h i g h ] S[mid + 1..high] S[mid+1..high]中;
  4. 调用算法Merge, 将有序的两个子序列 S [ l o w . . m i d ] S[low.. mid] S[low..mid] S [ m i d + 1.. h i g h ] S[mid + 1..high] S[mid+1..high]归并为一个有序的序列 T [ l o w . . h i g h ] T[low.. high] T[low..high]

图片来源:https://www.runoob.com/wp-content/uploads/2019/03/mergeSort.gif

算法描述

void Merge(vector<int> &Array, int low, int mid, int high) 
{
    vector<int> LeftSubArray(Array.begin() + low, Array.begin() + mid + 1);
    vector<int> RightSubArray(Array.begin() + mid + 1, Array.begin() + high + 1);
    int idxLeft = 0, idxRight = 0;
    LeftSubArray.insert(LeftSubArray.end(), numeric_limits<int>::max());
    RightSubArray.insert(RightSubArray.end(), numeric_limits<int>::max());
    for (int i = low; i <= high; i++) 
    {
        if (LeftSubArray[idxLeft] < RightSubArray[idxRight]) 
        {
            Array[i] = LeftSubArray[idxLeft++];
        } 
        else 
        {
            Array[i] = RightSubArray[idxRight++];
        }
    }
}

void MergeSort(vector<int> &Array, int low, int high) 
{
    if (low >= high)
        return;
    int mid = (low + high) / 2;
    MergeSort(Array, low, mid);
    MergeSort(Array, mid + 1, high);
    Merge(Array, low, mid, high);
}

代码来源:https://www.runoob.com/w3cnote/merge-sort.html

算法分析

n n n个记录时需要进行 ⌈ l o g 2 n ⌉ \lceil log_2n \rceil log2n趟归并排序,每次比较次数不超过 n n n,移动次数均为 n n n,所以时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

空间复杂度在使用顺序表实现时为 O ( n ) O(n) O(n)

归并排序是稳定排序

基数排序

算法步骤

算法描述

int maxbit(int data[], int n) //辅助函数,求数据的最大位数
{
    int maxData = data[0];              ///< 最大数
    /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
    for (int i = 1; i < n; ++i)
    {
        if (maxData < data[i])
            maxData = data[i];
    }
    int d = 1;
    int p = 10;
    while (maxData >= p)
    {
        //p *= 10; // Maybe overflow
        maxData /= 10;
        ++d;
    }
    return d;
/*    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;*/
}
void radixsort(int data[], int n) //基数排序
{
    int d = maxbit(data, n);
    int *tmp = new int[n];
    int *count = new int[10]; //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //进行d次排序
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; //统计每个桶中的记录数
            count[k]++;
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
        for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete []tmp;
    delete []count;
}

代码来源:https://www.runoob.com/w3cnote/radix-sort.html

算法分析

时间复杂度 O ( k × n ) O(k\times n) O(k×n),其中 k k k为桶的个数

基数排序是稳定的排序方法

参考

严蔚敏 李冬梅 吴伟民 《数据结构(C语言版)(第二版)》

菜鸟教程

猜你喜欢

转载自blog.csdn.net/i0o0iW/article/details/108441265
今日推荐