数据结构和算法之排序总结 上

简介: 数据结构和算法之排序总结

文章目录

一、排序的概念及应用

💦 排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次

序保持不变,即在原序列中,r[i]=r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排

序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

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

数据结构和算法动态可视化

💦 排序的运用

❗ 现实中排序的运用非常广泛,无处不在 ❕

好一个凡尔赛

💦 常见的排序算法

二、常见排序算法的实现

// 排序实现的接口
// 插入排序 void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
// 选择排序 
void SelectSort(int* a, int n);
// 堆排序 
void AdjustDwon(int* a, int n, int root); 
void HeapSort(int* a, int n);
// 冒泡排序 
void BubbleSort(int* a, int n)
// 快速排序递归实现 
// 快速排序hoare版本 
int PartSort1(int* a, int left, int right); 
// 快速排序挖坑法 
int PartSort2(int* a, int left, int right); 
// 快速排序前后指针法 
int PartSort3(int* a, int left, int right); 
void QuickSort(int* a, int left, int right);
// 快速排序 非递归实现 
void QuickSortNonR(int* a, int left, int right)
// 归并排序递归实现 
void MergeSort(int* a, int n) 
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
// 计数排序 void CountSort(int* a, int n);
}

💦 插入排序

1、直接插入排序

🔑 核心思想 🔑

  把待排序的记录按关键码的大小逐个插入到一个已经排好的序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列

实际中我们玩扑克牌时,就用了插入排序的思想

❗ 过程:❕

当插入第 i(i>=1) 个元素时,前面的 array[0], array[1], … , array[i-1] 已经排好序,此时用 array[i] 的排序码与 array[i-1], array[i-2],… 的排序码顺序进行比较,找到插入位置即将 array[i] 插入,原来位置上的元素顺序后移

❗ 直接插入排序的特性总结:❕

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

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

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

  4️⃣ 稳定性:稳定

❗ 动图演示:❕

🧿 实现代码 :

void InserSort(int* a, int n)
{
  //多趟控制
  int i = 0;
  for (i = 0; i < n - 1; i++)
  {
    //单趟控制
    int end = i ;
    int temp = a[end + 1];
    while (end >= 0)
    {
      //目标数小于其它数时,其它数就往后挪;大于则插入
      if (temp < a[end])
      {
        a[end + 1] = a[end];
        end--;
      }
      else
      {
        break;
      }
    }
    a[end + 1] = temp;
  }
}

❓ 插入排序的时间复杂度 ❔

  最坏的情况 - 逆序:O(N2)

  最好的情况 - 接近有序 :O(N)

2、希尔排序

希尔排序 (缩小增量排序)

🔑 核心思想 🔑

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

  人话就是:

    1️⃣ 预排序 (接近升序) - gap > 1

    2️⃣ 直接插入排序 - gap == 1

❗ 希尔排序特性总结 ❕

  1️⃣ 希尔排序是对直接插入排序的优化

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

  3️⃣ 希尔排序的时间复杂度并不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些数中给出的希尔排序的时间复杂度都不固定,官方给出的时间复杂度是 O(N1.3)

  4️⃣ 稳定性:不稳定

👁‍🗨 知识扩展

🧿 实现代码 :

代码的核心并不是一组一组的排,而是多组并排

以下只是预排序代码,还需要再调用 InsertSort 进行直接插入排序

void ShellSort(int* a, int n)
{
  int i = 0;
  int gap = 3;
  //多组并排
  for (i = 0; i < n - gap; i++)
  {
    int end = i;
    int temp = a[end + gap];
    while (end >= 0)
    {
      if (temp < a[end])
      {
        a[end + gap] = a[end];
        end -= gap;
      }
      else
      {
        break;
      }
    }
    a[end + gap] = temp;
  }
}

❓ 对于 gap 的值写成固定的并不好 ❔

  这里只是建议

void ShellSortPro(int* a, int n)
{
  //gap > 1 预排序
  //gap == 1 直接插入排序
  int i = 0;
  //gap的初始值为n
  int gap = n;
  while (gap > 1)
  {
    //每次循环gap都在减少,直到gap变成1
    gap = gap / 3 + 1;
    //gap /= 2;
    for (i = 0; i < n - gap; i++)
    {
      int end = i;
      int temp = a[end + gap];
      while (end >= 0)
      {
        if (temp < a[end])
        {
          a[end + gap] = a[end];
          end -= gap;
        }
        else
        {
          break;
        }
      }
      a[end + gap] = temp;
    }
  }
}

💦 选择排序

1、直接选择排序

🔑 核心思想 🔑

  每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

❗ 过程:❕

  1️⃣ 在元素集合 array[i] - array[n-1] 中选择关键码最大 (小) 的数据元素

  2️⃣ 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换

  3️⃣ 在剩余的 array[i] - array[n-2] (array[i+1]–array[n-1]) 集合中,重复上述步骤,直到集合剩余 1 个元素

❗ 直接选择排序的特性总结:❕

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

  2️⃣ 时间复杂度:O(N^2) - 最好 / 最坏

  3️⃣ 空间复杂度:O(1)

  4️⃣ 稳定性:不稳定

❗ 动图演示:❕

🧿 实现代码 :

void Swap(int* px, int* py)
{
  int temp = *px;
  *px = *py;
  *py = temp;
}
void SelectSort(int* a, int n)
{
  int i = 0;
  int begin = 0;
  while (begin < n)
  {
    int mini = begin;
    //选最小
    for (i = begin; i < n; i++)
    {
      if (a[i] < a[mini])
      {
        mini = i;
      }
    }
    //交换
    Swap(&a[begin], &a[mini]);
    //迭代
    begin++;
  }
}

🧿 实现 SelectSort 的优化代码 :

   遍厉一遍选出最小的和最大的,然后把最小的放在左边,最大的放在右边

void Swap(int* px, int* py)
{
  int temp = *px;
  *px = *py;
  *py = temp;
}
void SelectSortPro(int* a, int n)
{
  int i = 0;
  int begin = 0, end = n - 1;
  while (begin < end)
  {
    //选最大和最小
    int mini = begin, maxi = begin;
    for (i = begin; i <= end; i++)
    {
      if (a[i] > a[maxi])
      {
        maxi = i;
      }
      if (a[i] < a[mini])
      {
        mini = i;
      }
    }
    //交换
    Swap(&a[begin], &a[mini]);
    //当a数组里第1个元素是最大值时,此时经过上面的Swap,最大值的位置已经更改了,所以需要修正最大值的位置,让下一个Swap正确交换
    if (begin == maxi)
    {
      maxi = mini;
    }
    Swap(&a[end], &a[maxi]);
    //迭代
    ++begin;
    --end;
  }
}
2、堆排序

🔑 核心思想 🔑

  堆排序 (Heapsort) 是指利用堆积树 (堆) 这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

  关于堆排序详解请转到 ➡ 仅不到五万字轻松了解二叉树和堆

❗ 堆排序的特性总结:❕

  1️⃣ 堆排序使用堆来选数,效率就高了很多。

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

  3️⃣ 空间复杂度:O(1)

  4️⃣ 稳定性:不稳定

❗ 动图演示:❕

🧿 实现代码 :

void Swap(int* px, int* py)
{
  int temp = *px;
  *px = *py;
  *py = temp;
}
void AdjustDown(int* a, int n, int parent)
{
  int child = parent * 2 + 1;
  while (child < n)
  {
    if (a[child] < a[child + 1] && child + 1 < n)
    {  
      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)
{
  //建大堆
  int i = 0;
  for (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--;
  }
}

💦 交换排序

1、冒泡排序

🔑 核心思想 🔑

  所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

❗ 冒泡排序的特性总结:❕

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

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

  3️⃣ 空间复杂度:O(1)

  4️⃣ 稳定性:稳定

❗ 动图演示:❕

🧿 实现代码 :

void Swap(int* px, int* py)
{
  int temp = *px;
  *px = *py;
  *py = temp;
}
void BubbleSort(int* a, int n)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < n - 1; i++)
  {
    for (j = 0; j < n - 1 - i; j++)
    {
      if (a[j] > a[j + 1])
      {
        Swap(&a[j], &a[j + 1]);
      }
    }
  }
}

🧿 实现代码 BubbleSort 的优化版本 :

   当遍厉一遍后发现没有 Swap 时,那么说数组就是有序的

   时间复杂度:最坏 O(N2)

         最好 O(N)

void Swap(int* px, int* py)
{
  int temp = *px;
  *px = *py;
  *py = temp;
}
void BubbleSortPro(int* a, int n)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < n - 1; i++)
  {
    int flag = 1;
    for (j = 0; j < n - 1 - i; j++)
    {
      if (a[j] > a[j + 1])
      {
        flag = 0;
        Swap(&a[j], &a[j + 1]);
      }
    }
    //如果flag等于1说明此时数组是升序
    if (flag == 1)
      break;
  }
}
2、快速排序

🔑 核心思想 🔑

  快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

❗ 过程:❕

  1️⃣ 选出一个关键字 key,一般是头或者尾

  2️⃣ 经过一次单趟后,key 放到了正确的位置,key 左边的值比 key 小,key 右边的值比 key 大

  3️⃣ 再让 key 的左边区间有序、key 的右边区间有序

❗ 动图演示:❕

 一、首次单趟 (注意这三种方法首次单趟后不一定相同)

   💨 hoare 版本

 ❓ 如何保证相遇位置的值小于 key ❔

   💨 挖坑版本

   💨 前后指针法

 二、非首次单趟

🧿 实现代码 :首次 + 非首次 + 递归版本

void Swap(int* px, int* py)
{
  int temp = *px;
  *px = *py;
  *py = temp;
}
void PartSortHoare(int* a, int left, int right)
{
  int keyi = left;
  while(left < right)
  {
    //左边作key,右边先走找小
    while(a[right] >= a[keyi] && left < right)
    {
      right--;
    }
    //右边找到小,再找左边的大
    while(a[left] <= a[keyi] && left < right)
    {
      left++;
    }
    //交换小大
    Swap(&a[right], &a[left]);
  }
  //交换key
  Swap(&a[keyi], &a[right]);
  //返回分割大小的那个下标
  return left;
}
int PartSortHole(int* a, int left, int right)
{
  int key = a[left];
  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;//新的坑
  }
  //将key填最后一个坑
  a[hole] = key;
  return hole;
}
int PartSortPoint(int* a, int left, int right)
{
  int keyi = left;
  int prev = left;
  int cur = prev + 1;
  while (cur <= right)
  {
    //cur比keyi大时,prev不会++;且排除了自己交换自己
    if (a[cur] < a[keyi] && ++prev != cur)
    {
      Swap(&a[prev], &a[cur]);  
    }
    //两种情况cur都要++
    cur++;
  }
  //交换keyi
  Swap(&a[keyi], &a[prev]);
  return prev;
}
void QuickSort(int* a, int left, int right)
{
  //递归的结束条件
  if (left >= right)
  {
    return ;
  }
  //keyi拿到分割大小的下标 - [left, keyi - 1]; [keyi]; [keyi + 1, right]
  //int keyi = PartSortHoare(a, left, right);//版本1
  //int keyi = PartSortHole(a, left, right);//版本2
  int keyi = PartSortPoint(a, left, right);//版本3
  //递归左
  QuickSort(a, left, keyi - 1);
  //递归右
  QuickSort(a, keyi + 1, right);
}

❓ QuickSort 的时间复杂度 ❔

🧿 实现 QuickSort 的优化代码 —— 优化有序的情况

  三数取中选 key —— left、mid、right 中不是最大也不是最小的数

//三数取中
int GetMidIndex(int* a, int left, int right)
{
  //int mid = (left + right) / 2;
  int mid = left + (right - left) / 2;//防止溢出版本
  if (a[left] < a[mid])
  {
    if (a[mid] < a[right])
    {
      return mid;
    }
    else if (a[left] < a[right])
    {
      return right;
    }
    else
    {
      return left;
    }
  }
  else //a[left] > a[mid]
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if(a[left] < a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}
int PartSortHoarePro(int* a, int left, int right)
{
  int midi = GetMidIndex(a, left, right);
  Swap(&a[left], &a[midi]);
  int keyi = left;
  while (left < right)
  {
    while (a[right] >= a[keyi] && left < right)
    {
      right--;
    }
    while (a[left] <= a[keyi] && left < right)
    {  
      left++;
    }
    Swap(&a[right], &a[left]);
  }
  Swap(&a[keyi], &a[right]);
  return left;
}
int PartSortHolePro(int* a, int left, int right)
{
  int midi = GetMidIndex(a, left, right);
  Swap(&a[left], &a[midi]);
  int key = a[left];
  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;//新的坑
  }
  //将key填最后一个坑
  a[hole] = key;
  return hole;
}
int PartSortPointPro(int* a, int left, int right)
{
  int midi = GetMidIndex(a, left, right);
  Swap(&a[left], &a[midi]);
  int keyi = left;
  int prev = left;
  int cur = prev + 1;
  while (cur <= right)
  {
    //cur比keyi大时,prev不会++;且排除了自己交换自己
    if (a[cur] < a[keyi] && ++prev != cur)
    {
      Swap(&a[prev], &a[cur]);
    }
    //两种情况cur都要++
    cur++;
  }
  //交换keyi
  Swap(&a[keyi], &a[prev]);
  return prev;
}
void QuickSortPro(int* a, int left, int right)
{
  if (left >= right)
  {
    return;
  }
  //int keyi = PartSortHoarePro(a, left, right);//版本1
  //int keyi = PartSortHolePro(a, left, right);//版本2
  int keyi = PartSortPointPro(a, left, right);//版本3
  QuickSortPro(a, left, keyi - 1);
  QuickSortPro(a, keyi + 1, right);
}

❓ QuickSortHoarePro 的时间复杂度 ❔

  这里就不会出现最坏的情况 —— 有序,因为有了三数取中算法。


🧿 实现代码 :首次 + 非首次 + 非递归版本

  任何一个递归代码,要改成非递归

   1、循环

   2、栈 (数据结构) 模拟

  显然这里的快排不好直接改成循环,还要借助栈,所以这里复用了之前 C 实现的栈,详解请转 ➡ 爆肝两万字,我爷爷都看的懂的《栈和队列》,建议各位观众姥爷先收藏

🔑 核心思想 🔑

void QuickSortNonR(int* a, int left, int right)
{
  ST st;
  StackInit(&st);
  //先入第一组区间
  StackPush(&st, right);
  StackPush(&st, left);
  //栈不为空
  while (!StackEmpty(&st))
  {
    //取栈顶left,并Pop
    int begin = StackTop(&st);
    StackPop(&st);
    //再取栈顶right,并Pop
    int end = StackTop(&st);
    StackPop(&st);
    //这里就用前后指针版本进行单趟排
    int keyi = PartSortPointPro(a, begin, end);
    //再入区间 [left, keyi - 1]; [keyi]; [keyi + 1, end]
      //右区间 —— 只有1个值不会入
    if (keyi + 1 < end)
    {
      StackPush(&st, end);
      StackPush(&st, keyi + 1);
    }
      //左区间 —— 只有1个值不会入
    if (begin < keyi - 1)
    {
      StackPush(&st, keyi - 1);
      StackPush(&st, begin);
    }
  }
  StackDestory(&st);
}

❗ 快速排序的特性总结:❕

  1️⃣ 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

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

  3️⃣ 空间复杂度:O(logN)

  4️⃣ 稳定性:不稳定


相关文章
|
2月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
68 1
|
2月前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
160 4
|
11天前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
30 10
|
11天前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
42 10
|
11天前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
30 7
|
11天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
34 2
|
27天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
57 20
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
2月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
131 23

热门文章

最新文章