归并排序 与 计数排序

简介: 归并排序 与 计数排序

1.归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序核心步骤:

动图演示:

1.1 递归实现归并排序:

归并排序类似于二叉树中的后序遍历,先让整个数组分为两个子序列,归并这两部份子序列,但是归并需要两部份子序列有序,然后取小的尾插到一个新开辟的数组中,归并完成后后再拷贝回原数组,如何让子序列有序,还要再次将每个子序列分为两部分,直到每个子序列只有一个值,这时已经递归到最深处,然会递归向回归并。

递归代码实现:

//归并排序
//开辟好空间后由下面元素调用此函数
void _MergeSort(int* arr, int* tmp, int begin, int end)
{
  if (begin == end)
  {
    return;
  }
  int midi = (begin + end) / 2;
  _MergeSort(arr, tmp, begin, midi);
  _MergeSort(arr, tmp, midi+1, end);
  int begin1 = begin;
  int end1 = midi;
  int begin2 = midi + 1;
  int end2 = end;
  int i = begin;
  //归并  取小的尾插到开辟的空间
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (arr[begin1] <= arr[begin2])
    {
      tmp[i++] = arr[begin1++];
    }
    else
    {
      tmp[i++] = arr[begin2++];
    }
  }
  while (begin1 <= end1)
  {
    tmp[i++] = arr[begin1++];
  }
  while (begin2 <= end2)
  {
    tmp[i++] = arr[begin2++];
  }
    //将归并好的两组数据拷贝会原数组
  memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* arr, int n)
{
    //开辟空间
  int* tmp = (int*)malloc(sizeof(int) * n);
  _MergeSort(arr, tmp, 0, n - 1);
}

小区间优化

//小区间优化
if (end - begin +1<10)
{
    //使用插入排序
  InsertSort(arr + begin, end - begin + 1);
  return;
}

优化的本质是减小递归调用的次数,由于二叉树的性质。我们可以得出满二叉树后三层大约占总个数的85%。为了减小递归开销,我们可以将小区间的递归调用改为直接插入排序可以提高一点排序的性能,但也不会提高很多。快排也可以使用这种方式优化。

1.2 非递归实现归并排序

我们可以先让每组gap=1个数据,每次归并两组,然后在让gap*=2,再次归并,直到gap>n。

代码实现:

//非递归实现归并排序
void MergeSortNonR1(int* arr, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  //每组有gap个数据,归并两组
  int gap = 1;
  while (gap < n)
  {
    int j = 0;
    for (int i = 0; i < n; i += 2 * gap)
    {
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;
      if (end1 >= n || begin2 >= n)//不需要归并
      {
        break;
      }
      //修正
      if (end2 >= n)
      {
        end2 = n - 1;
      }
      //归并
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (arr[begin1] <= arr[begin2])
        {
          tmp[j++] = arr[begin1++];
        }
        else
        {
          tmp[j++] = arr[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = arr[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = arr[begin2++];
      }
      //将归并后的两组数据 拷贝回原数组 
      memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
    }
    gap *= 2;
  }
}

边界越界问题:

int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;

begin1不会越界,因为begin1 = i,i 复合循环条件 。

  1. end1,begin2,end2都越界
  2. begin2,end2越界
  3. end2越界

1. end1,begin2,end2都越界

  此时不需要归并直接跳出循环。

2. begin2,end2越界

此时也不需要归并直接跳出循环。

3. end2越界

此时需要归并,但是我们要修改end2,将end2改为n-1。

代码:

if (end1 >= n || begin2 >= n)//不需要归并
  {
    break;
  }
  //修正
  if (end2 >= n)
  {
    end2 = n - 1;
  }

1.3 归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

1.4 外部排序

概念:当数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

在我们所学的排序算法中,只有非递归归并排序的思想可以用于外部排序。其他排序算法都只适用于内部排序,因为他们都使用了下标来进行随机存取,而非递归归并排序不需要,是顺序存取,这里举个例子:

假如我们由100亿个整数要排序,也就是大约40G,而我们的内存中只有1G,步骤:

  1. 把40G的文件分为40份。
  2. 让每份文件依次放到内部中排序,让40份文件内部有序。
  3. 两两归并,分别从两个文件中读一个数据,然后选小的写文件,这时就与非递归归并排序相同了。

2.计数排序

思想:计数排序又称为鸽巢原理,是一种非比较排序,是对哈希直接定址法的变形应用。

2.1 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

代码实现:

// 计数排序
void CountSort(int* arr, int n)
{
  //遍历 确定最大值与最小值
  int max = arr[0];
  int min = arr[0];
  for (int i = 0; i < n; i++)
  {
    if (arr[i] < min)
    {
      min = arr[i];
    }
    if (arr[i] > max)
    {
      max = arr[i];
    }
  }
  //遍历计数
  int range = max - min + 1;
  int* CountA = (int*)malloc(sizeof(int) * range);
  memset(CountA, 0, sizeof(int) * range);
  for (int i = 0; i < n; i++)
  {
    CountA[arr[i] - min]++;
  }
  //回收到原数组
  int j = 0;
  for (int i = 0; i < range; i++)
  {
    while (CountA[i]--)
    {
      arr[j++] = i + min;
    }
  }
}

2.2 计数排序的特性总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)
  4. 稳定性:稳定

3. 7种常见比较排序比较

排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性
冒泡排序 O(N^2) O(N) O(N^2) O(1) 稳定
简单选择排序 O(N^2) O(N^2) O(N^2) O(1) 不稳定
直接插入排序 O(N^2) O(N) O(N^2) O(1) 稳定
希尔排序 O(NlogN)~O(N^2) O(N^1.3) O(N^2) O(1) 不稳定
堆排序 O(NlogN) O(NlogN) O(N*logN) O(1) 不稳定
归并排序 O(NlogN) O(NlogN) O(N*logN) O(n) 稳定
快速排序 O(NlogN) O(NlogN) O(N^2) O(logn)~O(n) 不稳定

本篇结束!

相关文章
|
5月前
|
搜索推荐 算法
【C/排序算法】:归并排序和计数排序
【C/排序算法】:归并排序和计数排序
38 0
|
6月前
|
索引
快速排序与归并排序
快速排序与归并排序
|
算法 搜索推荐
归并排序与非比较排序详解
归并排序与非比较排序详解
71 0
|
6月前
|
存储
堆排序、快速排序和归并排序
堆排序、快速排序和归并排序
46 0
|
存储 搜索推荐 算法
八大排序算法-直接插入排序、希尔排序、直接选择排序、冒泡排序、堆排序、快速排序、归并排序、基数排序(下)
八大排序算法-直接插入排序、希尔排序、直接选择排序、冒泡排序、堆排序、快速排序、归并排序、基数排序(下)
|
6月前
|
搜索推荐 算法
排序算法——计数排序
排序算法——计数排序
|
6月前
|
搜索推荐 BI
排序算法:非比较排序(计数排序)
排序算法:非比较排序(计数排序)
77 0
|
6月前
|
搜索推荐 算法 Java
java排序算法:快速排序、归并排序、堆排序等
排序算法:快速排序、归并排序、堆排序等
92 0
|
算法 搜索推荐
归并排序与计数排序
归并排序与计数排序
75 0
|
搜索推荐 算法
【排序算法(四)】归并排序&&计数排序(非比较排序)以及八大排序算法的总结(上)
【排序算法(四)】归并排序&&计数排序(非比较排序)以及八大排序算法的总结(上)