当咱们进行数据处理的时候,每每须要对数据进行查找操做,一个有序的数据集每每可以在高效的查找算法下快速获得结果。因此排序的效率就会显的十分重要,本篇咱们将着重的介绍几个常见的排序算法,涉及以下内容:算法
1、排序相关的基本概念
排序实际上是一个至关大的概念,主要分为两类:内部排序和外部排序。而咱们一般所说的各类排序算法其实指的是内部排序算法。内部排序是基于内存的,整个排序过程都是在内存中完成的,而外部排序指的是因为数据量太大,内存不能彻底容纳,排序的时候须要借助外存才能完成(经常是算计着某一部分已经计算过的数据移出内存让另外一部分未被计算的数据进入内存)。而咱们本篇文章将主要介绍内排序中的几种经常使用排序算法:数组
还有一个概念问题,排序的稳定性问题。若是Ai = Aj,排序前Ai在Aj以前,排序后Ai还在Aj以前,则称这种排序算法是稳定的,不然说明该算法不稳定。函数
2、插入类排序算法
插入类排序算法的核心思想是,在一个有序的集合中,咱们将当前值插入到适合位置上,使得插入结束以后整个集合依然是有序的。那咱们接下来就学习下这几种同一类别的不一样实现。学习
一、直接插入排序
直接插入排序算法的核心思想是,将第 i 个记录插入到前面 i-1 个已经有序的集合中。下图是一个完整的直接插入排序过程:指针
由于一个元素确定是有序的,i 等于 2 的时候,将第二个元素插入到前 i-1个有序集合中,当 i 等于3的时候,将第三个元素插入到前 i-1(2)集合中,等等。直到咱们去插入最后一个元素的时候,前面的 i-1 个元素构成的集合已是有序的了,因而咱们找到第 i 个元素的合适位置插入便可,整个插入排序完成。下面是具体的实现代码:code
public static void InsertSort(int[] array){ int i=0,j=0,key; for (i=1;i<10;i++){ key = array[i]; j = i-1; while(j>=0&&key<array[j]){ //须要移动位置,将较大的值array[j]向后移动一个位置 array[j+1] = array[j]; j--; } //循环结束说明找到适当的位置了,是时候插入值了 array[j+1] = key; } //输出排序后的数组内容 for (int value : array){ System.out.print(value+","); } } //主函数中对其进行调用 int[] array = {1,13,72,9,22,4,6,781,29,2}; InsertSort(array);
输出结果以下:排序
整个程序的逻辑是从数组的第二个元素开始,每一个元素都以其前面全部的元素为基本,找到合适的位置进行插入。对于这种按照从小到大的排序原则,程序使用一个临时变量temp保存当前须要插入的元素的值,从前面的子序列的最后一个元素开始,循环的与temp进行比较,一旦发现有大于temp的元素,让它顺序的日后移动一个位置,直到找到一个元素小于temp,那么就找到合适的插入位置了。递归
由于咱们使用的判断条件是,key>array[j]。因此来讲,插入排序算法也是稳定的算法。对于值相同的元素并不会更改他们原来的位置顺序。至于该算法的效率,最好的状况是全部元素都已有序,比较次数为n-1,最坏的状况是全部元素都是逆序的,比较次数为(n+2)(n-1)/2,因此该算法的时间复杂度为O(n*n)。索引
二、二分折半插入排序
既然咱们每次要插入的序列是有序的,咱们彻底可使用二分查找到合适位置再进行插入,这显然要比直接插入的效率要高一些。代码比较相似,再也不作解释。队列
public static void halfInsertSort(int[] array){ for(int k=1;k<array.length;k++){ int key = array[k]; //找到合适的位置 int low,high,mid; low = 0;high = k-1; while(low <= high){ mid = (low+high)/2; if(key == array[mid])break; else if(key > array[mid]){ low = mid+1; }else{ high = mid-1; } } //low的索引位置就是即将插入的位置 //移动low索引位置后面的全部元素 for(int x=k-1;x>=low;x--){ array[x+1] = array[x]; } array[low] = key; } //遍历输出有序队列内容 for(int key:array){ System.out.print(key + ","); } }
虽然,折半插入改善了查找插入位置的比较次数,可是移动的时间耗费并无获得改善,因此效率上优秀的量可观,时间复杂度仍然为O(n*n)。
二、希尔排序
直接插入排序在整个待排序序列基本有序的状况下,效率最佳,但咱们每每不能保证每次待排序的序列都是基本有序的。希尔排序就是基于这样的情形,它将待排序序列拆分红多个子序列,保证每一个子序列的组成元素相对较少,而后经过对子序列使用直接排序。对于本就容量不大的子序列来讲,直接排序的效率是至关优秀的。
希尔排序算法使用一个距离增量来切分子序列,例如:
如图,咱们初始有一个序列,按照距离增量为4来拆分的话,能够将整个序列拆分红四个子序列,咱们对四个子序列内部进行直接插入排序获得结果以下:
修改距离增量从新划分子序列:
很显然,当距离增量变小的时候,序列的个数也会变少,可是这些子序列的内部都基本有序,当对他们进行直接插入排序的时候会使得效率变高。一旦距离增量减小为1,那么子序列的个数也将减小为1,也就是咱们的原序列,而此时的序列内部基本有序,最后执行一次直接插入排序完成整个排序操做。
下面咱们看算法是的具体实现:
/*渐减delete的值*/ public static void ShellSort(){ int[] array = {46,55,13,42,94,17,5,70}; int[] delets = {4,2,1}; for (int i=0;i<delets.length;i++){ oneShellSort(array,delets[i]); } //遍历输出数组内容 for(int value : array){ System.out.print(value + ","); } }
/*根据距离增量的值划分子序列并对子序列内部进行直接插入排序*/ public static void oneShellSort(int[] array,int delet){ int temp; for(int i=delet;i<array.length;i++){ //从第二个子序列开始交替进行直接的插入排序 //将当前元素插入到前面的有序队列中 if(array[i-delet] > array[i]){ temp = array[i]; int j=i-delet; while(j>=0 && array[j] > temp){ array[j+delet] = array[j]; j -= delet; } array[j + delet] = temp; } } }
方法比较简单,具体的实现和直接插入排序算法相近,此处再也不作解释。
3、交换类排序
交换类的排序算法通常是利用两个元素之间的值的大小进行比较运算,而后移动外置实现的,这类排序算法主要有两种:
一、冒泡排序
冒泡排序经过两两比较,每次将最大或者最小的元素移动到整个序列的一端。这种排序至关常见,也比较简单,直接上代码:
public static void bubbleSort(int[] array){ int temp = 0; for(int i=0;i<array.length-1;i++){ for(int j =0;j<array.length-1-i;j++){ if(array[j]>array[j+1]){ //交换两个数组元素的值 temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } //遍历输出数组元素 for(int value : array){ System.out.print(value + ","); } }
二、快速排序
快速排序的基本思想是,从序列中任选一个元素,但一般会直接选择序列的第一个元素做为一个标准,全部比该元素值小的元素所有移动到他的左边,比他大的都移动到他的右边。咱们称这叫作一趟快速排序,位于该元素两边的子表继续进行快速排序算法直到整个序列都有序。该排序算法是目前为止,内部排序中效率最高的排序算法。具体它是怎么作的呢?
首先他定义了两个头尾指针,分别指向序列的头部和尾部。从high指针位置开始扫描整个序列,若是high指针所指向的元素值大于等于临界值,指针前移。若是high指针所指向的元素的值小于临界值的话:
将high指针所指向的较小的值交换给low指针所指向的元素值,而后low指针前移。
而后从low指针开始,逐个与临界值进行比较,若是low指向的元素的值小于临界值,那么low指针前移,不然将low指针所指向的当前元素的值交换给high指针所指向的当前元素的值,而后把high指针前移。
按照这样的算法,
当low和high会和的时候,就表明着本次快速排序结束,临界值的左边和右边都已归类。这样的话,咱们再使用递归去快速排序其两边的子表,直到最后,整张表必然是有序的。下面咱们看代码实现:
/*快速排序的递归定义*/ public static void FastSort(int[] array,int low,int high){ if(low<high){ int pos = OneFastSort(array,low,high); FastSort(array,low,pos-1); FastSort(array,pos+1,high); } }
public static int OneFastSort(int[] array,int low,int high){ //实现一次快速排序 int key = array[low]; int flag = 0; while (low != high) { if (flag == 0) { //flag为0表示指针从high的一端开始移动 if (array[high] < key) { array[low] = array[high]; low++; flag = 1; } else { high--; } } else { //指针从low的一端开始移动 if (array[low] > key) { array[high] = array[low]; high--; flag = 0; } else { low++; } } } array[low] = key; return low; }
若是上述介绍的快速排序的算法核心思想理解的话,这段代码的实现也就比较容易理解了。
4、选择类排序
选择类排序的基本思想是,每一趟会在n个元素中比较n-1次,选择出最大或者最小的一个元素放在整个序列的端点处。选择类排序有基于树的也有基于线性表的,有关树结构的各类排序算法,咱们将在后续文章中进行描述,此处咱们实现简单的选择排序算法。
public static void ChooseSort(int[] array){ for (int i=0;i<array.length;i++){ for (int j=i+1;j<array.length;j++){ if(array[i]>array[j]){ //发现比本身小的元素,则交换位置 int temp = array[j]; array[j]=array[i]; array[i] = temp; } } } //输出排序后的数组内容 for (int key : array){ System.out.print(key+","); } }
代码堪比冒泡排序的算法实现,比较简单直接,此处再也不赘述。
5、归并类排序算法
这里的归并类排序算法指的就是归并排序。归并排序的核心思想是,对于一个初始的序列不断递归,直到子序列中的元素足够少时,对他们进行直接排序。而后递归返回继续对两个分别有序的序列进行直接排序,最终递归结束的时候,整个序列必然是有序的。
对于一个初始序列,咱们递归拆分的结果如上图。最小的子序列只有两个元素,咱们能够轻易的对他们进行直接的排序。简单的排序结果以下:
而后咱们递归返回:
初看起来和咱们的希尔排序的基本思想有点像,希尔排序经过对初始序列的稀疏化,使得每一个子序列在内部上都是有序的,最终在对整个序列进行排序的时候,序列的内部基本有序,整体上能提升效率。可是咱们的归并排序的和核心思想是,经过不断的递归,直到子序列元素足够少,在内部对他们进行直接的排序操做,当递归返回的时候,对返回的两个子表再次进行归并排序,使得合成的新序列是有序的,一直到递归返回调用结束时候,整个序列就是有序的。
/*归并排序的递归调用*/ public static void MergeSort(int[] array,int low,int high){ if(low == high){ //说明子数组长度为1,无需分解,直接返回便可 }else{ int p = (low+high)/2; MergeSort(array,low,p); MergeSort(array,p+1,high); //完成相邻两个子集合的归并 MergeTwoData(array,low,high); } }
/*用于排序两个子序列的归并排序算法实现*/ public static void MergeTwoData(int[] array,int low,int high){ int[] arrCopy = new int[high-low+1]; int i,j; i = low;j= (low+high)/2+1; for (int key=0;key<=high-low;key++){ //若是左边子数组长度小于右边数组长度,当左数组所有入库以后,右侧数组不用作比较直接入库 if(i==(low+high)/2+1){ arrCopy[key] = array[j]; j++; } //若是右侧数组长度小于左侧数组长度,当右侧数组所有入库以后,左侧数组不用作比较直接入库 else if(j==high+1){ arrCopy[key]=array[i]; i++; }else if(array[i]<array[j]){ arrCopy[key]=array[i]; i++; }else{ arrCopy[key] = array[j]; j++; } } j = 0; //按顺序写回原数组 for(int x=low;x<=high;x++) { array[x] = arrCopy[j]; j++; } }
咱们的递归调用方法仍是比较简单的,首先从一个序列的中间位置开始递归分红两个子序列并传入该序列的头尾索引,当头尾索引相等的时候,说明序列中只有一个元素,因而无需调用归并排序,直接返回。等待两边的子序列都返回的时候,咱们调用归并排序对这两个子序列进行排序,继续向上返回直到递归结束,最终的序列必然是有序的。咱们主要看看用于归并两个子序列的排序算法的实现。
假设咱们递归返回了这样的两个子序列,由于这些子序列都是递归返回的,因此他们在内部都是有序的,因而咱们须要对两个子序列排序和并成新序列并向上递归返回。
排序的算法以下:
分别取出两个序列的栈顶元素,进行比较,把小的一方的元素出栈并存入新序列中,值较大的元素依然位于栈顶。这样,当有一方的元素所有出栈以后,另外一方的元素顺序填入新序列中便可。具体的算法实现已经在上述代码中给出了。
至此,线性的基本排序算法都已经介绍完成了,有些算法介绍的比较粗糙,待后续深刻理解后再回来补充,总结不到之处,望指出!