【数据结构和算法】各种基本的排序算法原理

简介: 【数据结构和算法】各种基本的排序算法原理

排序的概述


  • 排序方法的分类

image.png


  • 按存储介质分类:(内部排序和外部排序)

image.png


  • 按比较器个数分类:(串行排序和并行排序)

image.png


  • 按主要操作分类

image.png


  • 按辅助空间分类

image.png


  • 按稳定性分类

image.png


  1. 稳定排序的例子(相同的数值排序后相对位置没有发生改变)

image.png

  1. 非稳定排序的例子(相同的数值排序后相对位置发生了改变)

image.png

  • 记录序列顺序表的存储结构
#define MAXSIZE 20  //设记录不超过20个
typedef int KeyType;  //设关键字为整形量(int 型)
Typedef struct{   //定义每个记录(数据元素)的结构
  KeyType key;  //关键字
  InfoType otherinfo; //其他数据项
}RedType;
Typedef struct{   //定义顺序表的结构
  RedType r[MAXSIZE + 1]; //存储顺序表的向量,其中r[0]一般作为哨兵,可以减少比较的次数
  int length;   //顺序表的长度
}SqList;


直接插入排序


  • 实现思路

image.png

(缺点是每一次循环都要进行两次的比较,可以设置一个哨兵减少比较的次数,在0号的位置插入一个哨兵)


  • 增加一个哨兵的实现思路

image.png



  • 代码实现
//增加哨兵代码
void InsertSort(SqList &L){
  int i,j;
  for(i = 2; i <= L.length; i++){
  if(L.r[i].key < L.r[i - 1].key){
    L.r[0] = L.r[i];
    for(j = i-1; L.r[0].key < L.r[j].key; j--){
    L.r[j+1] = L.r[j];
    }
    L.r[j+1] = L.r[0];
  }
  }
}
//无哨兵代码(增加了时间复杂度)
void InsertSort(SqList &L){
  int i,j,temp;
  for(j = 1; j < L.length; j++){  //从第二个元素开始不断的整理这个数组
  temp = L.r[j];
  for(i = j -1; i >=0 && L.r[i] < temp; i--){ //如果要插入的值比此时的数据大,不断后移
    L.r[i + 1] = L.r[i];
  }
  L.r[i + 1] = temp;  //直到某一个数据比插入的值小,就插入在该数据后面
  }
}


  • 直接插入排序的性能分析

image.png

折半插入排序


  • 思路分析

(由于在插入的元素前面的元素已经是拍好戏的元素,所以对这些元素可以进行二分查找,一次查找排除了一半的元素所以查找得更快)

image.png

  • 代码实现
void BInsertSort(SqList &L){
  int i,j,low,high;
  for(i = 2; i <= L.length; ++i){ //依次插入第1~第n个元素
  L.r[0] = L.r[i];    //当前插入元素存到“哨兵”位置
  low = 1;
  high = i - 1;
  while(low <= high){   //二分法查找插入位置
    mid = (low + high)/2;
    if(L.r[0].key < L.r[mid].key) //如果比中间小。则查找前一半
    high = mid - 1;
    else      //如果比中间大。则查找后一半
    low = mid + 1;
  } 
  //循环结束,此时high+1则为插入的位置,因为此时low = mid。
  //而high必须为low-1才能结束循环,也可以说mid位置就是插入元素的为位置
  for(j = i - 1; j >= high + 1; j--){ //移动元素
    L.r[j+1] = L.r[j];
  }
  L.r[high + 1] = L.r[0];  //插入到正取的位置
  //  L.r[mid] = L.r[0];
  }
}


  • 折半插入排序的性能分析(平均性能优于直接插入排序)

image.png

image.png


希尔排序


  • 基本思路

首先选择一个增量序列,这个增量序列一次比一次小,按照这个增量实现间隔的插入排序,比如设定一开始的增量为5,则对序列进行5增量的间隔插入排序。增量序列是递减的,且最后的一步,必须增量为1,也就是进行一次直接的插入排序。(最后的一次序列已经基本有序了,所以只需要很少的移动就可实现)

image.png

  • 希尔排序实现的例子

image.png

  • 希尔排序的特点

image.png

  • 大致的代码实现
void Shellsort(SqList &L,int dk){
  //对顺序表L进行一趟增量为dk的Shell排序,dk为步长因子
  for(i = dk + 1; i <= L.elngth; i++){
  if(r[i].key < r[i-dk].key){
    r[0] = r[i];
    for(j = i - dk; j>0 && (r[0].key < r[j].key); j = j - dk){
    r[j + dk] = r[j];
    }
    r[j + dk] = r[0];
  }
  }
}
void SellSort(SqList &L,int dlat[],int t){
  //dk的值依次存储在dlat[t]中,按增量序列dlta[0..t-1]对顺序表L作希尔排序
  for(k = 0; k < t; k++)
  Selllnsert(L,dlta[k]);  //一趟增量为dlta[k]的插入排序
}
//与插入排序进行比较,就是将+1,-1的值换成增量dk
void InsertSort(SqList &L){
  int i,j;
  for(i = 2; i <= L.length; i++){
  if(L.r[i].key < L.r[i - 1].key){
    L.r[0] = L.r[i];
    for(j = i-1; L.r[0].key < L.r[j].key; j--){
    L.r[j+1] = L.r[j];
    }
    L.r[j+1] = L.r[0];
  }
  }
}


  • 希尔排序的时间复杂度

(与增量序列的取值有关)

image.png

  • 希尔排序是不稳定,例子如下:

image.png

  • 希尔排序算法分析

image.png


冒泡排序


(数值大的不断的靠后,数值小的不断地冒前,每一趟增加一个有序的元素,而此时序列靠后的元素就是有序的)


  • 冒泡排序的示例

image.png

image.png

(规律:每次需要比较的次数 = n个记录 - 第几趟的数值)


  • 冒泡排序的代码实现


//冒泡排序
void bubble_sort(SqList &L){    
  int i,j;
  for(i = 0; i < L.length-1; i++){  //比较 L.length-1轮
  for( j = 0; j < length - i - 1; i++){ //每轮比较 length - i - 1次
    if(L.r[j] < L.r[j+1]){    //两个数值交换
    L.r[j] = L.r[j+1] + L.r[j];
    L.r[j+1] = L.r[j] - L.r[j+1];
    L.r[j] = L.r[j] - L.r[j+1];
    }
  }
  }
}
//冒泡排序的改进
//如果后面的比较之中一直都没有进行数据的交换,则表示后面的数据已经是有序,已经冒泡排序已经无需进行
//也就是如果某一趟比较时不出现交换,说明已排好序
//实现,增加一个标志flag,如果一直没有进行交换flag==0,退出循环
void bubble_sort(SqList &L){
  int i,j,flag = 1;
  for(i = 0; i < L.length-1 && flag==1 ; i++){//比较 L.length-1轮
  flag = 0;   //每次循环重新置标志
  for( j = 0; j < length - i - 1; i++){ //每轮比较 length - i - 1次
    if(L.r[j] < L.r[j+1]){    //两个数值交换
    L.r[j] = L.r[j+1] + L.r[j];
    L.r[j+1] = L.r[j] - L.r[j+1];
    L.r[j] = L.r[j] - L.r[j+1];
    flag = 1; //发生交换,flag置为1,若本趟没有发生交换,flag保持0,退出循环
    }
  }
  }
}


  • 冒泡排序的算法分析

image.png


快速排序


  • 快速排序的基本思想(递归实现 )

image.png

  • 快速排序的例子

(只需要一个多余的位置)

  1. 将1号元素49搬到0号元素中存储,此时1号位置就多出来了一个空间。

image.png

  1. 从后面找出一个比此时的0号元素49小的元素,high不断的向前推进。49和49相等,不符合,27比49小,符合条件,所以将27搬到1号元素中。

image.png

  1. 此时后面空出来了一个元素,所以此时从前面的low指针向后移直到找到了一个比0号元素49大的元素,直到65,所以将65搬到后面,此时65所在的位置为空。

image.png

  1. 所以此时前面又空了,所以又轮到了high指针向前找一个比49小的数据搬到前面去,找到了13,将13班到前面空白的位置,而原本131的位置为空。

image.png

  1. 此时再从前面找一个大的搬过去,直到low指针和high指针相遇。

image.png

  1. 然后将0号元素搬回到此时指针相遇的地方。

image.png

  1. 同样的,对此时左右字表的元素又进行同样的方法,直到每个字表剩下一个元素。

image.png

ps:

1、字表的形成是采用从两头向中间交替式逼近法

2、趟中对个子表的操作都相识,可采用递归算法

  • 快速排序代码实现
void QSort(SqList &L,int low,int high){ //对顺序表L快速排序
  if(low < high){      //长度大于1
  pivotloc = Partition(L,low,high); //将顺序表一分为2,oivotloc为枢轴元素拍好序的位置
  QSort(L,low,pivotloc - 1);  //对低子表递归排序
  QSort(L,oivoloc + 1,high);  //对高子表递归排序
  }
}
int Partition(SqList &L,int low,int high){
  L.r[0] = L.r[low];  
  pivotkey = L.r[low].key;//每次设置从表中左边第一个元素开始
  while(low < high){
  while(low < high && L.r[high].key >= pivotkey)  
    --high;   //右边不断向后移直到找到了一个比0号数据小的元素
  L.r[low] = L.r[high];
  while(low < high && L.r[low],key <= pivotkey) 
    ++low;    //左边不断向前移直到找到了一个比0号数据大的元素
  L.r[high] = L.r[low];
  }
  L.r[low] = L.r[0];  //填补两个指针共同所指向的位置
  return low;    //此时low和high是指向相同的位置
}


  • 快速排序的时间复杂符

image.png

  • 快速排序的空间复杂度

image.png

  • 快速排序的稳定性

image.png

ps:如果对一个逆序的数组进行快速排序使其为正序的数组,快速排序每一次只能排好一个子表,此时快速排序退化成冒泡排序。所以快速排序不适用于原本或基本有序的记录序列进行排序。


选择排序


  • 选择排序的思路

image.png

(选择排序总共需要n-1趟就可以了)


  • 选择排序的例子

image.png

image.png

(每一趟找到一个最小值,只是这个最小值的范围不一样,第一趟从第一个位置开始,第n趟从第n个位置开始)


  • 选择排序的代码实现


void SelectSort(SqList &K){
  for(i = 1; i < L.length; ++i){
  for(j = i + 1; j <= L.length; j--){
    if(L.r[j].key < L.r[i].key) //交换两个值
    L.r[i].key = L.r[i].key + L.r[j].key;
    L.r[j].key = L.r[i].key - L.r[j].key;
    L.r[i].key = L.r[i].key - L.r[j].key;
  }
  }
}


  • 选择排序的稳定性

简单的选择排序是不稳定的排序,例子如下:

image.png


堆排序


  • 堆的定义(小根堆与大根堆)

image.png

  • 小根堆与大跟堆的例子

image.png

  • 堆排序的思路

image.png

实现堆排序需要解决掉两个问题:

  1. 如何有一个无序序列建成一个堆
  2. 如何在输出堆顶元素后,调整剩余元素为一个新的堆


  • 堆的调整(以小根堆为例)

image.png

堆排序的算法实现

void HeapAdjust(elem R[],int s,int m){
  /*已知R[s..m]中记录的关键字除了R[s]之外均满足堆的定义
  *本函数调整R[s]的关键字,使R[s..m]成为一个大根堆
  */
  rc = R[s];
  for(j = 2*s; j <= m; j *= 2){ //沿key较大的孩子结点向下筛选
  if(j < m && R[j] < R[j+1])
    ++j;  //j为key较大的记录的下标
  if(rc >= R[j])
    break;
  R[s] = R[j];
  s = j;
  }
  R[s] = rc;
}
void HeapSort(elem R[]){  //对R[1]到R[n]进行排序
  int i;
  for(i = n/2; i >= 1; i--)
  HeapAdjust(R,i,n);  //建初始堆
  for(i = n; i > 1; i--){ //进行n-1趟排序
  Swap(R[1],R[i]);  //根与最后一个元素交换
  HeapAdjust(R,1,i-1);//对R[1]到R[i-1]重新建堆
  }
}


  • 堆排序的算法分析

image.png

ps:

  1. 空间复杂度是O(1)
  2. 堆排序是非稳定的
  3. 堆排序的最好最坏的情况都一样,影响不太大


归并排序


  • 归并排序的思路

image.png

  • 归并排序的例子(二路归并排序)

image.png

  • 归并排序的算法分析

image.png


桶排序


  • 桶排序的基本思路

image.png

桶排序的例子

  1. 首先个位分派,个位是几就分到第几个箱子,收集之后个位就有序了。(从个位开始扔是以为需要排序的部分数据有只是个位的,没有百位和十位。)

image.png

  1. 第二次是对十位进行排序

image.png

  1. 第三次是对百位进行排序

image.png

由于这里需要排序的最高位只是百位,所以当百位回收回来时就已经分配好了。


各种排序算法的比较


  • 时间复杂度

image.png

image.png

  • 空间复杂度

image.png

  • 排序方法的稳定性能

image.png


参考链接:https://space.bilibili.com/40323036


目录
相关文章
|
16天前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
79 29
|
16天前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
72 25
|
16天前
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
58 23
|
1月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
51 2
|
1月前
|
机器学习/深度学习 算法 PyTorch
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
软演员-评论家算法(Soft Actor-Critic, SAC)是深度强化学习领域的重要进展,基于最大熵框架优化策略,在探索与利用之间实现动态平衡。SAC通过双Q网络设计和自适应温度参数,提升了训练稳定性和样本效率。本文详细解析了SAC的数学原理、网络架构及PyTorch实现,涵盖演员网络的动作采样与对数概率计算、评论家网络的Q值估计及其损失函数,并介绍了完整的SAC智能体实现流程。SAC在连续动作空间中表现出色,具有高样本效率和稳定的训练过程,适合实际应用场景。
214 7
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
|
2月前
|
算法 Java 数据库
理解CAS算法原理
CAS(Compare and Swap,比较并交换)是一种无锁算法,用于实现多线程环境下的原子操作。它通过比较内存中的值与预期值是否相同来决定是否进行更新。JDK 5引入了基于CAS的乐观锁机制,替代了传统的synchronized独占锁,提升了并发性能。然而,CAS存在ABA问题、循环时间长开销大和只能保证单个共享变量原子性等缺点。为解决这些问题,可以使用版本号机制、合并多个变量或引入pause指令优化CPU执行效率。CAS广泛应用于JDK的原子类中,如AtomicInteger.incrementAndGet(),利用底层Unsafe库实现高效的无锁自增操作。
理解CAS算法原理
|
2月前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
70 20
|
2月前
|
存储 人工智能 缓存
【AI系统】布局转换原理与算法
数据布局转换技术通过优化内存中数据的排布,提升程序执行效率,特别是对于缓存性能的影响显著。本文介绍了数据在内存中的排布方式,包括内存对齐、大小端存储等概念,并详细探讨了张量数据在内存中的排布,如行优先与列优先排布,以及在深度学习中常见的NCHW与NHWC两种数据布局方式。这些布局方式的选择直接影响到程序的性能,尤其是在GPU和CPU上的表现。此外,还讨论了连续与非连续张量的概念及其对性能的影响。
99 3
|
3天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
142 77

热门文章

最新文章