先提前约定交换函数:
protected void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
一、选择排序
思路:
从数组中选择最小元素,将它与数组的第一个元素交换位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。
不断进行这样的操作,直到将整个数组排序。
复杂度:
n个元素,第一次排序需要比较n-1次,第二次排序需要比较n-2次,则总共需要 ~
n^2
次比较
每一轮排序都是进行一次交换,所以选择排序需要 ~ n 次交换
特点:
选择排序的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要这么多的比较和交换操作。
选择排序的运行时间
代码:
public void sort(int[] nums){
int N = nums.length;
for(int i = 0; i < N - 1; i++){
int min = i;
for(int j = i+1; j<N;j++){
if(num[j]<num[i]){
min=j;
}
}
swap(nums,i,min);
}
}
二、冒泡排序
思路:
相邻元素两两比较,反序则交换,第一轮将最大元素浮到数组顶端,第二轮将第二大数组浮到第二个位置,直到没有任何一对数字需要比较
在一轮循环中,如果没有发生交换,那么说明数组已经是有序的,此时可以直接退出。
复杂度:
代码:
public static void bubbleSort(int[] arr){
int N = arr.length;
if(arr==null || N<2){
return;
}
boolean isSorted = false;
for(int i = N-1; i > 0&&!isSorted; end--){
isSorted = true;
// 如果该轮比较没有发生交换,则说明数组已经有序,isSorted变量为true不会设置为false,如果要发生交换,则会被设置为false
for(int j=0; j<i; j++){
if(arr[j] > arr[j+1]){
isSorted = false;
swap(arr, j, j+1);
}
}
}
}
三、插入排序
思路:
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
复杂度:
对于数组{3,5,2,4,1}, 它具有以下逆序:(3,2), (3,1), (5,2), (5,4), (5,1), (2,1), (4,1), 插入排序每次只能交换相邻元素,令逆序数量减少1。每交换一次,逆序数量少一个,因此插入排序需要交换的次数为逆序数量。
插入排序的时间复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么逆序较少,需要的交换次数也就较少,时间复杂度较低。
数组顺序即最好的情况下需要N-1次比较, 0 次交换
数组倒序即最差的情况下需要 ~
N^2 / 2
次比较以及 ~N^2/2
次交换平均情况下插入排序需要 ~ N^2/4比较以及 N^2/4次交换
代码:
public void sort(int nums){
int N = nums.length;
for(int i=1;i<N;i++){
for(int j=i; j>0; j--){
if(nums[j]<nums[j-1]){
swap(nums, j, j-1);
}
}
}
}
四、希尔排序
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少1。
希尔排序的出现就是为了解决插入排序的上述局限性。希尔排序也称递减增量排序算法,它通过交换不相邻的元素,每次可以将逆序数量减少大于1.(因为当两个元素间隔较远时,一次交换可能会减少2个以上的逆序数,进而减少了交换次数)
思路:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序时”,再对全体记录进行依次直接插入排序。
希尔排序使用插入排序对间隔h的序列进行排序。通过不断减小h,最后令h=1,就可以使得整个数组是有序的。
希尔排序的递增序列?
代码:
public void sort(int[] nums){
int N = nums.length;
int h = 1;
// 1. 选择一个增量序列序列
while(h < N/3){
h = 3*h + 1; //1,4,13,40...
}
// 2. 按增量序列个数k,对序列进行k趟排序
while(h>=1){
// 3.
for(int i=h; i< N; i++){
for(int j=i; j>=h; j-=h){
if(nums[j]<nums[j-h]){
swap(nums, j, j-h);
}
}
h = h/3;
}
}
}
五、归并排序
该算法采用分治法,采用分而治之的思想。归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。
归并排序的实现有两种方法:
- 自上而下的递归
- 自下而上的迭代
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好,因为每次都将问题对半分成两个子问题,这种对半分的算法复杂度一般为 O(NlogN) (推导可参考: