归并排序、希尔排序、堆排序和基数排序简析

归并排序

简述

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

排序步骤

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

设定两个指针,最初位置分别为两个已经排序序列的起始位置;

比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

重复步骤 3 直到某一指针达到序列尾;

将另一序列剩下的所有元素直接复制到合并序列尾。

在这里插入图片描述

代码实现

 void mergearray(int a[], int first, int mid, int last, int temp[])  
  {  
     int i = first, j = mid + 1;  
      int m = mid,   n = last;  
      int k = 0;  
        
      while (i <= m && j <= n)  
     {  
         if (a[i] <= a[j])  
             temp[k++] = a[i++];  
         else  
             temp[k++] = a[j++];  
     }  
       
     while (i <= m)  
         temp[k++] = a[i++];        
     while (j <= n)  
         temp[k++] = a[j++];  
       
     for (i = 0; i < k; i++)  
        a[first + i] = temp[i];  
 }  
 void mergesort(int a[], int first, int last, int temp[])  
 {  
     if (first < last)  
     {  
         int mid = (first + last) / 2;  
         mergesort(a, first, mid, temp);    //左边有序  
         mergesort(a, mid + 1, last, temp); //右边有序  
         mergearray(a, first, mid, last, temp); //再将二个有序数列合并  
     }  
 }  
   
 bool MergeSort(int a[], int n)  
 {  
     int *p = new int[n];  
     if (p == NULL)  
         return false;  
     mergesort(a, 0, n - 1, p);  
     delete[] p;  
     return true;  
 } 

复杂度分析

时间复杂度:O(nlogn)
空间复杂度:O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序
稳定性:归并排序是稳定的排序算法,temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];这行代码可以保证当左右两部分的值相等的时候,先复制左边的值,这样可以保证值相等的时候两个元素的相对位置不变。

希尔排序

希尔排序是基础插入排序的一种优化版本,它的具体排序实现还是依靠插入排序实现的,希尔排序是一种非稳定排序。
步骤:
1.确定一个增量(选数之间的间隔)。
2.依靠增量将原序列分成几个序列。
3.将每个序列用插入排序排好。
4.减少增量,重复2,3步骤,直到增量为1,希尔排序完成。

例:(来自百度百科)
假设待排序文件有10个记录,其关键字分别是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次为:
5,2,1

基数排序介绍

基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较
具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

基数排序思想

1.求出数组中最大的元素。
2.求出最大元素是几位数。设为i位。
3.对所有的数进行i轮排序。首先排个位,然后在十位,然后百位…
4.每一轮的排位都需要分桶,桶是有顺序的,然后在把桶里的数按顺序放入原来的数组中。
5.直到i轮排序结束后,数组排序完成。

图文介绍

通过基数排序对数组{53, 3, 542, 748, 14, 214, 154, 63, 616},它的示意图如下:
在这里插入图片描述

在上图中,首先将所有待比较树脂统一为统一位数长度,接着从最低位开始,依次进行排序。
1. 按照个位数进行排序。
2. 按照十位数进行排序。
3. 按照百位数进行排序。

排序后,数列就变成了一个有序序列。

代码实现

#include
using namespace std;

/*

  • 获取数组a中最大值
  • 参数说明:
  • a -- 数组
    
  • n -- 数组长度
    

*/
int getMax(int a[], int n)
{
int i, max;

max = a[0];
for (i = 1; i < n; i++)
    if (a[i] > max)
        max = a[i];
return max;

}

/*

  • 对数组按照"某个位数"进行排序(桶排序)
  • 参数说明:
  • a -- 数组
    
  • n -- 数组长度
    
  • exp -- 指数。对数组a按照该指数进行排序。
    
  • 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
  • (01) 当exp=1表示按照"个位"对数组a进行排序
  • (02) 当exp=10表示按照"十位"对数组a进行排序
  • (03) 当exp=100表示按照"百位"对数组a进行排序

  • */
    void countSort(int a[], int n, int exp)
    {
    int output[n]; // 存储"被排序数据"的临时数组
    int i, buckets[10] = {0};
// 将数据出现的次数存储在buckets[]中
for (i = 0; i < n; i++)
    buckets[ (a[i]/exp)%10 ]++;

// 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。
for (i = 1; i < 10; i++)
    buckets[i] += buckets[i - 1];

// 将数据存储到临时数组output[]中
for (i = n - 1; i >= 0; i--)
{
    output[buckets[ (a[i]/exp)%10 ] - 1] = a[i];
    buckets[ (a[i]/exp)%10 ]--;
}

// 将排序好的数据赋值给a[]
for (i = 0; i < n; i++)
    a[i] = output[i];

}

/*

  • 基数排序
  • 参数说明:
  • a -- 数组
    
  • n -- 数组长度
    

*/

void radixSort(int a[], int n)
{
   int exp;    // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
   int max = getMax(a, n);    // 数组a中的最大值

   // 从个位开始,对数组a按"指数"进行排序
   for (exp = 1; max/exp > 0; exp *= 10)
       countSort(a, n, exp);
}

int main()
{
   int i;
   int a[] = {53, 3, 542, 748, 14, 214, 154, 63, 616};
   int ilen = (sizeof(a)) / (sizeof(a[0]));

   cout << "before sort:";
   for (i=0; i<ilen; i++)
       cout << a[i] << " ";
   cout << endl;

   radixSort(a, ilen);    // 基数排序

   cout << "after  sort:";
   for (i=0; i<ilen; i++)
       cout << a[i] << " ";
   cout << endl;
   return 0;
}

堆排序算法

基本介绍

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

在这里插入图片描述

基本步骤

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列。

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

a.假设给定无序序列结构如下
 在这里插入图片描述

2.此时我们从最后一个非叶子结点开始(叶子结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
在这里插入图片描述

3.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
在这里插入图片描述

交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
在这里插入图片描述

此时,我们就将一个无需序列构造成了一个大顶堆。

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

1.将堆顶元素9和末尾元素4进行交换
在这里插入图片描述

2.重新调整结构,使其继续满足堆定义
在这里插入图片描述

3.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
在这里插入图片描述

继续进行调整,交换,如此反复进行,最终使得整个序列有序

在这里插入图片描述

总结下堆排序的基本思路:

a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

三、时间和空间复杂度
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值