转载至:https://www.cnblogs.com/fnlingnzb-learner/p/9374732.html

一、算法概述

0.1 算法分类

十种常见排序算法可以分为两大类:

非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。

线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。

0.2算法复杂度

0.3 相关概念

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。

不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。

时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。

空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

二、快速排序

假设我们现在对“6  1  2 7  9  3  4  5 10  8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(不要被这个名词吓到了,就是一个用来参照的数,待会你就知道它用来做啥的了)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列:

3  1  2 5  4  6  9 7  10  8

在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6,递归对左右两个区间进行同样排序即可。想一想,你有办法可以做到这点吗?这就是快速排序所解决的问题。

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。它的平均时间复杂度为O(nlogn),最坏时间复杂度为O(n^2).

首先上图:

从图中我们可以看到:

left指针,right指针,base参照数。

其实思想是蛮简单的,就是通过第一遍的遍历(让left和right指针重合)来找到数组的切割点。

第一步:首先我们从数组的left位置取出该数(20)作为基准(base)参照物。(如果是选取随机的,则找到随机的哨兵之后,将它与第一个元素交换,开始普通的快排)

第二步:从数组的right位置向前找,一直找到比(base)小的数,如果找到,将此数赋给left位置(也就是将10赋给20),此时数组为:10,40,50,10,60, left和right指针分别为前后的10。

第三步:从数组的left位置向后找,一直找到比(base)大的数,如果找到,将此数赋给right的位置(也就是40赋给10),此时数组为:10,40,50,40,60, left和right指针分别为前后的40。

第四步:重复“第二,第三“步骤,直到left和right指针重合,最后将(base)放到40的位置, 此时数组值为: 10,20,50,40,60,至此完成一次排序。

第五步:此时20已经潜入到数组的内部,20的左侧一组数都比20小,20的右侧作为一组数都比20大, 以20为切入点对左右两边数按照"第一,第二,第三,第四"步骤进行,最终快排大功告成。

快速排序代码如下:

 1 //快速排序,随机选取哨兵放前面
2 void QuickSort(int* h, int left, int right)
3 {
4 if(h==NULL) return;
5 if(left>=right) return;
6
7 //防止有序队列导致快速排序效率降低
8 srand((unsigned)time(NULL));
9 int len=right-left;
10 int kindex=rand()%(len+1)+left;
11 Swap(h[left],h[kindex]);
12
13 int key=h[left],i=left,j=right;
14 while(i<j)
15 {
16 while(h[j]>=key && i<j) --j;
17 if(i<j) h[i]=h[j];
18 while(h[i]<key && i<j) ++i;
19 if(i<j) h[j]=h[i];
20 }
21
22 h[i]=key;
23
24 //QuickSort(&h[left],0,i-1);
25 //QuickSort(&h[j+1],0,right-j-1);
26
27 QuickSort(h,left,i-1);
28 QuickSort(h,j+1,right);
29 }

三、冒泡排序

1.原理

冒泡排序在扫描过程中两两比较相邻记录,如果反序则交换,最终,最大记录就被“沉到”了序列的最后一个位置,第二遍扫描将第二大记录“沉到”了倒数第二个位置,重复上述操作,直到n-1 遍扫描后,整个序列就排好序了。

2.算法实现

 1 //冒泡排序
2 void BubbleSort(int* h,size_t len){
3 if(h==NULL)return;
4 if(len<=1)return ;
5 //i是次数,j是具体下标
6 for(int i=0;i<len-1;++i)
7 for(int j=0;j<len-1;++j)
8 if(h[j]>h[j+1])
9 Swap(h[j],h[j+1])
10 return ;
11 }

四、选择排序

选择排序也是一种简单直观的排序算法,它的工作原理很容易理解;初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。

选择排序不稳:

举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法

算法实现:

 1 //选择排序
2 void SelectionSort(int* h,size_t len){
3 if(h==nullptr) return;
4 if(len<=1) return;
5
6 int minindex,i,j;
7 //i是次数,也即排好的个数;j是继续排
8 for(i=0;i<len-1;++i){
9 minindex=i;
10 for(j=i+1;i<len;++j){
11 if(h[j]<h[minindex]) minindex=j;
12 }
13 Swap(h[i],h[minindex]);
14 }
15 return;
16 }

五、插入排序

直接插入排序(straight insertion sort),有时也简称为插入排序(insertion sort),是减治法的一种典型应用。其基本思想如下:

  • 对于一个数组A[0,n]的排序问题,假设认为数组在A[0,n-1]排序的问题已经解决了。
  • 考虑A[n]的值,从右向左扫描有序数组A[0,n-1],直到第一个小于等于A[n]的元素,将A[n]插在这个元素的后面。

  很显然,基于增量法的思想在解决这个问题上拥有更高的效率。

直接插入排序对于最坏情况(严格递减的数组),需要比较和移位的次数为n(n-1)/2;对于最好的情况(严格递增的数组),需要比较的次数是n-1,需要移位的次数是0。当然,对于最好和最坏的研究其实没有太大的意义,因为实际情况下,一般不会出现如此极端的情况。然而,直接插入排序对于基本有序的数组,会体现出良好的性能,这一特性,也给了它进一步优化的可能性。(希尔排序)。直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1),同时也是稳定排序。

下面用一个具体的场景,直观地体会一下直接插入排序的过程:

场景:

现有一个无序数组,共7个数:89 45 54 29 90 34 68。

使用直接插入排序法,对这个数组进行升序排序。

89 45 54 29 90 34 68

45 89 54 29 90 34 68

45 54 89 29 90 34 68

29 45 54 89 90 34 68

29 45 54 89 90 34 68

29 34 45 54 89 90 68

29 34 45 54 68 89 90

算法实现:

//插入排序
void InsertSort(int* h,size_t len){
if(h==nullptr)return;
if(len<=1)return; int i,j;
//i是次数,也即排好的个数;j是继续排
for(i=1;i<len;++i)
for(j=i;j>0;--j)
if(h[j]<h[j-1])Swap(h[j],h[j-1]);
else break;
return ; }

六、归并排序

基本思想

  归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

分而治之

可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

合并相邻有序子序列

  再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

算法实现:

 1 //合并两个序列
2 void mergeArray(int arr[],int first,int mid,int last,int temp[]){
3 int i=first;
4 int j=mid+1;
5 int m=mid;
6 int n=last;
7 int k=0;
8 while(i<=m && j<=n){
9 if(arr[i]<=arr[j])
10 temp[k++]=arr[i++];
11 else
12 temp[k++]=arr[j++];
13 }
14 while(i<=m)
15 temp[k++]=arr[i++];
16 while(j<=n)
17 temp[k++]=arr[j++];
18 for(i=0;i<k;i++)
19 arr[first+i]=temp[i];
20 }
21
22 void mySort(int arr[],int first,int last,int temp[]){
23 if(first<last){
24 int mid=(first+last)/2;
25 mySort(arr,first,mid,temp);
26 mySort(arr,mid+1,last,temp);
27 mergeArray(arr,first,mid,last,temp);
28 }
29 }
30 bool mergeSort(int arr[],int len){
31 int *p=new int[len];
32 if(nullptr==p)
33 return false;
34 mySort(arr,0,len-1,p);
35 delete[] p;
36 return true;
37 }

七、希尔排序

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。本文会以图解的方式详细介绍希尔排序的基本思想及其代码实现。

基本思想

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

简单插入排序很循规蹈矩,不管数组分布是怎么样的,依然一步一步的对元素进行比较,移动,插入,比如[5,4,3,2,1,0]这种倒序序列,数组末端的0要回到首位置很是费劲,比较和移动元素均需n-1次。而希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。

  我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。

算法实现:

 1 //希尔排序
2 void ShellSort(int* h,size_t len){
3 if(h==nullptr)return;
4 if(len<=1)return;
5
6 for(int div=len/2;div>=1;div/=2)
7 for(int k=0;k<div;++k)
8 for(int i=div+k;i<len;i+=div)
9 for(int j=i;j>k;j-=div)
10 if(h[j]<h[j-div])Swap(h[j],h[j-div]);
11 else break;
12 return;
13 }

八、堆排序

堆排序实际上是利用堆的性质来进行排序的,要知道堆排序的原理我们首先一定要知道什么是堆。 
堆的定义: 
堆实际上是一棵完全二叉树。 
堆满足两个性质: 
1、堆的每一个父节点都大于(或小于)其子节点; 
2、堆的每个左子树和右子树也是一个堆。 
堆的分类: 
堆分为两类: 
1、最大堆(大顶堆):堆的每个父节点都大于其孩子节点; 
2、最小堆(小顶堆):堆的每个父节点都小于其孩子节点; 
 
堆的存储: 
一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如下图所示: 
 
堆排序: 
由上面的介绍我们可以看出堆的第一个元素要么是最大值(大顶堆),要么是最小值(小顶堆),这样在排序的时候(假设共n个节点),直接将第一个元素和最后一个元素进行交换,然后从第一个元素开始进行向下调整至第n-1个元素。所以,如果需要升序,就建一个大堆,需要降序,就建一个小堆。 
堆排序的步骤分为三步: 
1、建堆(升序建大堆,降序建小堆); 
2、交换数据; 
3、向下调整。 
假设我们现在要对数组arr[]={8,5,0,3,7,1,2}进行排序(降序): 
首先要先建小堆: 

堆建好了下来就要开始排序了: 

现在这个数组就已经是有序的了。

算法实现

 1 //调整堆
2 void adjust_heap(int* a,int node,int size){
3 int left=2*node+1;
4 int right=2*node+2;
5 int max=node;
6 if(left<size && a[left]>a[max]){
7 max=left;
8 }
9 if(right<size && a[right]>a[max]){
10 max=right;
11 }
12 if(max!=node){
13 swap(a[max],a[node]);
14 adjust_heap(a,max,size);
15 }
16 }
17 //堆排序
18 void heap_sort(int* a,int len){
19 for(int i=len/2;i>=0;--i)
20 adjust_heap(a,i,len);
21 for(int i=len-1;i>=0;i--){
22 swap(a[0],a[i]); //将当前最大的放置到数组末尾
23 adjust_heap(a,0,i); //将未完成排序的部分继续进行堆排序
24 }
25 }

九、基数排序

基数排序与本系列前面讲解的七种排序方法都不同,它不需要比较关键字的大小

它是根据关键字中各位的值,通过对排序的N个元素进行若干趟“分配”与“收集”来实现排序的。

1.LSD(低位到高位的排序)

不妨通过一个具体的实例来展示一下,基数排序是如何进行的。

设有一个初始序列为: R {50, 123, 543, 187, 49, 30, 0, 2, 11, 100}。

我们知道,任何一个阿拉伯数,它的各个位数上的基数都是以0~9来表示的。

所以我们不妨把0~9视为10个桶。

我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是0,将这个数存入编号为0的桶中。

分类后,我们在从各个桶中,将这些数按照从编号0到编号9的顺序依次将所有数取出来。

这时,得到的序列就是个位数上呈递增趋势的序列。

按照个位数排序: {50, 30, 0, 100, 11, 2, 123, 543, 187, 49}。

接下来,可以对十位数、百位数也按照这种方法进行排序,最后就能得到排序完成的序列。

 1 int maxbit(int data[],int n){
2 int d=1;//保存最大的位数
3 int p=10;
4 for(int i=0;i<n;++i){
5 while(data[i]>=p){
6 p*=10;
7 ++d;
8 }
9 }
10 return d;
11 }
12
13 void radixsort(int data[],int n){//基数排序
14 int d=maxbit(data,n);
15 int tmp[n];
16 int count[10];//计数器
17 int i,j,k;
18 int radix=1;
19 for(i=1;i<=d;++i){
20 for(j=0;j<10;++j)
21 count[j]=0;//每次分配前清空计数器
22 for(j=0;i<n;j++){
23 k=(data[j]/radix)%10;//统计每个桶中的记录数
24 count[k]++;
25 }
26 for(j=1;j<10;++j)
27 count[j]=count[j-1]+count[j];//将tmp中的位置依次分配给每个桶
28 for(j=n-1;j>=0;j--){
29 k=(data[j]/radix)%10;
30 tmp[count[k]-1]=data[j];
31 count[k]--;
32 }
33 for(j=0;j<n;++j)//将临时数组的内容复制到data中
34 data[j]=tmp[j];
35 radix=radix*10;
36 }
37 }

转:C++经典排序算法总结的更多相关文章

  1. 经典排序算法 – 插入排序Insertion sort

    经典排序算法 – 插入排序Insertion sort  插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕. 插入排序方法分直接插入排序和折半插入排序两种, ...

  2. 经典排序算法总结与实现 ---python

    原文:http://wuchong.me/blog/2014/02/09/algorithm-sort-summary/ 经典排序算法在面试中占有很大的比重,也是基础,为了未雨绸缪,在寒假里整理并用P ...

  3. 经典排序算法及python实现

    今天我们来谈谈几种经典排序算法,然后用python来实现,最后通过数据来比较几个算法时间 选择排序 选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理是每一次从待排序的数据 ...

  4. 经典排序算法 - 基数排序Radix sort

    经典排序算法 - 基数排序Radix sort 原理类似桶排序,这里总是须要10个桶,多次使用 首先以个位数的值进行装桶,即个位数为1则放入1号桶,为9则放入9号桶,临时忽视十位数 比如 待排序数组[ ...

  5. 经典排序算法 - 高速排序Quick sort

    经典排序算法 - 高速排序Quick sort 原理,通过一趟扫描将要排序的数据切割成独立的两部分,当中一部分的全部数据都比另外一部分的全部数据都要小,然后再按此方法对这两部分数据分别进行高速排序,整 ...

  6. 经典排序算法 - 归并排序Merge sort

    经典排序算法 - 归并排序Merge sort 原理,把原始数组分成若干子数组,对每个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到所有合并完,形成有序的数组 举例 无序数组[6 2 ...

  7. C# 经典排序算法大全

    C# 经典排序算法大全 选择排序 using System; using System.Collections.Generic; using System.Linq; using System.Tex ...

  8. Jerry 2017年的五一小长假:8种经典排序算法的ABAP实现

    2017年4月29日~5月1日,国际劳动节, 三天的小长假. 在国内,小长假往往是这样的: 然而我当时在戏称为"德村"(德国农村)的Walldorf出差并且住在Wiesloch, ...

  9. 【最全】经典排序算法(C语言)

    算法复杂度比较: 算法分类 一.直接插入排序 一个插入排序是另一种简单排序,它的思路是:每次从未排好的序列中选出第一个元素插入到已排好的序列中. 它的算法步骤可以大致归纳如下: 从未排好的序列中拿出首 ...

  10. 【刷题】【LeetCode】000-十大经典排序算法

    [刷题][LeetCode]总 用动画的形式呈现解LeetCode题目的思路 参考链接 000-十大经典排序算法

随机推荐

  1. 华为eNSP环境,WLAN简介与组网,AP+AC详细配置

    WLAN简单组网一.概述    无线局域网(Wireless Local Area Networks: WLAN)利用无线技术在空中传输数据.话音和视频信号.作为传统布线网络的一种替代方案或延伸,无线 ...

  2. Ocelot和IdentityServer4初体验

    Ocelot是一个用.NET Core实现的开源API网关技术.IdentityServer4是一个基于OpenID Connect和OAuth2.0的针对ASP.NET Core的框架,以中间件的形 ...

  3. JavaWeb后端

    JavaWeb后端 我们学习JavaWeb的最终目的是为了搭建一个网站,并且让用户能访问我们的网站并在我们的网站上做一些事情. 计算机网络基础 在计算机网络(谢希仁 第七版 第264页)中,是这样描述 ...

  4. Java基础--环境变量配置

    安装JDK配置编程或运行环境(必要) ①下载JDK 在下载页面中你需要选择接受许可,并根据自己的系统选择对应的版本,本文以 Window 64位系统为例: 根据安装提示一步一步安装完成. ②配置环境变 ...

  5. maven实现compile时将资源目录输出到target中

    由于现在项目采用的是 jboot 框架,升级最新版本2.1.5后,需要将原来的webroot资源改到 src/main/webapp目录下, 发现转移后,通过框架的app.java入口main函数无法 ...

  6. Python:pandas(三)——DataFrame

    官方文档:pandas之DataFrame 1.构造函数 用法 pandas.DataFrame( data=None, index=None, columns=None, dtype=None, ) ...

  7. Spark中的Wordcount

    目录 通过scala语言基于local编写spark的Wordcount 基于yarn去调度WordCount 通过scala语言基于local编写spark的Wordcount import org ...

  8. cobbler check执行报错

    httpd does not appear to be running and proxying cobbler, or SELinux is in the way. 当执行cobbler check ...

  9. 二进制部署1.23.4版本k8s集群-5-部署Master节点服务

    1.安装Docker 在21.22.200三台机器上安装Docker.安装命令: 在21.22.200三台主机上部署Docker. ~]# curl -fsSL https://get.docker. ...

  10. go1.18泛型的简单尝试

    今天golang终于发布了1.18版本,这个版本最大的一个改变就是加入了泛型.虽然没有在beta版本的时候尝试泛型,但是由于在其他语言的泛型经验,入手泛型不是件难事~ 官方示例 Tutorial: G ...