<<算法很美>>——(三)十大排序算法(上)(一)

简介: <<算法很美>>——(三)十大排序算法(上)

前言


原来在前面数据结构与算法那里整理了七大常见算法,现在打算在其基础上再增加三个算法供大家伙参考。

推荐个网站可以看动态排序算法,更好地帮助你理解

Data Structure Visualization

冒泡排序


*  比较相邻的元素。如果第一个比第二个大,就交换它们两个;

* 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;

* 针对所有的元素重复以上的步骤,除了最后一个;

* 重复步骤1~3,直到排序完成。

图解冒泡


image.png

6d651ecddcc64b5bafaaa5177b587f0e.gif

代码实现


void swap(int* a, int* b)
{
  int temp = *a;
  *a = *b;
  *b = temp;
}
void bubblesort(int* a, int n)
{
    //10,6,7,1,3,9,4,2 
  for (int i = 0; i < n - 1; i++)//趟数
  {
    for (int j = 0; j < n - 1 - i; j++)//比较次数
    {
      if (a[j] > a[j + 1])
      {
        swap(&a[j], &a[j + 1]);
      }
    }
  }
}

冒泡优化


冒泡有一个最大的问题就是这种算法不管不管你有序还是没序,都先循环比较。 比如我举个数组例子:[1,2,3,4,5],一个有序的数组,根本不需要排序,它仍然是双层循环一个不少的把数据遍历干净,这其实就是做了没必要做的事情,属于浪费资源。针对这个问题,我们可以设定一个临时遍历来标记该数组是否已经有序,如果有序了就不用遍历了.

void swap(int* a, int* b)
{
  int temp = *a;
  *a = *b;
  *b = temp;
}
void bubblesort(int* a, int n)
{
    //10,6,7,1,3,9,4,2 
  for (int i = 0; i < n - 1; i++)//趟数
  {
            int flag=0;
    for (int j = 0; j < n - 1 - i; j++)//比较次数
    {
      if (a[j] > a[j + 1])
      {
        swap(&a[j], &a[j + 1]);
                   flag=1;
      }
    }
            if(flag==0)
               break;
  }
}

冒泡排序的特征总结:

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:稳定

选择排序


首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

重复第二步,直到所有元素均排序完毕。

图解选排


image.png

image.gif

代码实现


void swap(int* a, int* b)
{
  int temp = *a;
  *a = *b;
  *b = temp;
}
void selectsort(int* a, int n)
{
  for (int i = 0; i < n - 1; i++)
  {
    int min = i;
    for (int j = i + 1; j < n; j++)//走访未排列的元素
    {
      if (a[j] < a[min])//找到目前最小值
        min = j;//记录最小值
    }
    swap(&a[min], &a[i]);
  }
}

直接选择排序的特征总结:

   1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

   2. 时间复杂度:O(N^2)

   3. 空间复杂度:O(1)

   4. 稳定性:不稳定

插入排序


  * 从第一个元素开始,该元素可以认为已经被排序;

   * 取出下一个元素,在已经排序的元素序列中从后向前扫描;

   * 如果该元素(已排序)大于新元素,将该元素移到下一位置;

   * 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

   * 将新元素插入到该位置后;

   * 重复步骤2~5。

图解插入


09caf3b951fb4724bb4bbf4fda1b4387.gif

代码实现


void InsertSort(int* a, int n)
{
    //n:待排序数字的个数;n=sizeof(a)/sizeof(int);
  assert(a);//函数断言
  for (int i = 0; i < n - 1; ++i)
  {
    // 将x插入[0, end]有序区间
    int end = i;
    int x = a[end+1];
    while (end >= 0)
    {
      if (a[end] > x)
      {
        a[end + 1] = a[end];
        --end;
      }
      else
      {
        break;
      }
    }
    a[end + 1] = x;
  }
}

直接插入排序特性总结:

  1.元素集合越接近有序,直接插入排序算法的时间效率越高;

  2. 时间复杂度:O(N^2) ;

  3. 空间复杂度:O(1),它是一种稳定的排序算法 ;

  4. 稳定性:稳定;

希尔排序


希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所 有记录分成个 组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后 取, 重复上述分组和排序的工 作。当到达=1时,所有记录在统一组内排好序。

图解希尔


image.png

image.gif

代码实现:


void ShellSort(int* a, int n)
{
    //n:待排序数字的个数;n=sizeof(a)/sizeof(int);
  // 多次预排序(gap > 1) +直接插入 (gap == 1)
  int gap = n;
  while (gap > 1)
  {
    //gap = gap / 2;
    gap = gap / 3 + 1;
    // 多组并排
    for (int i = 0; i < n - gap; ++i)
    {
      int end = i;
      int x = a[end + gap];
      while (end >= 0)
      {
        if (a[end] > x)
        {
          a[end + gap] = a[end];
          end -= gap;
        }
        else
        {
          break;
        }
      }
      a[end + gap] = x;
    }
  } 
}

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。

 2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的      了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对      比。

3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3       —          N^2)

 4. 稳定性:不稳定

归并排序


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

* 把长度为n的输入序列分成两个长度为n/2的子序列;

* 对这两个子序列分别采用归并排序;

* 将两个排序好的子序列合并成一个最终的排序序列。

图解归并


image.png

60eef17032bc4374812c5ac944a29d94.gif

代码实现


递归

void _MergeSort(int* a, int left, int right, int* tmp)
{
  if (left >= right)
  {
    return;
  }
  int mid = (left + right) / 2;
  // [left, mid] [mid+1, right] 有序
  _MergeSort(a, left, mid, tmp);
  _MergeSort(a, mid + 1, right, tmp);
  int begin1 = left, end1 = mid;
  int begin2 = mid+1, end2 = right;
  int i = left;
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (a[begin1] < a[begin2])
    {
      tmp[i++] = a[begin1++];
    }
    else
    {
      tmp[i++] = a[begin2++];
    }
  }
  while (begin1 <= end1)
  {
    tmp[i++] = a[begin1++];
  }
  while (begin2 <= end2)
  {
    tmp[i++] = a[begin2++];
  }
  // tmp 数组拷贝回a
  for (int j = left; j <= right; ++j)
  {
    a[j] = tmp[j];
  }
}
void MergeSort(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int)*n);
  if (tmp == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  _MergeSort(a, 0, n - 1, tmp);
  free(tmp);
  tmp = NULL;
}

非递归

void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int)*n);
  if (tmp == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  int gap = 1;
  while (gap < n)
  {
    for (int i = 0; i < n; i += 2 * gap)
    {
      // [i,i+gap-1] [i+gap,i+2*gap-1]
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;
      // 核心思想:end1、begin2、end2都有可能越界
      // end1越界 或者 begin2 越界都不需要归并
      if (end1 >= n || begin2 >= n)
      {
        break;
      }
      // end2 越界,需要归并,修正end2
      if (end2 >= n)
      {
        end2 = n- 1;
      }
      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++];
      }
      // 把归并小区间拷贝回原数组
      for (int j = i; j <= end2; ++j)
      {
        a[j] = tmp[j];
      }
    }
    gap *= 2;
  }
  free(tmp);
  tmp = NULL;
}

归并排序的特征总结:

   1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外       排序问 题。

   2. 时间复杂度:O(N*logN)

   3. 空间复杂度:O(N)

   4. 稳定性:稳定

相关文章
|
6月前
|
搜索推荐 算法 Shell
【算法tips】面试官:说说常见的排序算法。—— 巧记十种排序算法名称
【算法tips】面试官:说说常见的排序算法。—— 巧记十种排序算法名称
478 2
|
6月前
|
JavaScript 算法 前端开发
【前端也要学算法系列】经典排序算法JS实现 —— 冒泡排序
【前端也要学算法系列】经典排序算法JS实现 —— 冒泡排序
436 0
|
4月前
|
存储 算法 搜索推荐
【算法训练-排序算法 三】【排序应用】合并区间
【算法训练-排序算法 三】【排序应用】合并区间
48 0
|
2月前
|
存储 分布式计算 搜索推荐
【数据结构排序算法篇】----对十大经典算法的总结
【数据结构排序算法篇】----对十大经典算法的总结
32 0
|
3月前
|
机器学习/深度学习 存储 人工智能
算法05-排序算法
算法05-排序算法
|
3月前
|
搜索推荐 JavaScript 前端开发
用openAI写个js的排序算法(快速排序算法)
用openAI写个js的排序算法(快速排序算法)
19 0
|
3月前
|
算法 搜索推荐 Java
数据结构与算法面试:基于比较的排序算法时间复杂度最坏情况下是 O(nlogn),请问有没有更快的算法?(提示:计数排序、基数排序)
数据结构与算法面试:基于比较的排序算法时间复杂度最坏情况下是 O(nlogn),请问有没有更快的算法?(提示:计数排序、基数排序)
21 0
|
4月前
|
机器学习/深度学习 算法 搜索推荐
【算法训练-排序算法 一】【手撕排序】快速排序、堆排序、归并排序
【算法训练-排序算法 一】【手撕排序】快速排序、堆排序、归并排序
56 0
|
6月前
|
存储 搜索推荐 算法
【算法基础】一文掌握十大排序算法,冒泡排序、插入排序、选择排序、归并排序、计数排序、基数排序、希尔排序和堆排序
【算法基础】一文掌握十大排序算法,冒泡排序、插入排序、选择排序、归并排序、计数排序、基数排序、希尔排序和堆排序
68 2
【算法基础】一文掌握十大排序算法,冒泡排序、插入排序、选择排序、归并排序、计数排序、基数排序、希尔排序和堆排序
|
7月前
|
机器学习/深度学习 存储 算法
【数据结构与算法篇】手撕八大排序算法之快排的非递归实现及递归版本优化(三路划分)
【数据结构与算法篇】手撕八大排序算法之快排的非递归实现及递归版本优化(三路划分)
54 0

热门文章

最新文章