整理系统的时候发现了原来写的各类算法的总结,看了一下,大吃一惊,那时候的我还如此用心,具体的算法,有的已经模糊甚至忘记了,看的时候就把内容整理出来,顺便在熟悉一下,之后须要的时候就能够直接过来摘抄了。下面是总结的几个经常使用的排序算法:算法
可能你们对插入排序,快速排序,冒泡排序比较经常使用,在知足需求的时候也简单一些,下面逐一说一下每一个算法的实现方式,不保证是写的最有效率的,可是能保证的是,各类算法的中心思想是对的,你们领会精神便可:api
插入排序:数组
插入排序在生活中最真实的场景就是在打牌的时候了,尤为在斗地主的时候使用的最频繁了,咱们天天在不知不觉中都在使用这种算法,因此请不要告诉你们你不会算法,来咱们你们一块儿重温那舒适的时刻,首先上家搬牌,做为比较聪明伶俐的人,手急眼快的人,我喜欢不看牌,叩成一摞,最后看,这跟排序算法没毛关系,略过,都抓完以后看牌。从左到右,看抓的什么牌,一看,第一圈抓了个5,第二圈来个2,假如说有强迫症,牌必须是按照顺序排好的,因为按照顺序排放,因此要知足,若是你左边的第一张牌的点数小于你抓牌的点数,那么左边全部牌的点数都小于所抓牌的点数,这时候你就要比较,5>2(只论点数),而后你手里的牌就是2---5,第三圈来个4,牌都好小,这时候你就拿4和5比(或者拿2比都同样),4<5,而后4应该在5的左边,如今的顺序暂时是2---4---5,由于还不肯定4左边的牌的点数是否是小于4,全部要找到所在位置左边第一个小于4的点数,把4放在这个点数的右边,好巧2<4,因此如今的顺序肯定为2---4---5,第四轮抓了一个3,都过小了,可是依然须要把3插到对应的位置,首先3<5,那么放到5的前面,顺序暂时是2---4---3---5,3继续向左边寻找,知道找到比它小的那张牌,而后它和4比较结果暂时为2---3---4---5,而后在跟2作对比,得出结果,顺序为2---3---4---5,以此类推,不知不觉中就完成了插入排序的算法。ide
过程就是这个样子,咱们看一下在代码中如何实现: ui
1 public void sort(int[] data) { 2 for (int j = 1; j < data.length; j++) { 3 int i = j - 1; 4 int key = data[j]; 5 while (i >= 0 && data[i] > key) { 6 data[i + 1] = data[i]; 7 data[i] = key; 8 i -= 1; 9 } 10 } 11 }
因为我用数组来实现,下标从0开始,因此看起来可能以为怪怪的,这让这段代码看起来可能有点很差理解,this
第2-10行:的循环就是咱们看牌的过程,为何从第张开始,是由于看第一张的时候没有比较,姑且认为它(点数为5)是最小的,spa
第3行:的意思就是告诉我当前看的这张牌将要和那个位置的牌作比较(应该都是跟如今看的牌所在位置的前一个位置的牌作比较),我第二张抓到的是2,他就要跟第与第一张比较,3d
第4行:看这张牌是什么点数。 code
第5行:若是尚未比到头,切前一个位置牌的点数大于这张牌的点数,那么将他们俩换位置。对象
第6-7行:互换位置。
第8行:因为这张牌的位置前移了一位,那么下次作比较牌的位置也要向前移动一位。
OK,插入排序很简单就说道这里了。适合数据量小的状况下排序使用。
快速排序:
请宽恕我生活阅历不高,快速排序我确实是找不到合适生活中的应用场景了,就画个小图来讲明一下吧:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 6 | 8 | 4 | 1222 | 29 | 872 | 5 |
这些数据是用来排序的,快速排序是经过把将要排序的数组进行拆分,而后局部进行排序,在程序开始的首先要取数组中的左后一个位置保存的值做为所进行排序的数组“分水岭”,就是把比这个值小的数都放在这个数的左边,比这个数大的数都放到这个数的右边。这是如何实现的,无非是两方面,比较和位置调整。
须要将数组中的全部数据都进行比较,若是有须要,那么调整位置,调整位置分为主动调整和被动调整两种状况。
主动调整:
须要知足两个条件:(1)小于最后一个位置保存的数据值。(2)所在位置前面的位置中保存的数值大于最后一个位置保存的数值。
主动调整的时候要将数据保存最后一个已经比较过得知保存的数值小于最后一个位置保存的数据值位置的后一位,是否是特别拗口,那上面的例子为例上面表格中上面的数字是对应数组的下标,在第一次比较的时候,比较1<5,前面没有大于5的值,位置不用变,6>5,也不用变,8>5,也不用变,4<5,且在坐标为1的位置上保存的数值为6,全部4须要主动调整,此时最后一个已经比较过得知保存的数值小于最后一个位置保存的数据值的位置为0,要讲4保存在1的位置,可是1的位置保存的值是6,全部6就要作被动调整。
被动调整:
因为自己的位置即将被别人占用,所作的调整。被动调整位置就是和发起主动调整的数据进行位置互换,最后将发起主动调整的数值保存到空出来的坐标上,发生后移的数值能够确定的认为是大于最后一个下标所保存的数值的,否则早就被主动调整了。若是仍是很差理解那么仍是以本例子为例:在比较到4的时候,须要调整,须要被动调整的下标起始位置为1,发起主动调整的数值的下标为3,那么就和小标3保存的值8进行位置互换,后移以后的结果为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 4 | 8 | 6 | 1222 | 29 | 872 | 5 |
第一轮比较以后,发现已经没有须要调整位置的时候了,这时候该调整最后一个下标保存值的位置了,由于前面说在先要取数组中的左后一个位置保存的值做为所进行排序的数组“分水岭”因此你们应该知道须要把下标为7保存的值5主动到下标下标为2的位置。而后进行位置调整。调整方法前面已经说过,再也不重复。最终结果以下:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 4 | 5 | 6 | 1222 | 29 | 872 | 8 |
接下来将要分配的数组分为两部分:
0 | 1 | 2 |
1 | 4 | 5 |
3 | 4 | 5 | 6 | 7 |
6 | 1222 | 29 | 872 | 8 |
对这两部分从新进行操做,因此快速排序,须要用递归的方式计算。下面就须要保存的几个变量作一下说明:
第一:是最后一个下标的值,由于在这个值在进行主动调整位置的时候,所在数组下标的位置会被覆盖。如上例中的下标 7.
第二:正在与最后值进行比较的数据的下标k,保存k是为了经过比较以后万一须要位置调整的时候,依次后移到的位置。
第三:已经与左后一个坐标保存的值比较过,明确不须要调整位置的最后一个数组下标i,保存这个变量是由于,若是后面的第k个下标元素须要调整位置,须要i+1下标上的数值和k下标的数值互换。
下面来看一下快速排序算法的代码实现:
1 public void sort(int[] data) { 2 quickSort(data, 0, data.length - 1); 3 } 4 5 public void quickSort(int[] data, int p, int r) { 6 if (p < r) { 7 int q = partition(data, p, r); 8 quickSort(data, p, q - 1); 9 quickSort(data, q + 1, r); 10 } 11 } 12 13 public int partition(int[] data, int p, int r) { 14 int x = data[r]; 15 int i = p - 1; 16 for (int j = p; j <= r; j++) { 17 if (data[j] < x && i < j) { 18 i++; 19 int key = data[j]; 20 data[j] = data[i]; 21 data[i] = key; 22 // if (i != j) { 23 // data[i] += data[j]; 24 // data[j] = data[i] - data[j]; 25 // data[i] -= data[j]; 26 // } 27 } 28 } 29 int key = x; 30 data[r] = data[i + 1]; 31 data[i + 1] = key; 32 33 // if ((i + 1) != r) { 34 // data[i + 1] += data[r]; 35 // data[r] = data[i + 1] - data[r]; 36 // data[i + 1] -= data[r]; 37 // } 38 39 return 1 + i; 40 }
若是要是可以将注释部分互换位置的部分闹明白,尤为是判断条件,那么就真的理解快速排序法了。例子中quickSort方法实现了递归调用,重要的方法是partition,它完成了拆分排序数组,和位置调整的操做。
在partition中,咱们用x来保存要比较的值。用i来保存已经肯定的不须要位置调整的下标,从-1开始,用j来保存正在比较验证的数值的数组下标,从0开始。
冒泡排序:
这个应该是最多见的,大概也就是在要排序的集合的最后面开始,用第一个元素依次和前面的比较,若是比前面的小,那么互换位置,而后在跟前一个比较,直到比较到一个位置(这个位置前面的数值必定小于正在比较的数值)。因此也须要用到两个变量,一个用来标记正在比较的数值的下标,一个用来保存那个“位置”,猜想你们都知道,因此就对于冒泡排序就很少废话了,上代码:
1 public void sort(int[] data) { 2 for (int i = 0; i < data.length; i++) { 3 for (int j = data.length - 1; j > i + 1; j--) { 4 if (data[j - 1] > data[j]) { 5 data[j - 1] = data[j - 1] + data[j]; 6 data[j] = data[j - 1] - data[j]; 7 data[j - 1] -= data[j]; 8 } 9 } 10 } 11 }
很简单。
堆排序:
不得不说,堆排序我已经不记得了,看了一会才看明白,看明白以后,我就对发明堆派寻的人很崇拜,多么牛逼的算法。
总的来讲堆排序就是把数组a[0.....n]中总找出一个最大值,而后把最大值max放到数组的最后a[n]=max,而后再从a[0.....n-1]中找出一个最大值,而后把最大值保存在a[0.....n-1]中,依次类推。堆排序的关键就是如何最快的找到必定范围内的最大值。
堆排序是经过最大堆的方式找到最大值,先了解一些概念:
二叉堆:就理解成二叉树就好了。
最大堆:知足全部的知足父>子。
最小堆:知足全部子>父。
首要要作的就是把要排序的数组构形成一个二叉堆,臆想的,好比排序的数组为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 6 | 8 | 4 | 1222 | 29 | 872 | 5 |
而后咱们经过想象构造出了以下一个二叉堆,构造二叉堆的方法很简单,从头到位按照二叉树的方式构造,构造结果以下:
经过构造的二叉堆咱们须要得出对咱们有用的信息,有用的信息就是指每一个元素在二叉堆中的孩子节点所在数组中的坐标,知道二叉树的人不用看二叉堆都知道节点i的子节点的下标应该是2*i+1,已经2(i+1),由于咱们是从下标0开始计算。数字就是经过下标来形象的描述二叉堆。
二叉堆构造完成之后,那么咱们来讲一下是如何将二叉堆构形成最大堆,构造方法是,用a[i]与他的两个孩子a[2*i+1],a[2(i+1)](若是存在的话)比较,将a[i],a[2*i+1],a[2(i+1)]中最大的值与a[i]位置互换,那么i从哪开始算呢?很简单就是找到最后一个有孩子的节点,由于咱们是从0开始计算,那么这个下标应该是从a.lenth-1/2到0,这样一轮下来a[0]就是最大值,代码实现:
1 package sort; 2 3 /** 4 * O(nlgn) 把数据假象构形成一个二叉堆,每一个节点的左右坐标跟别为2i,2i+1. 构造Max树: 5 * 6 * @author Think 7 * 8 */ 9 public class HeapSort implements ISort { 10 int dataLength = 0; 11 12 /*** 13 * 将最大的值拿出来,放到最后,而后经过1--n-1个数据,找新的最大数 14 */ 15 @Override 16 public void sort(int[] data) { 17 dataLength = data.length; 18 buildMaxHeap(data); 19 for (int i = dataLength - 1; i >= 1; i--) { 20 data[i] += data[0]; 21 data[0] = data[i] - data[0]; 22 data[i] -= data[0]; 23 dataLength--; 24 max_heapify(data, 0); 25 } 26 } 27 28 /** 29 * 保证二叉堆的性质 A[i] >= A[left[i]] A[i] >= A[right[i]] 30 * 在构造二叉堆和每次从最后移除一个元素之后都要从新组织二叉堆的结构 31 * 32 * @param data 33 * @param i 34 */ 35 public void max_heapify(int[] data, int i) { 36 int largest = 0; 37 int l = 2 * i + 1; 38 int r = 2 * (i + 1); 39 if (l < dataLength && data[l] > data[i]) { 40 largest = l; 41 } else { 42 largest = i; 43 } 44 if (r < dataLength && data[r] > data[largest]) { 45 largest = r; 46 } 47 if (largest != i) { 48 data[i] += data[largest]; 49 data[largest] = data[i] - data[largest]; 50 data[i] -= data[largest]; 51 max_heapify(data, largest); 52 } 53 } 54 55 /** 56 * 构建最大堆 57 * 58 * @param data 59 */ 60 public void buildMaxHeap(int[] data) { 61 for (int j = ((dataLength - 1) / 2); j >= 0; j--) { 62 max_heapify(data, j); 63 } 64 } 65 }
在sort方法中,作的内容是构造最大堆,把经过最大堆获取的最大值a[0]与a数组的最后一个下标保存的数值进行位置交换,而后在将最大值从构造最大堆的数据中排除。排除的方法是经过设置最大堆时候数组的界限,在例子中dataLength变量就是这个做用。
buildMaxHeap方法是第一次构造最大堆,你们可能有疑问在buildMaxHeap中调用max_heapify方法和在sort方法中调用max_heapify方法的第二个参数为什么不同,为何有的从dataLength - 1开始递减,有的一直传0,有没有想过,其实不难回答,由于在排序开始的时候buildMaxHeap方法必须先构造出一个最大堆,此时对数据是没有任何假设的,数彻底是随机的,咱们不能保证a[0]保存的数值不是最大的,若是a[0]保存的数值是最大的,那么在和代码39-46行中largest变量永远的值都为0,没有结果,因此要从最下面开始逐一比较。可是在sort方法中执行max_heapify的时候对数据的排列就有了最大堆性质的保证。因此就能够从0开始,可是若是仍是要从最后往前比较那也绝对是没有问题的。
因为交换完位置之后,可能致使被交换下去的较小的值,有小于它下面子节点值的可能,例如在本例子中的最后交换到1时候,刚交换完的时候应该是这样的:
全部为了确保最大堆的性质因此要递归排序,这段代码是经过47-51行来完成的。
计数排序:
我我的喜欢计数排序----简单,可是对于要排序的数据是有一些要求,好比要明确知道要排序的一组数的范围,输入的数据的值要在(0,k)的开区间内,以及数据最好要密集,若是这组数据的波动性较大不适合用技术排序的方式进行排序操做。计数排序的中心思想就是对于给定的数值key肯定有多少数小于key,有多少值,那么这个值就是key值应该在排序后的数组中的下标。
方向找到之后就要找方法实现,计数排序的关键在于如何计算而且保存小于x值的数值的个数。技术排序是经过一个临时数组来保存,这个数组声明的长度就是咱们前面所说的明确最大值的长度+1。具体如何保存的请看具体实现:
1 package sort; 2 3 /** 4 * 计数排序(前提条件,可以预先知道所需排序的上限,须要多余的一点空间) 适合数据密集。有明确范围的状况 5 * 6 * @author Think 7 * 8 */ 9 public class CountSort { 10 11 public int[] sort(int[] data, int max_limit) { 12 int[] tmp = new int[max_limit]; 13 int[] des = new int[data.length]; 14 for (int i = 0; i < data.length; i++) { 15 tmp[data[i]] += 1; 16 } 17 for (int j = 1; j < max_limit; j++) { 18 tmp[j] += tmp[j - 1]; 19 } 20 for (int k = data.length - 1; k >= 0; k--) { 21 des[tmp[data[k]] - 1] = data[k]; 22 tmp[data[k]] -= 1; 23 } 24 25 return des; 26 } 27 28 }
12-13行,声明临时数组和最终须要生成的数组。14-16行是计数排序比较巧妙的地方,在tmp中第data[i]个坐标上设置累加1,在tmp数组中下标k的值,是在data数组中存在k值的个数。好比说在data数组中有3个5的数值,那么在tmp[5]中保存的值是3。好比须要排序的数组为:
5 | 4 | 6 | 3 | 2 | 1 |
那么tmp数组通过14-16行的循环操做之后的内容为:
0 | 1 | 1 | 1 | 1 | 1 | 1 |
咱们根据tmp数组中的数据,以及tmp数组的下标就能经过17-19行的内容,算出要排序的数组每一个数值在排序中应该排在什么位置,通过17-19行的处理,tmp数组中的内容为:
0 | 1 | 2 | 3 | 4 | 5 | 6 |
结合tmp的下标和数值能够知道,tmp数组的第(data[i])个下标保存的值减1,(tmp[data[i]]-1)就是data[i]数据通过排序后所在数组中的位置。
因此在20-23行进行赋值,排序完成。 其中22行中tmp[data[k]] -= 1这句话是用来处理在派寻的数组中有重复数值的状况,对重复值进行排序。
桶排序:
若是须要排序的数组中的数值分布均匀,并且在区间[0,1)内,那么桶排序是个不错的选择,桶排序就是把[0,1)区间的分割成10个大小相同的自区间,而后将数组中的数值恰当的“漏”到对应的区间中,好比0.12要漏到[0.1,0.2)的区间中,就是值k要知足区间的左边值<=k<区间的右边值。而后在经过插入排序或者快速排序等方式对每一个区间的数值进行派寻,下面是我实现的桶派寻的代码,和书上说的有点不同:
1 package sort; 2 3 public class BucketSort { 4 5 public double[] sort(double[] data) { 6 return bucket_sort(data); 7 } 8 9 public double[] bucket_sort(double[] data) { 10 double[] des = new double[data.length]; 11 Bucket[] tmp = new Bucket[10]; 12 for (int i = 0; i < tmp.length; i++) { 13 tmp[i] = new Bucket(0, null); 14 } 15 for (int i = 0; i < data.length; i++) { 16 Bucket bucket = new Bucket(data[i], null); 17 int bucket_list_index = (int) (data[i] * 10); 18 bucket_in_sort(tmp[bucket_list_index], bucket); 19 } 20 int j = 0; 21 for (int i = 0; i < tmp.length; i++) { 22 Bucket tmp_bucket = tmp[i].next; 23 while (tmp_bucket != null) { 24 des[j] = tmp_bucket.value; 25 tmp_bucket = tmp_bucket.next; 26 j++; 27 } 28 } 29 return des; 30 } 31 32 public void bucket_in_sort(Bucket sourct_bucket, Bucket bucket) { 33 Bucket tmp = sourct_bucket.next; 34 if (tmp == null) { 35 sourct_bucket.next = bucket; 36 return; 37 } 38 while (tmp.next != null) { 39 if (tmp.value > bucket.value) { 40 bucket.next = sourct_bucket.next; 41 sourct_bucket.next = bucket; 42 break; 43 } 44 tmp = tmp.next; 45 } 46 tmp.next = bucket; 47 } 48 49 public class Bucket { 50 double value; 51 public Bucket next; 52 53 public Bucket(double value, Bucket bucket) { 54 this.value = value; 55 this.next = bucket; 56 } 57 58 public double getValue() { 59 return value; 60 } 61 62 public void setValue(double value) { 63 this.value = value; 64 } 65 66 public Bucket getBucketList() { 67 return next; 68 } 69 70 public void setBucketList(Bucket next) { 71 this.next = next; 72 } 73 74 } 75 76 }
Bucket是我定义的一个对象,来帮助我实现桶排序,有value和next两个属性,value用来保存排序的数值,next表示桶中的下一个对象,若是不存在那么为空。第12-14行在每一个桶中初始化一个链表对象Bucket,未来顺着这个链表链接“漏”在桶中的数值,第15-18行是给每一个数值肯定“漏”到那个桶中,而后在调用bucket_in_sort方法,在“漏”如桶中的过程当中直接排序,将value值大的Bucket对象放到后面。第21-28行是逐个桶中去把数值拿出来,拿出来的数值就是排序完成的。
几个常见的排序算法就说完了,不一样的算法解决不一样场景下的问题,欢迎你们和我进行交流。
下面是源代码的下载地址: