排序算法心得
原文:https://www.cnblogs.com/xiaohuiduan/p/11188304.html
十大排序算法
基本排序算法:
- 选择排序
- 插入排序
- 冒泡排序
高效排序算法:
- 梳排序
- 希尔排序
- 快速排序
- 堆排序
牺牲空间节约时间的高效排序:
- 归并排序
- 基数排序
- 记数排序
下面我将以我自己的看法以及总结网上大神的经验分析各种排序的优缺点、时间空间复杂度。
基本排序算法
选择排序
基本原理:在数组中找到最小(或最大)的元素,存到排序的起始位置,在余下的数据中再找到最小(或最大)的元素,放到已排好的元素后面。以此类推,直至所有的元素排序完毕。(注:括号中对应的是另一种情况,即元素的排序顺序为从大到小。)
public static int[] choose(int a[]){ int min; for(int i=0;i<a.length;i++){ min = i; for(int j=i+1;j<a.length;j++){ if(a[min]>a[j]){ min=j; }
//如果第一个取出来的元素不是最小值,那就交换 if(min!=i){ exchange(a,i,j); } }
return a; } } public static void exchange(int a[],int i,int j){ int temp=0; if(a[i]!=a[j]{ temp=a[i]; a[i] = a[j]; a[j] = temp; } }
选择排序的时间复杂度:
第一次排需要比较的是N-1次;
最小的元素方法第一位,下一次就不需要比较了。第二次需要比较N-2次;
第三次需要比较N-3次;
.............
第N-1次需要比较1次
总的次数为(N-1)+(N-2)+......+1=N(N-1)/2
最好的情况的所有的元素已经有序,需要交换0次。
最坏的情况是所有元素为逆序,进行交换的次数是N-1。
故时间复杂度:O(N^2)
选择排序的空间复杂度:
最好的情况是所有的元素有序,空间复杂度为O(0);
最坏的情况是所有元素为逆序,空间复杂度为O(n);
平均的空间复杂度为O(1)。
选择排序是不稳定的
插入排序
基本原理:将属组中第一个元素认为是有序数组,从前往后从第二个元素开始扫描,将元素一个个插入到那个被认为的有序的数组中,每插入一个元素,有序数组的长度加1。做法是从第二个元素往后扫描,若该元素比第一个大则不用交换,若比第一个元素小,则将该元素放置第一个元素前面,扫面第三个元素,若比首元素大而比第二个元素小,则插入其中,往后以此类推,直至所有的元素有序。
例如:
代码实现:
public class charu { public void InsertSort(){ int temp,i,j; int[] a= {38,65,97,76,13,27,49}; for( i=1;i<a.length;i++){ //外层向右的index,即作为比较对象的数据的index temp=a[i]; //用作比较的数据 int index = i-1; while(index>=0 && a[index]>temp){ //当比到最左边或者遇到比temp小的数据时,结束循环 a[index+1] = a[index]; index--; } a[index+1] = temp; } System.out.println(Arrays.toString(a)); } public static void main (String []args){ charu c = new charu(); c.InsertSort(); } }
插入排序的时间复杂度:
最好情况:元素全部有序,直接往后扫描元素都比前一个元素大,O(n)
最坏情况:元素为逆序,每次扫描都要交换位置
第一次,扫描第二个元素,发现比第一个元素小,需要比较1次,进行交换。
第二次,扫描第三个元素,发现比前两个元素都小,需要比较2次,插入到数组首个位置。
第三次,比较3次
..........
第N次,扫描第N个元素,发现比前面N-1个元素都小,需要比较N-1次,插到数组首个位置。
总共需要比较:1+2+3+.....+N-1=N(N-1)/2
时间复杂度为O(N^2)
平均情况:O(N^2)
故插入排序的时间复杂度为O(N^2)
插入排序的空间复杂度:
最好情况所有元素有序,空间复杂度O(1)
最坏情况所有元素排序为逆序,空间复杂度为O(n)
平均空间复杂度为O(1)。
插入排序是稳定的
冒泡排序
基本原理:小的数字慢慢上浮,或则大的数字慢慢下沉,上浮到最顶或则下沉到最底则结束一轮循环。每次比较相邻得的两个数值,如果前面数值大于后面数值则两者进行交换,大的数值逐渐往后移动,直至移到最后。
代码实现
public class MaoPao { public static int[] maopao(int []nums){ int len = nums.length; if(len==0||len==1){ return nums; } for(int i=0;i<len;i++){ for(int j=0;j<len-i-1;j++){ if(nums[j+1]<nums[j]){ int temp=nums[j+1]; nums[j+1] = nums[j]; nums[j] = temp; } } } return nums; } }
public class MaoPao { int nums[]={2,6,7,1,9,4,3}; public void maopao(){ int len = nums.length; if(len==0||len==1){ return ; } for(int i=0;i<len;i++){ for(int j=0;j<len-i-1;j++){ if(nums[j+1]<nums[j]){ int temp=nums[j+1]; nums[j+1] = nums[j]; nums[j] = temp; } } } System.out.println(Arrays.toString(nums)); } public static void main(String []args){ MaoPao mao = new MaoPao(); mao.maopao(); } }
冒泡排序的时间复杂度:
最坏情况:我们想要从小到大而数组却是从大到小,事先未知,那么
第一轮,将最小的数字移动到数组首位置,要比较的个数为N-1;
第二轮,将第二小的数字移动到首位置后,要比较的个数为N-2;
..........
第N轮,将倒数第二小的数字移动到最大数字前面,要比较的个数为1个。
总共要比较的个数为:(N-1)+(N-2)+.......+1=N(N-1)/2。
故最坏的情况时间复杂度为O(N^2)。
最好的情况:元素全部有序,从小到大排好,做N-1次比较。时间复杂度为O(N)。
平均时间复杂度为O(N^2)。
冒泡排序是稳定的排序算法。
高效排序算法
快速排序
基本原理:在数组中选取一个数(一般是第一个数),分别与其它每一个数进行比较,把比这个数小的都放到它前面,把比这个数大的都放到它后面,放完后数组就分成两部分,以刚开始选取的那个数为界,往前的数都比它小,往后的数都比它大,然后分别对这两个部分进行递归排序算法,就可以实现整个数组的排序。
详细过程:数组首个数为基准数,设置两个哨兵left(指向数组首个元素)和right(指向数组末尾元素),先让right往左移动,找到比基准数小的数字就停住,然后left开始往右移动,找个比基准数大的数字就停住,left所指的数字与right所指的数字交换,继续移动(同样的是right先移动)。当right=left即两个哨兵相遇时,停止移动,将相遇点的元素与基准数相交换,这样刚开始的基准数就“归位了”,左边的元素必然比基准数小,右边的元素必然比基准数大。递归调用,新的数组以首个元素为基准数设为left,“归位后”的元素为right,重复上面的步骤。直至所有的元素有序。
代码实现:
package suanfa; public class KuaiSuPai { public static void quickSort(int[] arr,int left,int right){ int i,j,temp,t; if(left>right) return; temp=arr[left];//temp存的就是基准数 i=left; j=right; while(i!=j){ //顺序很重要,从右往左找 while(arr[j]>=temp && i<j) j--; //再从左往右找 while(arr[i]<=temp && i<j) i++; //交换两个数在数组中的位置 if(i<j)//让哨兵i和j没有相遇时 { t=arr[i]; arr[i]=arr[j]; arr[j]=t; } } //最终将基准数归位 arr[left]=arr[i]; arr[i]=temp; quickSort(arr,left,i-1); quickSort(arr,i+1,right); return; } public static void main(String []args){ int []arr = {10,7,2,4,7,62,3,4,2,1,8,9,19}; quickSort(arr,0,arr.length-1);//调用快速排序,left=0,right为最右端数字 for(int i=0;i<arr.length;i++){ System.out.println(arr[i]); } } }
快速排序的时间复杂度:
快速排序的时间复杂度是O(nlogn)
T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。 T(n) = 2*T(n/2) + n; n>1
那T(n)等于多少呢,再细分一下:T(n)=2*(2*T(n/4)+n/2)+n
=2*2(2*T(n/8)+n/4)+n
=.........
=2^k(2*T(n/2^k))+kn
而T(n/2^k)=1得到k=log2(n).将k代入上式得到T(n)=nlogn。所以快速排序的时间复杂度就是O(nlogn)。
快速排序中的空间复杂度是多少呢?
由于没有调用新的空间,所有快速排序的空间复杂度为O(1)。
快速排序是一种原地的、不稳定的排序算法,因为它排序过程中如果遇到相同的元素,位置会发生变换,因此其算法是不稳定的。
归并排序:
归并排序跟快速排序很像,利用到了分治的思想,将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序,然后将他们合并成一个子序列。合并子序列的过程就是两路归并。
import org.junit.Test; public class MergeSort { //两路归并算法,两个排好序的子序列合并为一个子序列 public void merge(int []a,int left,int mid,int right){ int []tmp=new int[a.length];//辅助数组 int p1=left,p2=mid+1,k=left;//p1、p2是检测指针,k是存放指针 while(p1<=mid && p2<=right){ if(a[p1]<=a[p2]) tmp[k++]=a[p1++]; else tmp[k++]=a[p2++]; } while(p1<=mid) tmp[k++]=a[p1++];//如果第一个序列未检测完,直接将后面所有元素加到合并的序列中 while(p2<=right) tmp[k++]=a[p2++];//同上 //复制回原素组 for (int i = left; i <=right; i++) a[i]=tmp[i]; } public void mergeSort(int [] a,int start,int end){ if(start<end){//当子序列中只有一个元素时结束递归 int mid=(start+end)/2;//划分子序列 mergeSort(a, start, mid);//对左侧子序列进行递归排序 mergeSort(a, mid+1, end);//对右侧子序列进行递归排序 merge(a, start, mid, end);//合并 } } @Test public void test(){ int[] a = { 49, 38, 65, 97, 76, 13, 27, 50 }; mergeSort(a, 0, a.length-1); System.out.println("排好序的数组:"); for (int e : a) System.out.print(e+" "); } }
时间复杂度为:O(NlogN),它跟快排的时间复杂度不同的是,快排最坏时间复杂度可达O(N^2),而归并排序最坏也是O(NlogN).
堆排序
堆是通过数组实现的二叉树,数组的第一个值往往是整个数组的最大值(或最小值),排序的过程就是取出根节点,将概念中的树最后一个元素放在原来的根节点上,然后通过调整树的结构,将树的最大元素放在根上,依次取出,树中没有元素则排序完毕,整个数组元素有序。
//构建大根堆:将array看成完全二叉树的顺序存储结构 private int[] buildMaxHeap(int[] array){ //从最后一个节点array.length-1的父节点(array.length-1-1)/2开始,直到根节点0,反复调整堆 for(int i=(array.length-2)/2;i>=0;i--){ adjustDownToUp(array, i,array.length); } return array; } //将元素array[k]自下往上逐步调整树形结构 private void adjustDownToUp(int[] array,int k,int length){ int temp = array[k]; for(int i=2*k+1; i<length-1; i=2*i+1){ //i为初始化为节点k的左孩子,沿节点较大的子节点向下调整 if(i<length && array[i]<array[i+1]){ //取节点较大的子节点的下标 i++; //如果节点的右孩子>左孩子,则取右孩子节点的下标 } if(temp>=array[i]){ //根节点 >=左右子女中关键字较大者,调整结束 break; }else{ //根节点 <左右子女中关键字较大者 array[k] = array[i]; //将左右子结点中较大值array[i]调整到双亲节点上 k = i; //【关键】修改k值,以便继续向下调整 } } array[k] = temp; //被调整的结点的值放人最终位置 }
//堆排序 public int[] heapSort(int[] array){ array = buildMaxHeap(array); //初始建堆,array[0]为第一趟值最大的元素 for(int i=array.length-1;i>1;i--){ int temp = array[0]; //将堆顶元素和堆低元素交换,即得到当前最大元素正确的排序位置 array[0] = array[i]; array[i] = temp; adjustDownToUp(array, 0,i); //整理,将剩余的元素整理成堆 } return array; }
//删除堆顶元素操作 public int[] deleteMax(int[] array){ //将堆的最后一个元素与堆顶元素交换,堆底元素值设为-99999 array[0] = array[array.length-1]; array[array.length-1] = -99999; //对此时的根节点进行向下调整 adjustDownToUp(array, 0, array.length); return array; }
代码来自:https://www.cnblogs.com/CherishFX/p/4643940.html
时间复杂度为:O(NlogN)
空间复杂度:O(1)
不稳定的排序算法
希尔排序
把待排序的数据元素分成若干个小组,对同一小组内的数据元素用直接插入排序进行排序;小组的个数逐次缩小,当完成了所有的数据元素都在一个小组内的排序后,排序过程结束。所以希尔排序又称作缩小增量排序。
public static void shellSort(int[] arr){ //初始化增量 int h = 1; //计算最大间隔,公式:h = h * 3 + 1 while(h < arr.length / 3){ h = h * 3 + 1; } //缩小增量进行排序 while(h > 0){ //进行插入排序 int waitInsert; //等待插入的数 int i,j; //i表示当前待插入数下标;j表示本次被比较的有序数位置 for(i = h; i < arr.length; i++) { waitInsert = arr[i]; //得到本轮待插入的数 j = i - h; //比较位置初始化,也就是有序序列的最后一个位置,从后往前 //若大于或等于等待插入的数值大小,则该数右移一个间隔大小,然后进行下一次比较 while(j > -1 && arr[j] >= waitInsert) { arr[j + h] = arr[j]; j = j - h; } //插入的位置一定是上一次比较的数的位置,也就是j+h的位置。(注意到j-h的时机即可理解) arr[j + h] = waitInsert; } //缩小增量,公式:h = (h - 1) /3 h = (h - 1) / 3; } }
时间复杂度:O(NlogN)
空间复杂度:O(1)
不稳定
时间复杂度为O(N)的排序算法(不基于比较的算法,思想来源于桶排序)
基数排序
class Demo { public static void main(String[] args) { //定义整型数组 int[] arr = {21,56,88,195,354,1,35,12,6,7}; //调用基数排序函数 lsd_RadixSort(arr,3); //输出排序后的数组 for(int i=0;i<arr.length;i++) { System.out.print(arr[i]+" "); } } //arr是要排序的数组,max是数组中最大的数有几位 public static void lsd_RadixSort(int[] arr,int max) { //count数组用来计数 int[] count = new int[arr.length]; //bucket用来当桶(在下面你就理解了什么是桶了),放数据,取数据 int[] bucket = new int[arr.length]; //k表示第几位,1代表个位,2代表十位,3代表百位 for(int k=1;k<=max;k++) { //把count置空,防止上次循环的数据影响 for(int i=0;i<arr.length;i++) { count[i] = 0; } //分别统计第k位是0,1,2,3,4,5,6,7,8,9的数量 //以下便称为桶 //即此循环用来统计每个桶中的数据的数量 for(int i=0;i<arr.length;i++) { count[getFigure(arr[i],k)]++; } //利用count[i]来确定放置数据的位置 for(int i=1;i<arr.length;i++) { count[i] = count[i] + count[i-1]; } //执行完此循环之后的count[i]就是第i个桶右边界的位置 //利用循环把数据装入各个桶中,注意是从后往前装 //这里是重点,一定要仔细理解 for(int i=arr.length-1;i>=0;i--) { int j = getFigure(arr[i],k); bucket[count[j]-1] = arr[i]; count[j]--; } //将桶中的数据取出来,赋值给arr for(int i=0,j=0;i<arr.length;i++,j++) { arr[i] = bucket[j]; } } } //此函数返回整型数i的第k位是什么 public static int getFigure(int i,int k) { int[] a = {1,10,100}; return (i/a[k-1])%10; } }
(代码来源网络,只做笔记积累)
时间复杂度:O(N)
空间复杂度:O(M)
计数排序:
package sort; public class CountSort { private static int[] countSort(int[] array,int k) { int[] C=new int[k+1];//构造C数组 int length=array.length,sum=0;//获取A数组大小用于构造B数组 int[] B=new int[length];//构造B数组 for(int i=0;i<length;i++) { C[array[i]]+=1;// 统计A中各元素个数,存入C数组 } for(int i=0;i<k+1;i++)//修改C数组 { sum+=C[i]; C[i]=sum; } for(int i=length-1;i>=0;i--)//遍历A数组,构造B数组 { B[C[array[i]]-1]=array[i];//将A中该元素放到排序后数组B中指定的位置 C[array[i]]--;//将C中该元素-1,方便存放下一个同样大小的元素 } return B;//将排序好的数组返回,完成排序 } public static void main(String[] args) { int[] A=new int[]{2,5,3,0,2,3,0,3}; int[] B=countSort(A, 5); for(int i=0;i<A.length;i++) { System.out.println((i+1)+"th:"+B[i]); } } }
(代码来源网络,只做笔记积累)
时间复杂度:O(N)
空间复杂度:O(M)

浙公网安备 33010602011771号