程序员必知8大排序
排序算法就是基础中的基础,程序员必知!
一、插入排序
1)简单插入排序
(1)基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。例:对9, 2, 7, 19, 100, 97排序
得到结果:
java代码:
1 package Insertion_Sort; 2 3 import java.util.Arrays; 4 5 /** 6 * 直接插入排序(Straigh Insertion Sort) 内部排序、O(n^2)、稳定 原理:从待排序的数中选出一个来,插入到前面的合适位置。 7 * 过程:思路上从待排序的数据中选出一个,插入到前面合适的位置, 耗时点在插入方面,合适的位置意味着我们需要进行比较找出哪是 8 * 合适的位置,举个例子:对于9,2,7,19,100,97,63,208,55,78这组数, 9 * 第一个数9前面没有,不做操作,当第一个数完后,剩下的数就是待排序的数, 我们将要从除去9开始的书中选出一个插入到前面合适的位置, 10 * 拿到2后,放在temp上,进行注释中的2处的代码, 2处的代码就是通过循环找出这个合适的位置, 发现比temp大的数,立即将该数向后移动一位 11 * (这样做的目的是:前面需要空出一位来进行插入), 最后通过注释3处的代码将数插入。 本排序适合:基本有序的数据 12 * 13 * @author cq 14 * 15 */ 16 public class Straigh_ISort { 17 18 static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 }; 19 20 public static void insertSort() { 21 int L = data.length; 22 int temp; 23 for (int i = 1; i < L; i++) {// -----1----- 24 if (data[i] < data[i - 1]) { 25 temp = data[i]; // 将待插入的记录用temp保存下来 26 int j = i - 1; 27 while ((j >= 0) && temp < data[j]) {// -----2----- 28 data[j + 1] = data[j]; // 比待插入记录大的位置后移 29 j--; 30 } 31 data[j + 1] = temp; // 插入到正确位置 -----3----- 32 } 33 } 34 } 35 36 static void print(int[] a) { 37 System.out.println(Arrays.toString(a)); 38 } 39 40 public static void main(String[] args) { 41 print(data); 42 insertSort(); 43 print(data); 44 45 } 46 47 }
2)二分插入排序
(1)基本思想:利用“折半查找”来减少关键字间的比较次数,其基本操作是在一个有序(已排好序)中进行查找和插入,由此进行的插入排序过程:与直接插入排序相比,就是在关键字间比较的次数减少。
package Insertion_Sort; import java.util.Arrays; /** * 折半插入排序(Binary Insertion Sort)又叫 二分插入 * 内部排序、O(n^2)、不稳定 * 原理:利用“折半查找”来减少关键字间的比较次数,其基本操作是在一个有序(已排好序)中 * 进行查找和插入,由此进行的插入排序 * 过程:与直接插入排序相比,就是在关键字间比较的次数减少。 *本排序适合:基本有序的数据 * @author cq * */ public class Binary_ISort { static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 }; public static void insertSort() { int i, j, low, mid, high, temp, L= data.length; for(i = 1; i < L; i++){ temp = data[i]; low = 0; high = i-1; while(low <= high){ mid = (low + high)/2; //在data[low...high中折半查找插入位置] if(temp < data[mid]){ high = mid - 1; // 插入点在低半区 }else { low = mid + 1; // 插入点在高半区 } } for( j = i-1; j >= high+1; j--){ data[j+1] = data[j]; //将待插入位置以后的后移 } data[high+1] = temp; // 插入到正确位置 } } static void print(int[] a){ System.out.println(Arrays.toString(a)); } public static void main(String[] args) { print(data); insertSort(); print(data); } }
3、简单选择排序
(1)基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
(2)实例:
4、堆排序 (1)基本思想:堆排序是一种树形选择排序,是对直接选择排序的有效改进。
堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
(2)实例:
初始序列:46,79,56,38,40,84
建堆:
交换,从堆中踢出最大数
剩余结点再建堆,再交换踢出最大数
依次类推:最后堆中剩余的最后两个结点交换,踢出一个,排序完成。
5、冒泡排序
(1)基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
(2)实例:
6、快速排序
(1)基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
(2)实例:
上图中将待排序列分成两部分,一部分比基准元素小,一部分大于基准元素,然后对这两部分重复上图的求解过程。
(这只是快速排序的一种实现方式,个人认为比较容易理解)
7、归并排序
(1)基本排序:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
(2)实例:
8、基数排序
(1)基本思想:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
(2)实例:
稳定性说明:排序前,2(或者更多)个相等的数在序列的前后位置顺序和排序后它们在序列中的前后位置顺序一样。
实例:
待排序数列:5,4,8,6,1,8,7,9
排序结果:1,4,5,6,7,8,8,9
稳定:1,4,5,6,7,8,8,9
不稳定:1,4,5,6,7,8,8,9
说明:对比红色的8和紫色的8,看他们排序前后的位置。排序前,红8在紫8前面,如果排序后红8仍然在紫8前面,则排序算法稳定,否则不稳定。
现在我们分析一下8种排序算法的稳定性。
(请网友结合前面的排序基本思想来理解排序的稳定性(8种排序的基本思想已经在前面说过,这里不再赘述)不然可能有些模糊)
(1)直接插入排序:一般插入排序,比较是从有序序列的最后一个元素开始,如果比它大则直接插入在其后面,否则一直往前比。如果找到一个和插入元素相等的,那么就插入到这个相等元素的后面。插入排序是稳定的。
(2)希尔排序:希尔排序是按照不同步长对元素进行插入排序,一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,稳定性就会被破坏,所以希尔排序不稳定。
(3)简单选择排序:在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。光说可能有点模糊,来看个小实例:858410,第一遍扫描,第1个元素8会和4交换,那么原序列中2个8的相对前后顺序和原序列不一致了,所以选择排序不稳定。
(4)堆排序:堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2, ...这些父节点选择元素时,有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,所以堆排序并不稳定。
(5)冒泡排序:由前面的内容可知,冒泡排序是相邻的两个元素比较,交换也发生在这两个元素之间,如果两个元素相等,不用交换。所以冒泡排序稳定。
(6)快速排序:在中枢元素和序列中一个元素交换的时候,很有可能把前面的元素的稳定性打乱。还是看一个小实例:6 4 4 5 4 7 8 9,第一趟排序,中枢元素6和第三个4交换就会把元素4的原序列破坏,所以快速排序不稳定。
(7)归并排序:在分解的子列中,有1个或2个元素时,1个元素不会交换,2个元素如果大小相等也不会交换。在序列合并的过程中,如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,所以,归并排序也是稳定的。
(8)基数排序:是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
8种排序的分类,稳定性,时间复杂度和空间复杂度总结: