手撕各种排序(下)

简介: 手撕各种排序(下)

手撕各种排序(中):https://developer.aliyun.com/article/1389401

🌙归并排序

这里讲述两种方法,一种是递归型另一种则是非递归

💫归并排序递归型

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



void _MergeSort(int* a, int* tmp, int begin, int end)
{
  //尾小于头时
  if (end <= begin)
    return;
  //开始分割
  int mid = (end + begin) / 2;
  // [begin, mid][mid+1, end]
  _MergeSort(a, tmp, begin, mid);
  _MergeSort(a, tmp, mid + 1, end);
  //归并到tmp数据组,再拷贝回去
  //a->[begin, mid][mid+1, end]->tmp
  int begin1 = begin, end1 = mid;
  int begin2 = mid + 1, end2 = end;
  int index = begin;
  //开始拷贝
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (a[begin1] < a[begin2])
    {
      tmp[index++] = a[begin1++];
    }
    else
    {
      tmp[index++] = a[begin2++];
    }
  }
  //当第一段元素没有完全拷贝完就把剩下的拷贝进去
  while (begin1 <= end1)
  {
    tmp[index++] = a[begin1++];
  }
  //当第二段元素没有完全拷贝完就把剩下的拷贝进去
  while (begin2 <= end2)
  {
    tmp[index++] = a[begin2++];
  }
  //拷贝回原数组
  memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
//归并排序(分路并归)
void MergeSort(int* a, int n)
{
  //开辟空间
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  //调用
  _MergeSort(a, tmp, 0, n - 1);
  //释放内存
  free(tmp);
}

💫归并排序非递归型

      第一次当 gap 为 1 的时候,我们将距离为 1 的两个数组(其实就是两个元素为 1 的数)进行归并,得到了拥有两个元素的一个有序数组,然后通过循环将后面的元素全部用同样的方式归并。然后就得到了如上图所示蓝色表示的元素排布。同时我们在将这一步骤之后的数组拷贝回到原数组。在进行接下来的操作(这里的意思和上递归的是一样的)。接着每次将 gap 的值增加 2 倍,直到 gap 的值不小于 n 结束归并。这个过程我们将其称为小区间优化。



//归并排序(非递归)
void MergeSortNonR(int* a, int n)
{
  //开辟空间
  int* tmp = (int*)malloc(sizeof(int) * n);
  //判断
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  int gap = 1;
  while (gap < n)
  {
    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;
      // [begin1,end1] [begin2,end2] 归并
      // 如果第二组不存在,这一组不用归并了
      if (begin2 >= n)
      {
        break;
      }
      // 如果第二组的右边界越界,修正一下
      if (end2 >= n)
      {
        end2 = n - 1;
      }
      //printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
      int index = i;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[index++] = a[begin1++];
        }
        else
        {
          tmp[index++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[index++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[index++] = a[begin2++];
      }
      // 拷贝回原数组
      memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
    }
    printf("\n");
    gap *= 2;
  }
  free(tmp);
}

🌙计数排序

计数排序的朴素方法步骤:

       1、从无序数组中取出最大值max,新建一个长度为max+1的数组。

       2、遍历无序数组,取其中元素作为新建数组的索引,存在一个则新数组该索引所在的值自增。

       3、遍历新数组,当存在不为0的元素,取该元素的索引放入最终数组,并且该元素自减,直到为0,返回最终数组。

 



上代码:

//计数排序测试
void CountSort(int* a, int n)
{
  //找最大值和最小值
  int min = a[0], max = a[0];
  for (size_t i = 0; i < n; i++)
  {
    if (a[i] < min)
      min = a[i];
    if (a[i] > max)
      max = a[i];
  }
  //找出区间
  int range = max - min + 1;
  //开辟(区间)
  int* count = (int*)malloc(sizeof(int) * range);
  printf("range:%d\n", range);
  //判断
  if (count == NULL)
  {
    perror("malloc fail");
    return;
  }
  //计入数字的个数
  memset(count, 0, sizeof(int) * range);
  // 统计数据出现次数
  for (int i = 0; i < n; i++)
  {
    count[a[i] - min]++;
  }
  //排序
  int j = 0;
  for (int i = 0; i < range; i++)
  {
    while (count[i]--)
    {
      a[j++] = i + min;
    }
  }
}

⭐总结

咱看看每种排序的复杂度和稳定性。


🌠Sort.h代码

#include<stdio.h>
//打印数据
void PrintArray(int* a, int n);
//冒泡排序测试
void BubbleSort(int* a, int n);
//插入排序测试
void InsertSort(int* a, int n);
//希尔排序测试
void ShellSort(int* a, int n);
//选择排序测试
void SelectSort(int* a, int n);
//堆排序测试
void HeapSort(int* a, int n);
//快排测试
void QuickSort1(int* a, int begin, int end);
void QuickSort2(int* a, int begin, int end);
//快排非递归测试
void QuickSortNonR(int* a, int begin, int end);
//归并排序测试
void MergeSort(int* a, int n);
//归并排序(非递归)测试
void MergeSortNonR(int* a, int n);
//计数排序测试
void CountSort(int* a, int n);

🌠Test.c代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<time.h>
#include<stdlib.h>
#include"Sort.h"
#include"Stack.h"
//希尔排序测试
void TestShellSort()
{
  int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
  ShellSort(a, sizeof(a) / sizeof(int));
  PrintArray(a, sizeof(a) / sizeof(int));
}
//冒泡排序测试
void TestBubbleSort()
{
  int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
  BubbleSort(a, sizeof(a) / sizeof(int));
  PrintArray(a, sizeof(a) / sizeof(int));
}
//堆排序测试
void TestHeapSort()
{
  int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
  HeapSort(a, sizeof(a) / sizeof(int));
  PrintArray(a, sizeof(a) / sizeof(int));
}
//选择排序测试
void TestSelectSort()
{
  int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
  SelectSort(a, sizeof(a) / sizeof(int));
  PrintArray(a, sizeof(a) / sizeof(int));
}
//测试代码
void TestOP()
{
  srand(time(0));
  const int N = 100000;
  int* a1 = (int*)malloc(sizeof(int) * N);
  int* a2 = (int*)malloc(sizeof(int) * N);
  int* a3 = (int*)malloc(sizeof(int) * N);
  int* a4 = (int*)malloc(sizeof(int) * N);
  int* a5 = (int*)malloc(sizeof(int) * N);
  int* a6 = (int*)malloc(sizeof(int) * N);
  int* a7 = (int*)malloc(sizeof(int) * N);
  for (int i = N - 1; i >= 0; --i)
  {
    a1[i] = rand();
    a2[i] = a1[i];
    a3[i] = a1[i];
    a4[i] = a1[i];
    a5[i] = a1[i];
    a6[i] = a1[i];
    a7[i] = a1[i];
  }
  int begin1 = clock();
  //插入排序
  InsertSort(a1, N);
  int end1 = clock();
  int begin2 = clock();
  //希尔排序测试
  ShellSort(a2, N);
  int end2 = clock();
  int begin7 = clock();
  //冒泡排序测试
  BubbleSort(a7, N);
  int end7 = clock();
  int begin3 = clock();
  //选择排序测试
  SelectSort(a3, N);
  int end3 = clock();
  int begin4 = clock();
  //堆排序测试
  HeapSort(a4, N);
  int end4 = clock();
  int begin5 = clock();
  //快排测试
  //QuickSort(a5, 0, N - 1);
  int end5 = clock();
  int begin6 = clock();
  //MergeSort(a6, N);
  //计数排序测试
  //CountSort(a6, N);
  int end6 = clock();
  printf("InsertSort:%d\n", end1 - begin1);
  printf("ShellSort:%d\n", end2 - begin2);
  printf("BubbleSort:%d\n", end7 - begin7);
  printf("SelectSort:%d\n", end3 - begin3);
  printf("HeapSort:%d\n", end4 - begin4);
  printf("QuickSort:%d\n", end5 - begin5);
  printf("MergeSort:%d\n", end6 - begin6);
  printf("CountSort:%d\n", end6 - begin6);
  free(a1);
  free(a2);
  free(a3);
  free(a4);
  free(a5);
  free(a6);
  free(a7);
}
int main()
{
  TestOP();
  //TestBubbleSort();
  //TestHeapSort();
  //TestSelectSort();
  //TestQuickSort();
  //TestMergeSort();
  //TestCountSort();
  //printf("%d\n", Func(100000));
  return 0;
}

🌠Sort.c代码

#define _CRT_SECURE_NO_WARNINGS 1
#include"Sort.h"
#include"Stack.h"
//交换函数
void Swap(int* x, int* y)
{
  int tmp = *x;
  *x = *y;
  *y = tmp;
}
//打印数据
void PrintArray(int* a, int n)
{
  //循环
  for (int i = 0; i < n; i++)
  {
    printf("%d ", a[i]);
  }
  printf("\n");
}
//冒泡排序测试
void BubbleSort(int* a, int n)
{
  for (int i = 0; i < n - 1; i++)
  {
    int exchange = 0;
    for (int j = 1; j < n - i; j++)
    {
      if (a[i - 1] > a[i])
      {
        //这里是一个交换函数
        Swap(&a[i - 1], &a[i]);
        exchange = 1;
      }
    }
    //这里进行一趟有序时,直接跳出循环
    if (exchange == 0)
    {
      break;
    }
  }
}
//插入排序
void InsertSort(int* a, int n)
{
  for (int i = 0; i < n - 1; i++)
  {
    int end = i;
    int tmp = a[end + 1];
    //一个元素进行插入
    while (end >= 0)
    {
      if (tmp < a[end])
      {
        a[end + 1] = a[end];
      }
      else
      {
        break;
      }
      --end;
    }
    //最后把插入的元素赋值回去
    a[end + 1] = tmp;
  }
}
//希尔排序测试
void ShellSort(int* a, int n)
{
  int gap = n;
  while (gap > 1)
  {
    //间隔gap的元素进行排序
    gap = gap / 3 + 1;
    //本质上是插入排序
    for (int i = 0; i < n - gap; ++i)
    {
      int end = i;
      int tmp = a[end + gap];
      //一个元素进行插入
      while (end >= 0)
      {
        if (tmp < a[end])
        {
          a[end + gap] = a[end];
          end = end - gap;
        }
        else
        {
          break;
        }
      }
      //最后把插入的元素赋值回去
      a[end + gap] = tmp;
    }
  }
}
//void ShellSort(int* a, int n)
//{
  /*int gap = 3;
  for (int j = 0; j < gap; j++)
  {
    for (int i = j; i < n - gap; i += gap)
    {
      int end = i;
      int tmp = a[end + gap];
      while (end >= 0)
      {
        if (tmp < a[end])
        {
          a[end + gap] = a[end];
          end -= gap;
        }
        else
        {
          break;
        }
      }
      a[end + gap] = tmp;
    }
  } */
//选择排序测试
void SelectSort(int* a, int n)
{
  //初始化第一个元素 和 最后一个元素
  int begin = 0;
  int end = n - 1;
  while (begin < end)
  {
    //选出最大值和最小值
    int mini = begin, maxi = begin;
    for (int i = begin + 1; i <= end; i++)
    {
      if (a[i] > a[maxi])
      {
        maxi = i;
      }
      if (a[i] < a[mini])
      {
        mini = i;
      }
    }
    //最小值和最初的元素交换
    Swap(&a[begin], &a[mini]);
    //如果max被换走就需要重新赋值
    if (maxi == begin)
    {
      maxi = mini;
    }
    //最大值和最末的元素交换
    Swap(&a[end], &a[maxi]);
    ++begin;
    --end;
  }
}
//堆排序测试
//实现向下调整建大堆
void AdjustDown(int* a, int n, int parent)
{
  //孩子和父亲的关系 
  int child = parent * 2 + 1;
  while (child < n)
  {
    //找出小的那个孩子
    if (child + 1 < n && a[child + 1] < a[child])
    {
      ++child;
    }
    //实现向下调整元素
    if (a[child] < a[parent])
    {
      //元素交换
      Swap(&a[child], &a[parent]);
      // 继续往下调整
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
//堆排序测试
void HeapSort(int* a, int n)
{
  //向下调整建堆
  for (int i = (n - 1 - 1) / 2; i >= 0; i--)
  {
    //实现向下调整建大堆
    AdjustDown(a, n, i);
  }
  int end = n - 1;
  while (end > 0)
  {
    //元素交换
    Swap(&a[0], &a[end]);
    //实现向下调整建大堆
    AdjustDown(a, end, 0);
    --end;
  }
}
//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
  //中间值
  int mid = (left + right) / 2;
  //三个数比较找出中间值
  if (a[left] < a[mid])
  {
    if (a[mid] < a[right])
    {
      return mid;
    }
    else if (a[left] > a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
  else
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if (a[left] < a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}
// 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
int PartSort1(int* a, int left, int right)
{
  //找中间值(相对)
  int midi = GetMidi(a, left, right);
  //把最左边(相对)跟中间值(相对)换
  Swap(&a[left], &a[midi]);
  //定位最左边(相对)
  int keyi = left;
  while (left < right)
  {
    //找小
    while (left < right && a[right] >= a[keyi])
    {
      --right;
    }
    //找大
    while (left < right && a[left] <= a[keyi])
    {
      ++left;
    }
    //左右交换
    Swap(&a[left], &a[right]);
  }
  //此时左边走到中间了,开始交换
  Swap(&a[keyi], &a[left]);
  //返回
  return left;
};
//挖坑法
int PartSort2(int* a, int left, int right)
{
  //找中间值(相对)
  int midi = GetMidi(a, left, right);
  //把最左边(相对)跟中间值(相对)换
  Swap(&a[left], &a[midi]);
  //定位最左边(相对)
  int key = a[left];
  //保存key值以后,左边形成第一个坑
  int hole = left;
  while (left < right)
  {
    //右边先走,找小,填到左边的坑,右边形成新的坑位
    while (left < right && a[right] >= key)
    {
      --right;
    }
    a[hole] = a[right];
    hole = right;
    //左边再走,找大,填到右边的坑,左边形成新的坑位
    while (left < right && a[left] <= key)
    {
      ++left;
    }
    a[hole] = a[left];
    hole = left;
  }
  a[hole] = key;
  return hole;
}
//前后指针
int PartSort3(int* a, int left, int right)
{
  //找中间值(相对)
  int midi = GetMidi(a, left, right);
  //把最左边(相对)跟中间值(相对)换
  Swap(&a[left], &a[midi]);
  //定义指针开头
  int prev = left;
  //定义指针开头后一个
  int cur = prev + 1;
  int keyi = left;
  while (cur <= right)
  {
    if (a[cur] < a[keyi] && ++prev != cur)
    {
      Swap(&a[prev], &a[cur]);
    }
    ++cur;
  }
  //交换
  Swap(&a[prev], &a[keyi]);
  return prev;
}
//快排测试一
void QuickSort1(int* a, int begin, int end)
{
  if (begin >= end)
    return;
  //小区间优化,小区间不再递归分割排序,降低递归次数
  //当元素小于10时,采用插入排序
  if ((end - begin + 1) > 10)
  {
    //找值
    int keyi = PartSort3(a, begin, end);
    // [begin, keyi-1] keyi [keyi+1, end]
    QuickSort1(a, begin, keyi - 1);
    QuickSort1(a, keyi + 1, end);
  }
  else
  {
    //插入排序
    InsertSort(a + begin, end - begin + 1);
  }
}
//快排测试二
void QuickSort2(int* a, int begin, int end)
{
  if (begin >= end)
    return;
  //调用
  int keyi = PartSort3(a, begin, end);
  //[begin, keyi-1] keyi [keyi+1, end]
  QuickSort2(a, begin, keyi - 1);
  QuickSort2(a, keyi + 1, end);
}
//快排非递归(采用栈的形式)(先进后出)
void QuickSortNonR(int* a, int begin, int end)
{
  //定义
  ST st;
  //初始化
  STInit(&st);
  //添加元素
  STPush(&st, end);
  STPush(&st, begin);
  while (!STEmpty(&st))
  {
    //剥离元素  让left取先入但是后出的元素(区间的左边)
    int left = STTop(&st);
    STPop(&st);
    //让right取栈顶元素(是我们后入的区间的右边)
    int right = STTop(&st);
    STPop(&st);
    // 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
    int keyi = PartSort1(a, left, right);
    //分割成段 
    // [lefy,keyi-1] keyi [keyi+1, right]
    if (keyi + 1 < right)
    {
      //添加元素
      STPush(&st, right);
      STPush(&st, keyi + 1);
    }
    if (left < keyi - 1)
    {
      //添加元素
      STPush(&st, keyi - 1);
      STPush(&st, left);
    }
  }
  //销毁
  STDestroy(&st);
}
void _MergeSort(int* a, int* tmp, int begin, int end)
{
  //尾小于头时
  if (end <= begin)
    return;
  //开始分割
  int mid = (end + begin) / 2;
  // [begin, mid][mid+1, end]
  _MergeSort(a, tmp, begin, mid);
  _MergeSort(a, tmp, mid + 1, end);
  //归并到tmp数据组,再拷贝回去
  //a->[begin, mid][mid+1, end]->tmp
  int begin1 = begin, end1 = mid;
  int begin2 = mid + 1, end2 = end;
  int index = begin;
  //开始拷贝
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (a[begin1] < a[begin2])
    {
      tmp[index++] = a[begin1++];
    }
    else
    {
      tmp[index++] = a[begin2++];
    }
  }
  //当第一段元素没有完全拷贝完就把剩下的拷贝进去
  while (begin1 <= end1)
  {
    tmp[index++] = a[begin1++];
  }
  //当第二段元素没有完全拷贝完就把剩下的拷贝进去
  while (begin2 <= end2)
  {
    tmp[index++] = a[begin2++];
  }
  //拷贝回原数组
  memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
//归并排序(分路并归)
void MergeSort(int* a, int n)
{
  //开辟空间
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  //调用
  _MergeSort(a, tmp, 0, n - 1);
  //释放内存
  free(tmp);
}
//归并排序(非递归)
void MergeSortNonR(int* a, int n)
{
  //开辟空间
  int* tmp = (int*)malloc(sizeof(int) * n);
  //判断
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  int gap = 1;
  while (gap < n)
  {
    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;
      // [begin1,end1] [begin2,end2] 归并
      // 如果第二组不存在,这一组不用归并了
      if (begin2 >= n)
      {
        break;
      }
      // 如果第二组的右边界越界,修正一下
      if (end2 >= n)
      {
        end2 = n - 1;
      }
      //printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
      int index = i;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[index++] = a[begin1++];
        }
        else
        {
          tmp[index++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[index++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[index++] = a[begin2++];
      }
      // 拷贝回原数组
      memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
    }
    printf("\n");
    gap *= 2;
  }
  free(tmp);
}
//计数排序测试
void CountSort(int* a, int n)
{
  //找最大值和最小值
  int min = a[0], max = a[0];
  for (size_t i = 0; i < n; i++)
  {
    if (a[i] < min)
      min = a[i];
    if (a[i] > max)
      max = a[i];
  }
  //找出区间
  int range = max - min + 1;
  //开辟(区间)
  int* count = (int*)malloc(sizeof(int) * range);
  printf("range:%d\n", range);
  //判断
  if (count == NULL)
  {
    perror("malloc fail");
    return;
  }
  //计入数字的个数
  memset(count, 0, sizeof(int) * range);
  // 统计数据出现次数
  for (int i = 0; i < n; i++)
  {
    count[a[i] - min]++;
  }
  //排序
  int j = 0;
  for (int i = 0; i < range; i++)
  {
    while (count[i]--)
    {
      a[j++] = i + min;
    }
  }
}

🌟结束语

      今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

目录
相关文章
|
6天前
|
搜索推荐
手撕各种排序(中)
手撕各种排序(中)
49 0
|
6天前
|
安全 Java C语言
手撕各种排序(上)
手撕各种排序
30 0
|
8月前
|
算法 搜索推荐
【数据结构与算法篇】手撕八大排序算法之交换排序
【数据结构与算法篇】手撕八大排序算法之交换排序
33 0
|
8月前
|
机器学习/深度学习 存储 算法
【数据结构与算法篇】 手撕八大排序算法之选择排序
【数据结构与算法篇】 手撕八大排序算法之选择排序
38 0
|
8月前
|
算法 程序员
手撕代码
手撕代码是什么
|
9月前
|
缓存 调度
手撕代码系列(四)
手撕代码系列(四)
|
9月前
|
搜索推荐 算法 Shell
手撕希尔排序
手撕希尔排序
53 0
|
10月前
|
存储
手撕二叉搜索树
手撕二叉搜索树
41 0
【手撕归并排序】
一、归并排序是什么? 归并排序是将一段区间分成若干个子问题,子问题再次分成子问题,这个是分治过程;最后分成的子问题只存在一个数时,就可以开始合并,合并的过程就是比较两个子问题的过程,合并完成后将合并的新数据拷贝到原数据即可。