博主在学习过程中深感基础的重要,经典排序算法是数据结构与算法学习过程中重要的一环,这里对笔试面试最常涉及到的7种排序算法(包括插入排序、希尔排序、选择排序、冒泡排序、快速排序、堆排序、归并排序)进行了详解。每一种算法都有基本介绍、算法原理分析、算法代码。

插入排序

1)算法简介

插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

2)算法描述和分析

具体算法描述如下:

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

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

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

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

5. 将新元素插入到该位置后

6. 重复步骤2~5

如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数减去(n-1)次。平均来说插入排序算法复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

3)算法代码

void InsertSort(int a[], int n)
{//循环变量int i,j;//中间变量int temp;for (i=1; i<n; i++){temp=a[i];//从后向前循环,将a[0]~a[i-1]中大于temp的值后移for (j=i-1; j>=0&&a[j]>temp; j--)a[j+1]=a[j];//将temp放入合适位置a[j+1]=temp;}
}

希尔排序

1)算法简介

希尔排序,也称缩小增量排序算法,名称源于它的发明者Donald Shell,是插入排序的一种高速而稳定的改进版本。

2)算法描述

1、先取一个增量把元素分割成若干个子序列,对各子序列分别进行直接插入排序。

2、依次缩减增量再进行排序。

3、直至所取的增量足够小时,再进行一次直接插入排序。

希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n^2),而Hibbard增量的希尔排序的时间复杂度为O(N^(3/2)),但是现今仍然没有人能找出希尔排序的精确下界。

3)算法代码

void ShellSort(int a[], int n)
{//循环变量int i,j;//增量int increment;//中间变量int temp;for (increment=n/2; increment>0; increment/=2){//在增量分割的子序列中进行插入排序for (i=increment; i<n; i++){temp=a[i];for (j=i-increment; j>=0&&a[j]>temp; j-=increment){//右移a[j+increment]=a[j];}a[j+increment]=temp;}}
}

冒泡排序

1)算法简介

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

2)算法描述

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。

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

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

4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

冒泡排序是与插入排序拥有相等的执行时间,但是两种法在需要的交换次数却很大地不同。在最坏的情况,冒泡排序需要O(n^2)次交换,而插入排序只要最多O(n)交换。冒泡排序的实现(类似下面)通常会对已经排序好的数列拙劣地执行(O(n^2)),而插入排序在这个例子只需要O(n)个运算。因此很多现代的算法教科书避免使用冒泡排序,而用插入排序取代之。冒泡排序如果能在内部循环第一次执行时,使用一个旗标来表示有无需要交换的可能,也有可能把最好的复杂度降低到O(n)。在这个情况,在已经排序好的数列就无交换的需要。若在每次走访数列时,把走访顺序和比较大小反过来,也可以稍微地改进效率。有时候称为往返排序,因为算法会从数列的一端到另一端之间穿梭往返。

最差时间复杂度     O(n^2)

最优时间复杂度     O(n)

平均时间复杂度     O(n^2)

最差空间复杂度     总共O(n),需要辅助空间O(1)

3)算法代码

void BubbleSort(int a[], int n)
{int i,j;//中间变量int temp;for (i=0; i<n; i++){for (j=0; j<n-1-i; j++){//交换if (a[j+1]<a[j]){temp=a[j];a[j]=a[j+1];a[j+1]=temp;}}}
}

选择排序

1)算法简介

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。选择排序是不稳定的排序方法。

2)算法描述和分析

1、初始状态:无序区为R[1..n],有序区为空,令i=0。

2、在无序区R[i..n-1]中选出关键字最小的记录 R[k],将它与无序区的第1个记录R[i]交换,交换之后R[0…i]就形成了一个有序区。

3、i++并重复第二步,直到i==n-1,数组有序化了。

3)算法代码

void SelectSort(int a[], int n)
{//循环变量int i,j;//最小元素的下标int mindex;//中间变量int temp;for (i=0; i<n; i++){mindex=i;for (j=i+1; j<n; j++){if (a[j]<a[mindex]){mindex=j;}}temp=a[i];a[i]=a[mindex];a[mindex]=temp;}
}

归并排序

1)算法简介

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

2)算法描述和分析

归并排序具体算法描述如下(递归版本):

1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。

2、Conquer: 对这两个子序列分别采用归并排序。

3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。

归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。

3)算法代码

//将两个有序数列a[first~mid]和a[mid+1~last]合并
void merge(int a[], int pTemp[], int first, int mid, int last)
{int i=first,j=mid+1,k=first;while(i<=mid&&j<=last){if (a[i]<a[j]){pTemp[k++]=a[i++];}elsepTemp[k++]=a[j++];}while(i<=mid)pTemp[k++]=a[i++];while(j<=last)pTemp[k++]=a[j++];for (i=first; i<=last; i++)a[i]=pTemp[i];
}//归并排序
void MSort(int a[], int pTemp[], int left, int right)
{int Center;if (left<right){Center=(left+right)/2;MSort(a,pTemp,left,Center);//左边有序MSort(a,pTemp,Center+1,right);//右边有序merge(a,pTemp,left,Center, right);//将两个有序数列合并}
}bool MergeSort(int a[], int n)
{int *pTempArray;pTempArray=(int *)malloc(n*sizeof(int));if (pTempArray==NULL)return false;MSort(a,pTempArray,0,n-1);free(pTempArray);return true;
}

堆排序

1)算法简介

堆排序(HeapSort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

   堆排序方法对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上。

  堆排序在最坏的情况下,其时间复杂度也为O(nlogn)。相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需一个记录大小的供交换用的辅助存储空间。

2)堆的定义

n个元素的序列{k1,k2,…,kn}当且仅当满足下列关系之一时,称之为堆。

  情形1:ki <= k2i 且ki <= k2i+1 (最小化堆或小顶堆)

  情形2:ki >= k2i 且ki >= k2i+1 (最大化堆或大顶堆)

其中i=1,2,…,n/2向下取整;

若将和此序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。

  由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。

若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。

3)堆的存储

  一般用数组来表示堆,若根结点存在序号0处, i结点的父结点下标就为(i-1)/2。i结点的左右子结点下标分别为2*i+1和2*i+2。(注:如果根结点是从1开始,则左右孩子结点分别是2i和2i+1。)

4)堆排序的实现

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

  1.如何由一个无序序列建成一个堆?

2.如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?

  先考虑第二个问题,一般在输出堆顶元素之后,视为将这个元素排除,然后用表中最后一个元素填补它的位置,自上向下进行调整:首先将堆顶元素和它的左右子树的根结点进行比较,把最小的元素交换到堆顶;然后顺着被破坏的路径一路调整下去,直至叶子结点,就得到新的堆。

  我们称这个自堆顶至叶子的调整过程为“筛选”。从无序序列建立堆的过程就是一个反复“筛选”的过程。

5)构造初始堆

  初始化堆的时候是对所有的非叶子结点进行筛选。最后一个非终端元素的下标是[n/2]向下取整,所以筛选只需要从第[n/2]向下取整个元素开始,从后往前进行调整。

  比如,给定一个数组,首先根据该数组元素构造一个完全二叉树。然后从最后一个非叶子结点开始,每次都是从父结点、左孩子、右孩子中进行比较交换,交换可能会引起孩子结点不满足堆的性质,所以每次交换之后需要重新对被交换的孩子结点进行调整。

6)进行堆排序

  堆排序是一种选择排序。建立的初始堆为初始的无序区。

  排序开始,首先输出堆顶元素(因为它是最值),将堆顶元素和最后一个元素交换,这样,第n个位置(即最后一个位置)作为有序区,前n-1个位置仍是无序区,对无序区进行调整,得到堆之后,再交换堆顶和最后一个元素,这样有序区长度变为2。

不断进行此操作,将剩下的元素重新调整为堆,然后输出堆顶元素到有序区。每次交换都导致无序区-1,有序区+1。不断重复此过程直到有序区长度增长为n-1,排序完成。

 由排序过程可见,若想得到升序,则建立大顶堆,若想得到降序,则建立小顶堆。

7)算法代码

// 输入数组A,堆的长度len,以及需要调整的节点i,调堆
void HeapAdjust(int A[], int len, int i)
{int left=2*i+1;//结点i的左孩子int right=2*i+2;//结点i的右孩子int largest=i;int temp;while(left<len||right<len){if (left<len&&A[left]>A[largest]){largest=left;}if (right<len&&A[right]>A[largest]){largest=right;}//如果最大值不是父结点if (i!=largest){//交换父结点和拥有最大值的子结点temp=A[i];A[i]=A[largest];A[largest]=temp;//新的父结点,以备迭代调堆i=largest;//新的子结点left=2*i+1;right=2*i+2;}elsebreak;}
}//建堆
void BuildHeap(int A[], int len)
{//最后一个非叶子结点int begin=len/2-1;for (int i=begin; i>=0; i--){HeapAdjust(A,len,i);}
}//堆排序
void HeapSort(int A[], int n)
{int temp;//建堆BuildHeap(A,n);while(n>1){//交换堆的第一个元素和最后一个元素temp=A[n-1];A[n-1]=A[0];A[0]=temp;n--;//调堆HeapAdjust(A,n,0);}
}

快速排序

快速排序是各种笔试面试最爱考的排序算法之一,且排序思想在很多算法题里面被广泛使用。是需要重点掌握的排序算法。

1)算法简介

快速排序是由东尼·霍尔所发展的一种排序算法。其基本思想是,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

2)算法描述和分析

快速排序使用分治法来把一个串(list)分为两个子串行(sub-lists)。

步骤为:

1、从数列中挑出一个元素,称为 "基准"(pivot),

2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

在平均状况下,排序n个项目要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

最差时间复杂度    O(n^2)

最优时间复杂度    O(n log n)

平均时间复杂度    O(n log n)

最差空间复杂度   根据实现的方式不同而不同

我们选取数组的第一个元素作为主元,每一轮都是和第一个元素比较大小,通过交换,分成大于和小于它的前后两部分,再递归处理。

3)算法代码

void QuickSort(int a[], int left, int right)
{int i,j,v;if (left<right){i=left;j=right;//以本次最左边的值为标准进行划分v=a[i];do {//从右向左找第一个小于标准位置jwhile(a[j]>v&&i<j)j--;if (i<j){a[i]=a[j];i++;//将第j个元素置于左端,并重置i}//从左向右找第一个大于标准位置iwhile(a[i]<v&&i<j)i++;if (i<j){a[j]=a[i];j--;//将第i个元素置于右端,并重置j}} while (i!=j);//将标准值放入它的最终位置a[i]=v;//对标准值左半部分递归QuickSort(a,left,i-1);//对标准值右半部分递归QuickSort(a,i+1,right);}
}

总结

总结一下各种排序算法如下:

名称

时间复杂度

额外空间

稳定性

考点

插入排序

平均O(n^2)

最优O(n)

最差O(n^2)

O(1)

稳定

选择填空

各种时间复杂度

移动元素个数

希尔排序

最差O(n log n)

最优 O(n)

O(n)

不稳定

时间复杂度

比较次数

选择排序

O(n^2)

O(1)

不稳定

同插入排序

冒泡排序

O(n^2)

最优O(n)

最差O(n^2)

O(1)

稳定

时间复杂度

比较次数

单轮冒泡

快速排序

O(n log n)

O(1)

不稳定

时间复杂度

快排partition算法

堆排序

O(n log n)

O(n)

不稳定

时间复杂度

堆调整,建堆,堆排序,Top K问题

归并排序

平均O(nlogn)

最差O(nlogn)

最优O(n)

O(n)

******************

作者:hao_09

时间:2015/8/4

文章地址:http://blog.csdn.net/lsh_2013/article/details/47280135

******************

常见经典排序算法学习总结(插入、shell、冒泡、选择、归并、快排等)相关推荐

  1. 经典排序算法(一) —— Selection Sort 选择排序

    经典排序算法(一) -- Selection Sort 选择排序 文章目录 经典排序算法(一) -- Selection Sort 选择排序 简介 排序过程 实现 复杂度 简介 选择排序是一种简单直观 ...

  2. 经典排序算法学习笔记二——快速排序

    快速排序 数据结构 不定 最差时间复杂度 O(n^2) 最优时间复杂度 O (n*log n) 平均时间复杂度 O (n*log n) 最差空间复杂度 根据实现的方式不同而不同 https://zh. ...

  3. 排序算法学习整理一(冒泡)

    排序算法顾名思义,给元素排序,无论是从小到大也好还是从大到小也罢,都归属于排序,作为一个刚入坑但又在能力上有所欠缺的萌新来说排序算法是简直难以逾越的天坑,我曾经见过一个朋友冒泡排序敲了一周QAQ,勉强 ...

  4. 经典排序算法学习笔记七——堆排序

    堆排序 数据结构 数组 最差时间复杂度 O(n*log n) 最优时间复杂度 O(n*log n) 平均时间复杂度 O(n*log n) 最差空间复杂度 О(n) total, O(1) auxili ...

  5. 归并排序(常见经典排序算法)

    步骤:1.将序列中待排序数字分为若干组,每个数字分为一组            2.将若干个组两两合并,保证合并后的组是有序的            3.重复第二步操作直到只剩下一组,排序完成  基本 ...

  6. datatable的数据进行组内排序_排序算法学习分享(四)希尔排序

    排序,也称为排序算法,可以说是我们学习算法的过程中遇到的第一个门槛,也是实际应用中使用得较为频繁的算法,我将自己对所学的排序算法进行一个归纳总结与分享,如有错误,欢迎指正! 排序算法学习分享(一)选择 ...

  7. 冒泡和快速排序的时间复杂度_排序算法学习分享(二)交换排序---冒泡排序与快速排序...

    排序,也称为排序算法,可以说是我们学习算法的过程中遇到的第一个门槛,也是实际应用中使用得较为频繁的算法,我将自己对所学的排序算法进行一个归纳总结与分享,如有错误,欢迎指正! 排序算法学习分享(一)选择 ...

  8. 经典排序算法 - 希尔排序Shell sort

    经典排序算法 - 希尔排序Shell sort 希尔排序Shell Sort是基于插入排序的一种改进,同样分成两部分, 第一部分,希尔排序介绍 第二部分,如何选取关键字,选取关键字是希尔排序的关键 第 ...

  9. 常用排序算法的c++实现(冒泡,选择,插入,堆,shell,快速,归并 )与sort()对比 - coder_xia的专栏 - 博客频道 - CSDN.NET...

    常用排序算法的c++实现(冒泡,选择,插入,堆,shell,快速,归并 )与sort()对比 - coder_xia的专栏 - 博客频道 - CSDN.NET 常用排序算法的c++实现(冒泡,选择,插 ...

最新文章

  1. JFinal整合Shiro(二)
  2. python os.environ gpu_Tensorflow下如何实现多GPU数据并行训练?
  3. keil 查看 stm32 io波形_这样学习STM32单片机,从菜鸟到牛人很简单!
  4. 玩家游戏账号被封十年,解封后一进游戏傻眼了:比当年还火?
  5. 硬件基础知识(12)---为什么我的处理器这么耗电?原因不只是一个小小的限流电阻.
  6. dubbo日志关闭_不可忽视的Dubbo线程池避坑指南
  7. 当一个人把一个行业说得特别容易赚钱的时候
  8. TensorFlow:模型的保存与恢复(Saver)
  9. 开发技巧汇总|对于imag.js你不知道的事
  10. Unity3D基础12:碰撞体
  11. ubuntu 搭建 php 环境
  12. 一次接口超时排查,花费了我两个星期。。
  13. torch.nn.NLLLoss()
  14. 旁路电容0.1uF,是怎么来的?
  15. 手机12306买卧铺下铺技巧_12306网购火车票怎么选上中下铺(详细代码及图解)...
  16. 禁止电信远程控制服务器自动下发修改光猫配置信息
  17. DCM: 诊断通信管理 (Diagnostic Communiction Manager)
  18. Redis解决商品秒杀与超卖
  19. CEOI 2020, Day 2 A,B题解 CF1403A CF1403B
  20. 2022年第三次面试,含泪整理万字面试题。

热门文章

  1. matlab strel
  2. Python实现Kubernetes Operator
  3. 日志收集系统Elasticsearch,Fluentd , Kibana
  4. 安卓自定义相机拍照功能全解(不调用系统相机)
  5. 在LaTex中插入代码块
  6. 运行Lattice Diamond时报错 License checkout failed的解决方法
  7. HTML5之本地存储localstorage
  8. centos7安装rabbitmq 总结
  9. 图像处理PILLOW的使用
  10. php和java的一些比较