十种基本排序算法
一、
//直接插入排序 //思路:先取一个有序的队列,然后将其他数字一个一个和这个有序数列排序 //稳定 //时间复杂度 最好情况:O(n) 最坏情况O(n²) //空间复杂度 O(n)
/**
* 直接插入排序
* @author TMAC-J
*
*/
public class InsertSort {
private int[] array;
public InsertSort(int[] array) {
this.array = array;
}
/**
* 按从小到大的顺序排列
*/
public void calculate(){
for(int i = 1;i<array.length;i++){
for(int j = 0;j<i;j++){
if(array[i]<array[j]){
int temp = array[i];
//向后移动
for(int k = i-1;k>=j;k--){
array[k+1] = array[k];
}
array[j] = temp;
}
}
}
}
public static void main(String[] args) {
int[] a = {10,9,8,7,6,5,4,3,2,1};
InsertSort insertSort = new InsertSort(a);
insertSort.calculate();
for(int i = 0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
二、插入排序-希尔排序
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度
void print(int a[], int n ,int i){ cout<<i <<":"; for(int j= 0; j<8; j++){ cout<<a[j] <<" "; } cout<<endl; } /** * 直接插入排序的一般形式 * * @param int dk 缩小增量,如果是直接插入排序,dk=1 * */ void ShellInsertSort(int a[], int n, int dk) { for(int i= dk; i<n; ++i){ if(a[i] < a[i-dk]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入 int j = i-dk; int x = a[i]; //复制为哨兵,即存储待排序元素 a[i] = a[i-dk]; //首先后移一个元素 while(x < a[j]){ //查找在有序表的插入位置 a[j+dk] = a[j]; j -= dk; //元素后移 } a[j+dk] = x; //插入到正确位置 } print(a, n,i ); } } /** * 先按增量d(n/2,n为要排序数的个数进行希尔排序 * */ void shellSort(int a[], int n){ int dk = n/2; while( dk >= 1 ){ ShellInsertSort(a, n, dk); dk = dk/2; } } int main(){ int a[8] = {3,1,5,7,2,4,9,6}; //ShellInsertSort(a,8,1); //直接插入排序 shellSort(a,8); //希尔插入排序 print(a,8,8); }
三、冒泡排序
-
//冒泡排序 //思路:两两互相排序,把大的放后面,每一轮最后面的都是最大的,就好像冒泡一下 //时间复杂度 O(n²) //空间复杂度 O(1) public class BubbleSort { static int[] arr = { 5, 3, 2, 4, 7 }; public static void main(String[] args) { BubbleSort bs = new BubbleSort(); bs.betterSort1(); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } public void sort() { int temp = 0; for (int i = 0; i < arr.length - 1; i++) { for (int j = 1; j < arr.length; j++) { if (arr[j - 1] > arr[j]) { temp = arr[j - 1]; arr[j - 1] = arr[j]; arr[j] = temp; } } } } //改良版冒泡排序 public void betterSort1() { int pos = arr.length; while (pos > 0) { int temp = 0; for (int j = 1; j < pos; j++) { if (arr[j - 1] > arr[j]) { temp = arr[j - 1]; arr[j - 1] = arr[j]; arr[j] = temp; } } pos--; } } //两头冒泡法 public void Bubble_2 ( int r[], int n){ int low = 0; int high= n -1; //设置变量的初始值 int tmp,j; while (low < high) { for (j= low; j< high; ++j) //正向冒泡,找到最大者 if (r[j]> r[j+1]) { tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp; } --high; //修改high值, 前移一位 for ( j=high; j>low; --j) //反向冒泡,找到最小者 if (r[j]<r[j-1]) { tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp; } ++low; //修改low值,后移一位 } } }
四、简单选择排序
//简单选择排序 //思路:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。 //O(n^2) //空间复杂度 O(1) public class SelectSort { static int[] arr={3,1,5,7,2,10,9}; public static void main(String[] args){ SelectSort ss=new SelectSort(); ss.sort(); for(int i=0;i<arr.length;i++){ System.out.println(arr[i]); } } public void sort(){ for(int i=0;i<arr.length;i++){ int temp=0; for(int j=i+1;j<arr.length;j++){ if(arr[i]>arr[j]){ temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } } } } }
五、归并算法
import java.util.Arrays; public class MergeSort { /** * 归并排序 * 简介:将两个(或两个以上)有序表合并成一个新的有序表 即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列 * 时间复杂度为O(nlgn) * 稳定排序方式 * @param nums 待排序数组 * @return 输出有序数组 */ public static int[] sort(int[] nums, int low, int high) { int mid = (low + high) / 2; if (low < high) { // 左边 sort(nums, low, mid); // 右边 sort(nums, mid + 1, high); // 左右归并 merge(nums, low, mid, high); } return nums; } public static void merge(int[] nums, int low, int mid, int high) { int[] temp = new int[high - low + 1]; int i = low;// 左指针 int j = mid + 1;// 右指针 int k = 0; // 把较小的数先移到新数组中 while (i <= mid && j <= high) { if (nums[i] < nums[j]) { temp[k++] = nums[i++]; } else { temp[k++] = nums[j++]; } } // 把左边剩余的数移入数组 while (i <= mid) { temp[k++] = nums[i++]; } // 把右边边剩余的数移入数组 while (j <= high) { temp[k++] = nums[j++]; } // 把新数组中的数覆盖nums数组 for (int k2 = 0; k2 < temp.length; k2++) { nums[k2 + low] = temp[k2]; } } // 归并排序的实现 public static void main(String[] args) { int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 }; MergeSort.sort(nums, 0, nums.length-1); System.out.println(Arrays.toString(nums)); } }
六、快速排序
//快速排序 //思路:一般选取第一个数作为基数,然后从后往前比较,如果比基数小,就和基数交换,在从左往右比较,如果比基数大,就交换,直到i=j为止,此时,把这个数组按基数为分界,分成两组,在进行上述计算 //空间复杂度 O(1)
//时间复杂度O(n^2)
public class Quick { /**主方法*/ public static void main(String[] args) { //声明数组 int[] nums = {27, 8, 100, 9, 23, 41, 65, 19,3,6, 0, 1, 2, 4, 5}; //应用快速排序方法 quickSort(nums, 0, nums.length-1); //显示排序后的数组 for(int i = 0; i < nums.length; ++i) { System.out.print(nums[i] + ","); } System.out.println(""); } /**快速排序方法*/ public static void quickSort(int[] a, int lo0, int hi0) { int lo = lo0; int hi = hi0; if (lo >= hi) return; //确定指针方向的逻辑变量 boolean transfer=true; while (lo != hi) { if (a[lo] > a[hi]) { //交换数字 int temp = a[lo]; a[lo] = a[hi]; a[hi] = temp; //决定下标移动,还是上标移动 transfer = (transfer == true) ? false : true; } //将指针向前或者向后移动 if(transfer) hi--; else lo++; //显示每一次指针移动的数组数字的变化 /*for(int i = 0; i < a.length; ++i) { System.out.print(a[i] + ","); } System.out.print(" (lo,hi) = " + "(" + lo + "," + hi + ")"); System.out.println("");*/ } //将数组分开两半,确定每个数字的正确位置 lo--; hi++; quickSort(a, lo0, lo); quickSort(a, hi, hi0); } }
七、堆排序
//堆排序 //思路:初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。 //时间复杂度 O(nlgn ) //空间复杂度 O(1) public class HeapSort { private static int heapSize; public static void main(String[] args) { int[] a={1,4,2,7,9,3}; heapSort(a); for (int i : a) System.out.print(i + " "); } private static void heapSort(int[] a) { heapSize = a.length; buildMaxHeap(a); for (int i = a.length - 1; i >= 1; i--) { swap(a, i, 0); heapSize = heapSize - 1; maxHeapify(a, 0); } } private static void swap(int[] a, int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } private static void buildMaxHeap(int[] a) { for (int i = a.length / 2; i >= 0; i--) { maxHeapify(a, i); } } private static void maxHeapify(int[] a, int i) { int l = left(i); int r = right(i); int largest = i; if (l < heapSize && a[l] > a[i]) largest = l; else largest = i; if (r < heapSize && a[r] > a[largest]) largest = r; if (largest != i) { swap(a, i, largest); maxHeapify(a, largest); } } private static int left(int i) { return 2 * i; } private static int right(int i) { return 2 * i + 1; } }
八、计数排序
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改。牺牲空间换取时间的方法,在某个整数范围内,快于任何算法
public class CountSort { public static void main(String[] args) throws Exception { int[] array = { 9, 8, 7, 6, 5, 4, 3, 2, 6, 1, 0 }; countSort(array, 9); for(int i=0;i<array.length;i++){ System.out.println(array[i]); } } public static void countSort(int[] array, int range) throws Exception { if (range <= 0) { throw new Exception("range can't be negative or zero."); } if (array.length <= 1) { return; } int[] countArray = new int[range + 1]; for (int i = 0; i < array.length; i++) { int value = array[i]; if (value < 0 || value > range) { throw new Exception("array element overflow range."); } countArray[value] += 1; } for (int i = 1; i < countArray.length; i++) { countArray[i] += countArray[i - 1]; } int[] temp = new int[array.length]; for (int i = array.length - 1; i >= 0; i--) { int value = array[i]; int position = countArray[value] - 1; temp[position] = value; countArray[value] -= 1; } for (int i = 0; i < array.length; i++) { array[i] = temp[i]; } } }
九、桶排序
//桶排序 //思路:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。 // 简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。 // 例如要对大小为[1..1000]范围内的n个整数A[1..n]排序 // // 首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B[i]存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。 // // 然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。 // // 最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。 //当均匀分布时,空间复杂度接近为0(n) //空间复杂度O(N+M) import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; public class BucketSort { public static void bucketSort(double array[]) { int length = array.length; ArrayList arrList[] = new ArrayList[length]; /* * 每个桶是一个list,存放落在此桶上的元素 * 上次的基数排序我采用的是计数排序实现的,其实也可以用下面的方法,有兴趣的读者不妨一试(我认为太复杂) * 不过效率估计不高(采用了动态数组) */ //划分桶并填元素 for (int i = 0; i < length; i++) { //0.7到0.79放在第8个桶里,编号7;第一个桶放0到0.09 int temp = (int) Math.floor(10 * array[i]); if (null == arrList[temp]) arrList[temp] = new ArrayList(); arrList[temp].add(array[i]); } // 对每个桶中的数进行插入排序 for (int i = 0; i < length; i++) { if (null != arrList[i]) { //此处排序方法不定,不过越快越好,除了三大线性排序外,都没有Collections //和Arrays里的sort好,因为这是调优后的快拍 //Arrays里也有,在基数排序里用过copyOf和fill方法 Collections.sort(arrList[i]); } } //输出类似鸽巢排序 int count = 0; for (int i = 0; i < length; i++) { if (null != arrList[i]) { Iterator iter = arrList[i].iterator(); while (iter.hasNext()) { Double d = (Double) iter.next(); array[count] = d; count++; } } } } /* * 每个元素满足0<=array[i]<1,貌似还要长度相同, * 若是相同小数位(digit),则可以把小数搞为整数,最后再除以10^digit * 可以Random.nextInt(101)/100 */ public static void main(String[] args) { double array[] = { 0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68 }; bucketSort(array); for (int i = 0; i < array.length; i++) System.out.print(array[i] + " "); System.out.println(); } }
十、基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
第一步
第二步
第三步
import java.util.Arrays; public class RadixSort { public static void main(String[] args) { int[] data = new int[] { 1100, 192, 221, 12, 23 }; print(data); radixSort(data, 10, 4); System.out.println("排序后的数组:"); print(data); } public static void radixSort(int[] data, int radix, int d) { // 缓存数组 int[] tmp = new int[data.length]; // buckets用于记录待排序元素的信息 // buckets数组定义了max-min个桶 int[] buckets = new int[radix]; for (int i = 0, rate = 1; i < d; i++) { // 重置count数组,开始统计下一个关键字 Arrays.fill(buckets, 0); // 将data中的元素完全复制到tmp数组中 System.arraycopy(data, 0, tmp, 0, data.length); // 计算每个待排序数据的子关键字 for (int j = 0; j < data.length; j++) { int subKey = (tmp[j] / rate) % radix; buckets[subKey]++; } for (int j = 1; j < radix; j++) { buckets[j] = buckets[j] + buckets[j - 1]; } // 按子关键字对指定的数据进行排序 for (int m = data.length - 1; m >= 0; m--) { int subKey = (tmp[m] / rate) % radix; data[--buckets[subKey]] = tmp[m]; } rate *= radix; } } public static void print(int[] data) { for (int i = 0; i < data.length; i++) { System.out.print(data[i] + "\t"); } System.out.println(); } }
总结
各种排序的稳定性,时间复杂度和空间复杂度总结:
我们比较时间复杂度函数的情况:
时间复杂度函数O(n)的增长情况
所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。
时间复杂度来说:
(1)平方阶(O(n2))排序
各类简单排序:直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlog2n))排序
快速排序、堆排序和归并排序;
(3)O(n1+§))排序,§是介于0和1之间的常数。
希尔排序
(4)线性阶(O(n))排序
基数排序,此外还有桶、箱排序。
说明:
当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n);
而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O(n2);
原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。
稳定性:
排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。
稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序
选择排序算法准则:
每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。
选择排序算法的依据
影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
1.待排序的记录数目n的大小;
2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
3.关键字的结构及其分布情况;
4.对排序稳定性的要求。
设待排序元素的个数为n.
1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序 : 如果内存空间允许且要求稳定性的,
归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。
2) 当n较大,内存空间允许,且要求稳定性 =》归并排序
3)当n较小,可采用直接插入或直接选择排序。
直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。
直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序
5)一般不使用或不直接使用传统的冒泡排序。
6)基数排序
它是一种稳定的排序算法,但有一定的局限性:
1、关键字可分解。
2、记录的关键字位数较少,如果密集更好
3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
注:更多资料可参考:http://blog.csdn.net/hguisu/article/details/7776068