给定一个规模为n的数组,在n个数字中寻找最小的一个,而后与第0位置的数交换,而后在后n-1个数中找最小的,与第1个位置的数字交换。当交换了i次之后,数组左边的i个数字是有序的,右边n-i个数字无序。而后把在n-i个数字中寻找最小的,与i位置的数字交换位置。直到交换n次之后,整个数组有序。算法
时间复杂度:O(n²)数组
实现:markdown
public class Selection{ public static void sort(int[] a) { int t; for(int i=0;i<a.length;i++){ int index=i;//用来保存剩余乱序数组中的最小值的位置 for(int j=i+1;j<a.length;j++){ if(a[index]>a[j]){//寻找最小值并保存位置 index=j; } } //找到最小值的位置后交换 t=a[i]; a[i]=a[index]; a[index]=t; } } }
给定一个规模为n的数组,把第i号(iui
public class Insertion{ public static void sort(int[] a) { int t=0; for(int i=1;i<a.length;i++){ int v=a[i],k; for(k=i-1;k>=0&&v<a[k];k--){//查找左边比a[i]的元素大的元素,右移。 a[k+1]=a[k]; } a[k+1]=v;//把a[i]放入比a[i]小的元素的后面 } } }
根据插入排序,若是n个元素中最小的数在最右端,那么该数字须要比较n-1次才能够到达目标位置,由于是逐个比较元素的大小的。若是把逐个比较改成相隔1个比较并移动、相隔1个比较并移动、相隔h个比较并移动,那么最右端的元素到目标位置的比较移动次数将会大幅度减少。因此,在每隔h个元素都比较移动完之后,n个数中将会出现n/h个有序对,他们彼此相隔h,而后把h–,而后再比较移动,直到h==1且比较移动完后,排序结束。插入排序就是h=1的希尔排序。
时间复杂度:未知
适用范围:数据规模较大,数据较离散。
实现:spa
public class Shell { public static void sort(int[] a){ int h=1,t=0; h=a.length/2; while(h>=1){//直到相隔距离为1时,最后一次比较移动 for(int i=h-1,j;i<a.length;i++){//从i位置开始,保证以前每隔h位置的元素都是有序的,一开始从h-1开始,左边只有h个元素,因此每一组都是有序的 int base=a[i];//要插入的元素 for(j=i-h;j>=0&&a[j]>base;j-=h){//把要插入的元素与该组的其余元素比较大小,若是被比较的元素较大,则右移h个位置 a[j+h]=a[j]; } a[j+h]=base;//把要插入的元素插入到找到的位置 } h/=2;//相隔距离每次减半 } // display(a); } }
经过递归的方式,把n个元素等分红两部分,而后再将这两部分等分红四部分,以此类推,知道将n个元素分红n个单独的组,这n个组由于只有一个元素,因此是有序的。
而后把其中相邻的两个组合并,要求合并后的组也是有序的,因而造成了n/2个组。而后再相邻的组合并,造成n/4个组。依此类推,知道合并成只有一个组,排序完毕。
合并两个组时,能够将两个指针指向两个数组的第一个位置,而后比较大小,把小的放入临时数组,而后该指针后移。若是其中一个数组的指针到了最右,则把另外一个数组剩下的元素复制到临时数组。
过程以下图:
时间复杂度:O(nlgn)
缺点:须要开辟一个与原数组相同大小的辅助数组
实现:指针
public class Merge { static int[] temp=null; public static void sort(int[] a){ int start,end; temp=new int[a.length]; start=0; end=a.length-1; merge(a,start,end); // display(a); } public static void merge(int[] a,int start,int end){ if(start==end) return; int mid=(start+end)/2; merge(a,start,mid);//把数组分红两半,分别排序 merge(a,mid+1,end); if(a[mid]<a[mid+1])//前一个数组中最大的数比后一个数组中最小的数小,说明两个数组合并后已经有序,全部不须要再排序 return; mergeSort(a,start,end); } public static void mergeSort(int[] a,int start,int end){ int mid=(start+end)/2; for(int i=start,j=mid+1,k=start;k<=end;k++){//i,j分别表示两个数组的第一个元素(物理上只有一个数组,但逻辑上分为两个数组) if(i>mid) temp[k]=a[j++];//若是第一个数组比较结束,则以此把第二个数组全部元素复制到临时数组 else if(j>end) temp[k]=a[i++];//若是第二个数组比较结束,则以此把第一个数组全部元素复制到临时数组 else if(a[i]<a[j]) temp[k]=a[i++];//若是第一个数组i位置元素比较小,则把a[i]复制到临时数组,而且i右移 else temp[k]=a[j++];//若是第二个数组j位置元素比较小,则把a[j]复制到临时数组,而且j右移 } System.arraycopy(temp, start, a, start, end-start+1);//把临时数组中的元素复制到原数组 } public static void display(int[] a){ for(int i=0;i<a.length;i++) System.out.print(a[i]+" "); System.out.println(); } }
一、 给定n个数,从start到end进行排序
二、 从左边往右边找到i知足a[i]>a[start]
三、从右边往左边找到j知足a[j]>a[start]
四、 交换a[i]和a[j]
五、 重复三、4,直到i>=j
六、 交换a[start]和a[j],此时知足a[0~j-1]都小于a[j], a[j+1,end]都大于于a[j]
七、 从1从新执行,此时start=start,end=j-1 和start=j+1,end=end,直到start>=end
过程图:
时间复杂度(平均):O(nlgn)
适用范围:重复数字少,有序数字少
实现:code
public class Quick { public static void sort(int[] a) { sort(a, 0, a.length - 1); } private static void sort(int[] a, int start, int end) { if (start >= end) return; int i = partition(a, start, end);// 返回切割位置,此时i位置的元素大于[start,i-1],下雨[i+1,end] sort(a, start, i - 1); sort(a, i + 1, end); } private static int partition(int[] a, int start, int end) {// 要求返回位置的元素大于等于左边元素,返回位置的元素都小于等于右边的元素 int i = start + 1, j = end; i = start; j = end + 1; int value=a[start]; while (true) { while (a[++i] < value) // 向右寻找比a[start]大的数 if (i == end) break; while (a[--j] > value);// 向左寻找比a[start]小的数,但由于最左边是a[start],因此j>start恒成立 // if (j == start) // break; if (i >= j)// 若是i,j相遇或i>j,则结束寻找 break; swap(a, i, j); } // j就是知足a[j]大于等于左边,小于等于右边的位置 // 由于a[j]必定小于等于a[start]或者j等于start // a[i]必定大于等于a[start]或者i等于end // 根据前一种状况,i跟j不在同一位置的话,a[j]就是小于a[start]最右边的数,再往右的话就是a[i],而a[i]必定大于a[start] // 若是a[j]>a[start],就意味着j到了最左边(由于j是从右往左寻找比a[start]小的数的,若是没找到且停下来了,就意味着到头了) // 可是所谓的最左边就是a[start]所在位置,因此a[j]必定小于或等于a[start] swap(a, j, start); return j; } private static void swap(int[] a, int p, int q) { int t = a[p]; a[p] = a[q]; a[q] = t; } }
(二叉)堆有序定义:在一颗二叉树中,若是全部父节点不小于子节点,则该二叉树就是堆有序
下潜操做定义:若是一个父节点大于两个子节点或者子节点中的一个,那么把父节点与子节点中大的那个交换位置,而后把以前的父节点(如今已被交换到子节点的位置)与其新的子节点进行比较并交换,知道该节点大于其子节点,则结束操做。该操做称为下潜操做。排序
对于一个有序堆,序号为0的节点必定是最大的元素,把这个元素去掉,并把最后一个元素放到0位置,该堆又变成了无序堆。而后对0节点进行下潜操做,该堆从新变成了有序堆。而后继续移除、继续排序,最终该堆大小为0,原数组的位置变为一个有序序列。
该图为把一个有序堆的最大元素移除、排序最终获得有序数列的过程。
那么接下来的问题就是如何把一个无序堆构建成有序堆。
只要把一个堆自下而上进行下潜操做,那么最终就是一个有序堆。而最后一层节点没有子节点,因此不须要进行下潜操做。从倒数第二层的最后一个节点开始进行下潜操做,知道第一个元素,那么就能够造成一个有序堆,过程以下图:
递归
算法复杂度:O(nlgn)图片
实现:
public class Heap { // 以下一颗二叉树数字表示其序号 // // 0 // 1 2 // 3 4 5 6 // // 根据二叉树的定义,序号为n的节点的父节点(若是存在)序号为n/2⌉-1,其子节点(若是存在)序号为n*2+1和n*2+2 // 若是共有n个元素,则倒数第二次的最后一个节点序号为(n+1)/2⌉+1 public static void sort(int[] a) { int n = a.length; for (int k = (int) (Math.ceil((double) (n + 1) / 2) + 1); k >= 0; k--) // 从倒数第二层的最后一个几点开始建堆 sink(a, k, n); while (n > 1) { swap(a, 0, --n); sink(a, 0, n); } } public static void sink(int[] a, int k, int n) {// 下潜操做 while (k * 2 + 2 < n) { int j = k * 2 + 1;// j表示第一个子节点 if (a[j] < a[j + 1])// 若是第一个子节点小于第二个子节点 j++;// 此时j表示第二个子节点 if (a[k] > a[j])// 若是大的子节点小于父节点,则表示父节点大于两个子节点,则不须要继续下潜了 break; swap(a, k, j);// 不然把父节点和子节点交换 k = j; } } private static void swap(int[] a, int p, int q) { int t = a[p]; a[p] = a[q]; a[q] = t; } }