本文已参与「新人创作礼」活动,一起开启掘金创作之路。
\
选择排序:直接插入排序,简单选择排序,堆排序
插入排序:希尔排序
交换排序:冒泡排序,快速排序
归并排序
基数排序
冒泡排序
时间复杂度:O(n^2)
从前往后多次扫描,每次扫描一遍就把待排序序列中的最大值排到最后,像冒气泡一样
//冒泡排序 static void f2(int[] arr){ int temp; for(int j = arr.length-1; j > 0; j--) for(int i = 0; i < j; i++){ if(arr[i] > arr[i+1]){ temp = arr[i]; arr[i] = arr[i+1]; arr[i+1] = temp; } } }
插入排序
时间复杂度:O(n^2)
将一个无序的数插入到一个有序的序列中
//插入排序 static void f3(int[] arr){ for(int j = 1; j < arr.length; j++){ int target = arr[j]; int index = j-1; while (index > -1 && target < arr[index]){ arr[index+1] = arr[index]; index -= 1; } arr[index+1] = target; } }
希尔排序
时间复杂度:O(n^1.3-2)
将原本的数据进行分组,对每个组进行排序。
再将数据进行分组,但是每个组的数据量增多------由增量确定
对每一组进行插入排序。
//希尔排序 static void f1(int[] arr){ //第一个循环将间隔化为总长度的一半,如9个元素就是以间隔为4分组 坐标分别为【0,4,8】,【1,5】,【2,6】,【3,7】 //第二个循环将间隔再缩一半,为【0,2,4,6,8】,【1,3,5,7】 for(int interval = arr.length/2; interval > 0; interval = interval/2){ //第一轮循环 //第一个循环4开始,先比较4,0 //第二个循环5开始,比较5,1 //第三个循环6开始,比较6,2 //第四个循环7开始,比较7,3 //第五个循环8开始,比较8,4,0 //第二轮循环从2开始,先比较2,0 //3,1->4,2,0->5,3,1->6,4,2,0->7,5,3,1->8,6,4,2,0 for(int i = interval; i < arr.length; i++){ int target = arr[i]; int j = i - interval; while (j > -1 && target < arr[j]){ arr[j + interval] = arr[j]; j -= interval; } arr[j+interval] = target; } } }
希尔排序的性能分析
最坏的情况
希尔排序是分组的插入排序,如果最后一次分组所有数据仍然不是有序的,那么就跟直接插入排序没什么区别了,复杂度就是O(n^2)
如数据1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16
从增量4变化到增量为2,各组都是有序的
但是增量为1时,发现完全无序,排序效果跟直接插入排序相同,反而徒增了前面分组的时间
选择排序
时间复杂度:O(n^2)
每次选择最大或者最小的放到最前面或最后面,只记录下标,找到最大的那个下标,然后将该位置的数与放到最前面或者最后面
//选择排序 倒序 将排好的放到最前面 static void f4(int[] arr){ int i,j,max,temp; for(i = 0; i < arr.length; i++){ max = i; for(j = i + 1; j < arr.length; j++){ if(arr[j] > arr[max]){ max = j; } } temp = arr[max]; arr[max] = arr[i]; arr[i] = temp; } }
选择排序和冒泡排序的区别就是 冒泡排序每次比较发现不相等就交换,而选择排序每次比较只记录下标,最后再交换。
快速排序
时间复杂度:nlogn
1.选一个中心轴pivot
2.将大于Pivot的数字放到轴右边
3.将小于pivot的数字放到轴左边
4.递归调用两侧子序列,重新前三个步骤
//快速排序 static void f5(int[] arr,int L,int R){ //如果某侧只剩一个元素了,L就会等于R,退出排序 if(L >= R){ return; } int left = L,right = R; //选择最左侧当中心轴 int pivot = arr[left]; while (left < right){ //如果右侧的比轴大,就移动比较下一个 while (left < right && arr[right] >= pivot){ right--; } //如果比轴小,就换到左侧 if(left < right){ arr[left] = arr[right]; } //再去比较右侧,如果左侧小就移动比较下一个 while (left < right && arr[left] <= pivot){ left++; } //比轴大就换到右侧 if(left < right){ arr[right] = arr[left]; } //最后将轴放到指针交汇处位置 if(left >= right){ arr[left] = pivot; } } //递归排序轴左侧的序列和轴右侧的序列 f5(arr,L,right-1); f5(arr,right+1,R); }
堆排序
时间复杂度:O(nlogn)
堆符合的要求
- 1.是一颗完全二叉树(叶子节点都往左靠)
- 2.所有父节点的值都大于(小于)子节点
分类
- 大顶堆:每个结点的值都大于等于子节点
- 小顶堆:每个结点的值都小于等于子节点
步骤
先构建堆,建好堆之后,堆顶是最大的数据或是最小的数,每次取堆顶的数(与数组末尾交换),取完之后,再重新建堆,直到取完所有的数,就得到了有序的序列。
将树的逻辑结构存到数组中,下标之间的关系为:
编辑
public class HeapSort { //堆排序入口 static void heap_sort(int elem[],int n){ //建堆 int i; for(i = n/2 - 1; i >= 0; i--){ //从最后一个有孩子的节点开始 heapify(elem,n,i); } //排序 for(i = n-1; i > 0; i--){ swap(elem,i,0); //将数组第一个元素和最后一个元素交换位置 heapify(elem,i,0); //数组第一个元素位置被交换后,从开头重新维护堆的性质 } } //维护堆的性质 arr 数组,n 数组长度, i,待维护节点的下标 static void heapify(int elem[],int n,int i){ int largest = i; //最大节点的下标,假设是根节点 int lson = i*2+1; //左孩子下标 int rson = i*2+2; //右孩子下标 if(lson < n && elem[largest] < elem[lson]){ //根小于左孩子 largest = lson; //最大节点下标为左孩子 } if(rson < n && elem[largest] < elem[rson]){ largest = rson; //最大节点下标为右孩子 } if(largest != i){ //最大值不是根节点,则需要进行交换 swap(elem,largest,i);//交换 heapify(elem,n,largest); //下标largest的值被换为了elem[i],递归判断是否遵循堆的性质 } } static void swap(int elem[],int largest,int i){ int temp; temp = elem[largest]; elem[largest] = elem[i]; elem[i] = temp; } public static void main(String[] args) { int elem[] = {5,9,4,2,1,7,3,9,6}; heap_sort(elem,elem.length); for (int i : elem) { System.out.print(i); } } }
计数排序
编辑
基数排序
多关键字排序
例如多个百位数排序,先按照个位数排,将个位相同的放到一个桶中,再按照十位数排,十位相同的放到一个桶中,最后按照百位排,百位数相同的放到一个桶中
归并排序