常用排序算法
来源:互联网 发布:淘宝上卖美女图片 编辑:程序博客网 时间:2024/06/07 19:43
写在前面
内部排序算法及其改进算法很多种,但常用的无非以下 7 个。
基本排序算法
待排序数量不大(< 10K),平均时间复杂度 O(n^2)
- 冒泡排序
- 直接选择排序
- 直接插入排序
- 希尔排序(介于基本排序和高效排序之间)
高效排序算法
待排序数量较大,平均时间复杂度 O(nlogn)
- 快速排序
- 堆排序
- 归并排序
冒泡排序
简介
冒泡排序
是入门级排序算法,因其实现和理解较为简单而被广泛使用,平均时间复杂度为O(n^2)
,由于在排序过程中两两比较,如果相同不作互换,所以是一种稳定
的排序算法。
思路
进行 n 次冒泡,n 为待排序元素个数。每次冒泡产生一个当前未排序元素中最大(小)的元素。
- 如第一次冒泡,从第一个元素开始,和第二个元素进行比较,如果大于第二个元素,则交换位置。 继续将第二个元素与第三个进行比较,如果大于第三个元素,则交换位置。类似的两两比较直到最后一个元素也被比较。由于每次都把相对大的元素向后推,最终最后一个元素就是最大的。
- 第 1 步操作完成一个元素的冒泡,而进行第二次冒泡时,只需进行
n - 1
次比较,因为最大的元素已经排好序了在最后一个。同理,进行第i
次冒泡时,只需进行n - i
次比较。由此,进行n
次冒泡,每次比较n-i
次即可完成冒泡排序。
代码
public class BubbleSort { public static void bubbleSort(int a[]){ int n = a.length; for(int i =0 ; i< n-1; i++) { //进行n次冒泡 for(int j = 0;j < n-i-1;j++) { //进行n-i次比较 if(a[j] > a[j+1]) {//交换位置 int tmp = a[j] ; a[j] = a[j+1] ; a[j+1] = tmp; } } } } public static void main(String[] args) { int[] arr = new int[]{2,7,3,0,5,4}; bubbleSort(arr); for(int i = 0;i < arr.length;i++){ System.out.print(arr[i] + ","); } }}
直接选择排序
直接选择排序
理解起来相对容易,平均时间复杂度为O(n^2)
,直接选择排序
在排序过程涉及到两个元素位置的交换,所以相同数值的元素的排列顺序很可能在交互过程发生改变,所以是一种不稳定
的排序算法。
思路
进行 n 次选择,n 为待排序元素个数,每次选择即排好一个元素。
示例待排序元素:【2,7,3,0】,下面高亮为已经排好序的。
- 以上面待排序元素为例,首先选择第一个元素
2
,再从2
后面所有元素中找出最小的元素,可通过依次比较每次存下最小值位置,遍历到最后发现0
元素为最小元素,将2
与0
互相交换位置。目前序列为:【0
,7,3,2】。 - 接着选择元素
7
,找出7
后面最小元素,发现是2
,7
和2
交换位置。目前序列为:【0
,2
,3,7】。 - 同理,直到选择到最后一个元素,排序结束。
代码
public class StraightChooseSort { public static void chooseSort(int a[]){ int n = a.length; for(int i = 0;i < n;i++){ int k = i;//记录a[i]到a[n-1]中找最小的元素的位置 for(int j = i + 1;j < n;j++){ if(a[j] < a[k]) k = j; } //交换位置 if(k != i){ int tmp = a[i] ; a[i] = a[k] ; a[k] = tmp; } } } public static void main(String[] args) { int[] arr = new int[]{2,7,3,0,5,4}; chooseSort(arr); for(int i = 0;i < arr.length;i++){ System.out.print(arr[i] + ","); } }}
直接插入排序
直接插入排序
理解起来也相对容易,平均时间复杂度为O(n^2)
,直接插入排序
在与前一个进行比较时,如果相等就排在其后面,所以是一种稳定
的排序算法。
思路
进行 n-1 次插入,n 为待排序元素个数,每次插入即排好一个元素。
示例待排序元素:【2,7,3,0】,下面高亮为已经排好序的。
- 以上面待排序元素为例,首先从第二个元素
7
开始,先记下7
这个值,然后与前一个元素2
进行比较,发现2
比自己小,不做移动。目前序列为:【2
,7
,3,0】。 - 接着从第三个元素
3
开始,先记下3
这个值,与前一个元素7
进行比较,发现7
比3
大,将7
往后移动一个位置,即现在7
在3
的位置。接着再与前一个2
比较,发现2
比3
小,不做移动,将3
插入到2
后面,即原先7
的位置。目前序列为:【2
,3
,7
,0】。 - 接着从第三个元素
0
开始,先记下0
这个值,与前一个元素3
进行比较,发现3
比0
大,将3
往后移动一个位置;接着与前一个7
进行比较,发现7
比0
大,将7
往后移动一个位置;接着与前一个2
进行比较,发现2
比0
大,将2
往后移动一个位置。这时由于前面已经没有元素,所以把0
插入现在所在位置,即第一个。目前序列为:【0
,2
,3
,7
】。
代码
public class StraightInsertionSort { private static void InsertSort(int[] arr){ int temp; for(int i = 1;i < arr.length;i++){ if(arr[i] < arr[i - 1]){ //从第二个开始,如果当前比前一个小 temp = arr[i]; //记录要插入的值 int j = i - 1; //从前一个开始 do{ //依次往后移动一个位置 arr[j + 1] = arr[j]; j--; }while(j >= 0 && arr[j] > temp);//当且仅当遇到的数都比要插入的数大 arr[j + 1] = temp;//找到位置,插入 } } } public static void main(String[] args) { int[] arr = new int[]{2,7,3,0,5,4}; InsertSort(arr); for(int i = 0;i < arr.length;i++){ System.out.print(arr[i] + ","); } }}
希尔排序
希尔排序
又叫缩小增量排序
,它是在直接插入排序
的基础上,引入增量
的做法,使得排序效率更高。时间复杂度约为n的1.2次幂
,介于基本排序算法和高效排序算法间。由于希尔排序
跳跃式的移动,使得值相同的元素可能被打乱顺序,因而它是不稳定排序。
思路
希尔排序
提出缩小增量
的概念在于解决直接插入排序
存在的问题:没一个元素在插入之前,都需要使大量被比较的元素移动位置。
而希尔排序
通过不断划分子序列并在子序列里进行直接排序,这样一来每个子序列相当于跨间隔进行比较,举个例子:按间隔为10
划分后,可能第1
个元素和第10
个元素在同一个子序列,原先直接插入排序
可能需要多次移动才能将第1
个元素和第10
个元素进行排序,现在跨间隔只需要1次。而经过多次缩小间隔划分后,所有元素会趋向于值小的在前面,值大的在后面,达到基本有序,这将有利于我们高效率地进行最后一次全体直接插入排序
。
缩小增量
基本思想是这样的:首先取一个整数gap < n
作为间隔(经试验,普遍认为gap=(n/3)+1
效率比较高)将全部元素氛围gap
个子序列,所以距离gap
的元素放在同一个子序列,在每一个子序列分别施行直接插入排序
,然后缩小间隔gap
,重复子序列划分和排序,直到gap==1
,所有元素在同一个序列。由于在排序后期,大多元素已经基本有序,所以排序速度仍然很快。
代码
public class ShellSort { public static void shellSort(int a[]){ int n = a.length; int gap = n; int j; do{ gap = gap/3 + 1;//缩小间隔,直到间隔为1,进行全体直接插入排序 for (j = gap; j < n; j++){//从数组第 gap+1 个元素开始 if (a[j] < a[j - gap])//每个元素与自己组内的数据进行直接插入排序 { int temp = a[j]; int k = j - gap; while (k >= 0 && a[k] > temp) { a[k + gap] = a[k]; k -= gap; } a[k + gap] = temp; } } }while(gap > 1); } public static void main(String[] args) { int[] arr = new int[]{2,7,3,0,5,4}; shellSort(arr); for(int i = 0;i < arr.length;i++){ System.out.print(arr[i] + ","); } }}
快速排序
快速排序
是目前最通用的高效的内部排序算法,平均时间复杂度为O(nlogn)
,一般情况下所需要的额外内存也是O(nlogn)
,如果不考虑额外内存的消耗,通常使用快排,在大量随机数排序时,其表现最好。另外,快排是一种不稳定的排序。
思路
快速排序
可以采用递归调用,每一次调用能对当前所有元素进行一次划分,保证在被选中的基本元素左边都是比它小的,右边都是比它大的。
如第一次我们选择所有元素的第一个元素作为基准元素,经过一次快排后,基准元素的位置可能变化到其他地方,但是此时可以保证的是在基准元素左边的都是比它小,在右边都是比它大的元素。这样一来,我们再对其左边的所有元素进行递归调用快排,右边的所有元素递归调用快排,最终就能完成排序。
这里进行一次快排的步骤演示:示例待排序元素:【2,7,3,0,1,4】,高亮为已经排好序的元素,<
表示指针left
,>
表示指针right
。
- 初始定义两个指针:
left
指向数组下标0,即第一个元素;right
指针数组下标5,即最后一个元素。指针left
往右移动,指针right
往左移动。两指针相遇,表示全部元素已被遍历,完成一次快排。 - 首先选择第一个元素
2
作为基准元素
,从右往左移动指针right
,找第一个比它小的元素,找到元素1
,移动元素1
到指针left
所在位置,即数组下标0的位置。现在指针right
从右往左走到数组下标4的位置。当前排序情况为:【1
<,7,3,0,>1,4】。 - 接着从左往右找比基准元素
2
大的元素(下文指的从左往右或从右往左即为排序情况中<>
里的元素),找到元素7
,把元素7
移动到指针right
的位置,同时指针left
向右移动一步走到数组下标1的位置。当前排序情况:【1
,7<,3,0,>7
,4】。 - 继续从右往左找比基准元素小的,找到元素
0
,于是把元素0
移动到指针left
所在位置,同时指针right
向左走了一步走到数组下标3的位置。当前排序情况:【1
,0
<,3,>0,7
,4】。 - 继续从左往右找比基准元素大的,找到元素
3
,于是把元素3
移动到指针right
所在位置,同时指针left
向右走了一步走到数组小标4的位置。当前排序情况:【1
,0
,3<,>3
,7
,4】。 - 接着从右往左移动指针
right
,立即和left
相遇,表示遍历结束,于是把基准元素2
放到相遇的位置,即数组下标2。此时完成一次快排,基准元素2
左边都比它小,右边都比它大。当前排序情况:【1
,0
,2
<>,3
,7
,4】。 - 接着再对基准元素左边进行递归快排,同样右边元素也进行递归快排。
代码
public class QuickSort{ public static void quickSort(int[] arr,int start,int end){ if(start >= end) return; int left = start; //当前这段数组最左端下标 int right = end; //当前这段数组最右端下标 int standard = arr[start]; //定义当前这段数组最左端元素为基准元素 while(left < right){ //left从左递增,right从右递减相遇了,说明所有元素都扫描过了。 //从右往左找比基准值小的 while(arr[right] >= standard && left < right){ right--; } if(left < right){ //如果没有找到,则left = right,说明基准元素右边都比他大,则不用替换。 arr[left++] = arr[right]; //left++ 保证下一步从左往右找时从 left + 1 个找起,因为第i在本行已经被替换确认为是比基准元素小的 } //从左往右找比基准值大的 while(arr[left] <= standard && left < right){ left++; } if(left < right){ arr[right--] = arr[left]; } } //此时left == right这个位置的值应该为基准值,且左边所有元素比它小,右边比它大。 arr[left] = standard; //继续递归调用 quickSort(arr, start, left-1); //对当前基准值左边元素排序 quickSort(arr, right+1, end); //对当前基准值右边元素排序 } public static void main(String[] args) { int[] arr = new int[]{2,7,3,0,5,4}; quickSort(arr,0,arr.length-1); for(int i = 0;i < arr.length;i++){ System.out.print(arr[i] + ","); } }}
堆排序
堆排序
也是一种高效的内部排序算法,平均时间复杂度为O(nlogn)
。由于其利用堆
这一数据结构,所以实现起来相对麻烦一些;不过只要理解了堆
,实际上堆排序
算法非常简洁。由于堆
结构经常需要向上或向下调整,所以堆排序
也是一种不稳定排序。
思路
理解堆排序
的重点在于理解堆
,这里简要介绍下堆
。
堆
是一种特殊的完全二叉树,主要在于它的所有父节点都大于或小于子节点,所以堆也分为最大堆
和最小堆
。由于其根节点可以一直维持为所有最大或最小的元素,这种结构称为优先队列。在一些需要操作所有元素的最大或最小值时效率特别高。
如在最大堆
中取得最大值的时间复杂度为O(1)
;而维持最大堆
的时间复杂度也仅为O(logn)
。
- 利用这一特点,我们可以每次将根节点,即堆的最大值,与倒数第
(n-i)
个节点交换值(这里n
为节点个数,i
为已交换次数),这一步只需要O(1)
的时间复杂度。但此时最大堆的特性已经被破坏,根节点不再是最大值了。 - 这时我们再调整为最大堆,这一步耗费
O(logn)
。 - 重复步骤 1,2 直到所有
(n == i
),即所有结点都交换成功。此时从根节点开始遍历,就是完成的升序。
所以最关键的算法就是调整堆为最大堆,做法如下:
从根节点开始,与左右子结点进行比较,如果子结点比父节点大,则两结点交换,即把较大值向上调整,保持父节点始终比子节点大。接着再把被调整下来的原父节点继续与子结点进行比较和调整。一旦父节点比左右子节点大,则不再往下调整。
(设所有结点个数为n
,当前结点为第i
个结点,则左子树存在公式为:(i * 2) <= n
,同理右子数存在公式为:(i * 2 + 1) <= n
;如果是以数组存储堆的话,该公式改为:左子树存在公式为:(i * 2 + 1) <= (n - 1)
,同理右子数存在公式为:(i * 2 + 2) <= (n -)
,其中i
为数组下标。)
而这一切的前提是最大堆已经建立成功,所以下面介绍下最大堆的建立:
这里有个做法能在O(n)的时间复杂度里完成最大堆的建立。首先有个元素数量为n
的数组,我们知道完全二叉树的倒数第一个非叶子节点的位置为第n/2
个,在数组中的下标为n/2 - 1
。把大于这个下标的节点直接作为叶子节点开始进行建堆。然后从倒数第n/2
个位置开始,进行最大堆调整,这时就能把倒数第n/2
个位置的节点调整为具有最大值的父节点。同理,依次对倒数第n/2 - 1
个、n/2 - 2
个 … 1
个节点进行调整,最终就能得到整个最大堆。
代码
public class HeapSort { private int[] heap;//用来存放堆的数组 private int count;//用来存储堆中元素的个数 //排序 public void heapSort(){ while(count > 1){ swap(0,--count); siftdown(0); } } //向下调整函数 public void siftdown(int i) //传入一个需要向下调整的结点编号i,这里传入0,即从堆的顶点开始向下调整 { int maxIndex = 0;//记录较大值的下标 boolean needSiftDown = true;//用来标记是否需要继续向下调整 //当节点有儿子且需要向下调整 while((i*2 + 1) <= count-1 && needSiftDown) { //得到节点和其左儿子中较大的一个的下标 maxIndex = (heap[i] < heap[(i*2 + 1)]) ? (i*2 + 1) : i; if((i*2 + 2) <= count-1)//如果有右儿子,再得到当前较大的下标 maxIndex = (heap[maxIndex] < heap[(i*2 + 2)]) ? (i*2 + 2) : maxIndex; //如果发现最大的结点编号不是自己,说明子结点中有比父结点更大的 if(maxIndex != i) { swap(maxIndex,i);//交换 i = maxIndex;//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整 } else needSiftDown = false;//则否说明当前的父结点已经比两个子结点都要大了,不需要在进行调整了 } } //建堆 public void creat() { //从最后一个非叶结点到第1个结点依次进行向上调整 for(int i = count/2 - 1;i >= 0;i--) siftdown(i); } //通过下标交换数组堆中两个元素值 public void swap(int x,int y) { int temp = heap[x]; heap[x] = heap[y]; heap[y] = temp; } //赋值时同时得到个数 public void setHeap(int[] heap) { this.heap = heap; this.count = heap.length; } public int[] getHeap() { return heap; } public static void main(String[] args) { int[] arr = new int[]{2,7,3,0,5,4}; HeapSort hs = new HeapSort(); hs.setHeap(arr);// 设置待排序的数组 hs.creat();//建堆 hs.heapSort();//排序 for(int i = 0;i < hs.getHeap().length;i++){ System.out.print(hs.getHeap()[i] + ","); } }}
归并排序
归并排序
是一种高效内部排序算法,与快排
和堆排序
相比它的优势在于:它是稳定的排序,并且性能与输入顺序无关,总是为O(nlogn)
。
思路
归并排序
采用分治法
,将大问题不断化为更小的问题,直到小问题达到能够直接解决的程度,再向上不断合并小问题。
如我们要对一个数组采用归并排序
,我们不断地将它从中间划分成两个小数组,小数组再划分成小数组,直到小数组元素个数为1,能很方便地进行计算,就开始合并两个已经排好序的小数组,不断地向上合并,最终整个数组就是排好序的。
所以现在关键在于对两个有序数组的合并。我们可以这么做:
设有有序数组 A
,B
;以及临时合并数组 C
。
首先选取A
、B
数组中第一个元素较小的元素放入数组C
中,假设拥有最小元素的数组为A
数组。接着再从A
数组中取第二个元素与B
数组的第一个元素进行比较,谁小就放入C
数组,不断重复这样的步骤,期间如果有一个数组为空,则直接把另一个数组后面元素全部放到C
数组中,完成合并。
代码
public class MergeSort { public static void sort(int a[], int first, int last, int temp[]){ if (first < last) { int mid = (first + last) / 2; sort(a, first, mid, temp); //左边有序 sort(a, mid+1, last, temp); //右边有序 merge(a, first, mid, last, temp); //再将两个有序数列合并 } } public static void merge(int a[], int first, int mid, int last, int temp[]) { int i = first; int j = mid + 1; int k = 0; while (i <= mid && j <= last) { if (a[i] <= a[j]) temp[k++] = a[i++]; else temp[k++] = a[j++]; } while (i <= mid) temp[k++] = a[i++]; while (j <= last) temp[k++] = a[j++]; for (i = 0; i < k; i++) //回写 a[first + i] = temp[i]; } public static void main(String[] args) { int[] arr = new int[]{2,7,3,0,5,4}; int[] temp = new int[6]; sort(arr, 0, arr.length-1, temp); for(int i = 0;i < arr.length;i++){ System.out.print(arr[i] + ","); } }}
参考
《数据结构》殷人昆版
白话经典算法系列之五 归并排序的实现
坐在马桶上看算法(12):堆—神奇的优先队列
- 常用排序算法--冒泡排序
- 常用排序算法--插入排序
- 常用排序算法--希尔排序
- 常用排序算法--堆排序
- 常用排序算法--归并排序
- 常用排序算法--快速排序
- 常用排序算法--冒泡排序
- 常用排序算法--快速排序
- 常用排序算法-快速排序
- 常用排序算法-冒泡排序
- 常用排序算法-归并排序
- 常用排序算法-希尔排序
- 常用排序算法-冒泡排序
- C++常用排序算法
- C++常用排序算法
- 常用排序算法
- 常用排序算法
- 常用的排序算法
- 继承
- android内容提供者
- hdu 1078 FatMouse and Cheese(记忆化搜索)
- Java 多线程编程之五:一个理解 wait() 与 notify() 的例子
- MLiA ID3 DecisionTree
- 常用排序算法
- Android Studio上方便使用butterknife注解框架的偷懒插件Android Butterknife Zelezny
- HorizontalScrollView嵌套gridview布局的冲突
- 混淆配置eg
- iOS 启动页广告
- 地鼠的困境_ssl1333_匹配
- Android中四种设置控件背景颜色的方法
- C++补做作业1
- spring.xml配置