说道排序,我们经常提到时间复杂度和空间复杂度,那么什么是时间复杂度什么又是空间复杂度呢?
时间复杂度:时间复杂度是指执行这个算法所需要的计算工作量
求解算法的时间复杂度的具体步骤是:
⑴ 找出算法中的基本语句;
算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
⑵ 计算基本语句的执行次数的数量级;
只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。
⑶ 用大Ο记号表示算法的时间性能。
将基本语句执行次数的数量级放入大Ο记号中。
如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体,如果算法中包含并列的循环,则将并列循环的时间复杂度相加
空间复杂度:空间复杂度是指执行这个算法所需要的内存空间
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度;计算机上占用内存包括
程序代码所占用的空间,输入输出数据所占用的空间,辅助变量所占用的空间这三个方面。
程序代码所占用的空间取决于算法本身的长短,输入输出数据所占用的空间取决于要解决的问题,
是通过参数表调用函数传递而来,只有辅助变量是算法运行过程中临时占用的存储空间,与空间复杂度相关;
通常来说,只要算法不涉及到动态分配的空间,以及递归、栈所需的空间,空间复杂度通常为0(1);
1.冒泡排序
@Test public void test1() { int [] nums={10,5,8,2,3,50,5000,500,30,65,98}; Long startTime = System.currentTimeMillis(); System.out.println("开始时间 " +System.currentTimeMillis()); //Arrays.sort(nums);该方法是java自带的方法 在包 java.util中 for(int i = 0; i< nums.length-1;i++){ for(int j = 0; j< nums.length - i -1; j++){ int temp = 0; if(nums[j]>nums[j+1]){ temp = nums[j]; nums[j] = nums[j+1]; nums[j+1] = temp; } } } System.out.println("结束时间 " +System.currentTimeMillis()); Long endTime = System.currentTimeMillis(); System.out.println("用时 " + Long.valueOf(endTime-startTime)); for (int i : nums) { System.out.println(i); } }2.三角行
@Testpublic void Demo(){ for(int i = 1 ; i <=5 ; i++){ for(int j = 1; j <= 5 - i; j++){ System.out.print(" "); } for(int j = 1; j <= 2*i-1; j++){ System.out.print("*"); } System.out.println(); }}3,插入排序(就是玩扑克牌时的排序(升序))小的排序
/** * 升序。。。。插入排序,从第一个元素开始,改元素可以认为已经被排序,取出下一个元素,在已经排序的元素序列中从后向前扫描 * 如果该元素(已排序)大于新元素,将该元素移到下一个位置 * 平均时间复杂度O(n^2) * 空间复杂度O(1) * 稳定 */@Test public void sort1(){ int[] nums={6,5,3,1,8,7,2,4}; int j ; for(int i = 1; i < nums.length; i++){ j = i; int target = nums[i];//抓到的牌(其实是第二张牌) while(j>0&&target<nums[j-1]){//加入手里只有一张牌,则默认该牌就是排序好的(最大的)。nums[j-1]是手里的牌,如果手里的牌比抓到的牌大(手里的牌是按照从左到右依次减小排序的) nums[j] = nums[j-1];//交换位置 j--;//变成手里第二大的牌。 } nums[j] = target;//确定抓到牌的位置并插入 } for(int i:nums){ System.out.print(i); }}
4.Java自带的排序 Arrays.sort(array);对于基础类型,底层使用的是快速排序。对于非基础类型,底层使用归并排序。这是因为考虑到算法的稳定性,对于基础类型,相同值是无差别的,排序前后相同值的相对位置并不重要,所以选择较为高效的选择排序对于非基础类排序,排序前后相等实例的相对位置不易改变,所以选择较为稳定的归并排序。5.快速排序快速排序是使用分治策略来吧一个序列分为两个子序列从元素中挑出一个元素作为基准,把比基准小的元素放到元素前面,比基准大的元素放到元素后面,相同的可以放到任意一边。这就是分区操作然后再把基准前后的元素再作为一个待排序的序列,同样进行快速排序,挑选基准,以此类推,直到排好序。
import java.util.Arrays;/** * 分类 ------------ 内部比较排序 * 数据结构 --------- 数组 * 最差时间复杂度 ---- 每次选取的基准都是最大(或最小)的元素,导致每次只划分出了一个分区,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2) * 最优时间复杂度 ---- 每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,只需要logn次划分就能结束递归,时间复杂度为O(nlogn) * 平均时间复杂度 ---- O(nlogn) * 所需辅助空间 ------ 主要是递归造成的栈空间的使用(用来保存left和right等局部变量),取决于递归树的深度,一般为O(logn),最差为O(n) * 稳定性 ---------- 不稳定 */public class Demo { public static void sort(int a[], int low, int hight) { int i, j, index; if (low > hight) { return; } i = low; j = hight; index = a[i]; // 用子表的第一个记录做基准,但是注意,这个基准并不占位。 while (i < j) { // 从表的两端交替向中间扫描 //先从右往左遍历开始 while (i < j && a[j] >= index){ j--; } if (i < j) a[i++] = a[j];// 用比基准小的记录替换低位记录 //先从右往左遍历结束 while (i < j && a[i] < index){ i++; } if (i < j) // 用比基准大的记录替换高位记录 a[j--] = a[i]; } System.out.print(i); System.out.println(Arrays.toString(a)); a[i] = index;// 将基准数值替换回 a[i] sort(a, low, i - 1); // 对低子表进行递归排序 sort(a, i + 1, hight); // 对高子表进行递归排序 } public static void quickSort(int a[]) { sort(a, 0, a.length - 1); } public static void main(String[] args) { int a[] = { 49, 38, 65, 97, 76, 13, 27, 50 }; quickSort(a); System.out.println(Arrays.toString(a)); }}
结果:
i = 3[27, 38, 13, 97, 76, 97, 65, 50]
i = 1[13, 38, 38, 49, 76, 97, 65, 50]
i = 0[13, 27, 38, 49, 76, 97, 65, 50]
i = 2[13, 27, 38, 49, 76, 97, 65, 50]
i = 6[13, 27, 38, 49, 50, 65, 65, 97]
i = 4[13, 27, 38, 49, 50, 65, 76, 97]
i = 5[13, 27, 38, 49, 50, 65, 76, 97]
i = 7[13, 27, 38, 49, 50, 65, 76, 97]
[13, 27, 38, 49, 50, 65, 76, 97]
6,归并排序归并排序分为递归实现和非递归实现,就是将大问题分割成若干个小问题解决,然后用小问题的答案解决大问题,这个就是递归。非递归实现的归并序列首先进行两辆归并,然后四四归并,八八归并,一直下去直到归并整个数组。归并排序主要依赖于归并操作,归并操作主要是指将两个已经排序的序列归并成一个序列的操作。操作步骤如下
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针到达序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
import java.util.Arrays;/** * 分类 -------------- 内部比较排序 * 数据结构 ---------- 数组 * 最差时间复杂度 ---- O(nlogn) * 最优时间复杂度 ---- O(nlogn) * 平均时间复杂度 ---- O(nlogn) * 所需辅助空间 ------ O(n) * 稳定性 ------------ 稳定 */public class Demo { public static void merge(int[] a, 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 (a[i] < a[j]) { temp[k++] = a[i++]; } else { temp[k++] = a[j++]; } } // 把左边剩余的数移入数组 while (i <= mid) { temp[k++] = a[i++]; } // 把右边边剩余的数移入数组 while (j <= high) { temp[k++] = a[j++]; } // 把新数组中的数覆盖nums数组 for (int k2 = 0; k2 < temp.length; k2++) { a[k2 + low] = temp[k2]; } } public static void mergeSort(int[] a, int low, int high) { int mid = (low + high) / 2; if (low < high) { // 左边 mergeSort(a, low, mid); // 右边 mergeSort(a, mid + 1, high); // 左右归并 merge(a, low, mid, high); System.out.println(Arrays.toString(a)); } } public static void main(String[] args) { int a[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; mergeSort(a, 0, a.length - 1); System.out.println("排序结果:" + Arrays.toString(a)); }}运行结果
[5, 6, 3, 1, 8, 7, 2, 4]//5,6一组。3,1一组。8,7一组。2,4一组
[5, 6, 1, 3, 8, 7, 2, 4]//5,6排序好了。1,3排序好了。8,7.2,4先待定
[1, 3, 5, 6, 8, 7, 2, 4]//13作为一个整体,56作为一个整体,开始排序[1, 3, 5, 6]
[1, 3, 5, 6, 7, 8, 2, 4]//开始排序后面2组
[1, 3, 5, 6, 7, 8, 2, 4]//开始排序后面2组
[1, 3, 5, 6, 2, 4, 7, 8]//[7,8][2,4]分别作为一个整体进行排序成[2,4,7,8]的整体
[1, 2, 3, 4, 5, 6, 7, 8]//[1, 3, 5, 6]和[2,4,7,8]分别作为一个整体进行排序
排序结果:[1, 2, 3, 4, 5, 6, 7, 8]
7.选择排序
工作原理:初始时在序列中找到最小后者最大元素。放到排序的气势位置作为已排序序列;然后再从剩下的元素中继续寻找
最小或者最大元素,放到已排序的尾部,一次类推,知道排序完成。
代码实现:
/** * 选择排序,稳定的排序算法 */@Testpublic void sort2(){ int[] nums = {6,5,3,1,8,7,2,4}; for(int i = 0; i< nums.length-1;i++){ int k = i;//k的值主要是记录最小值的下标,现在默认第一位的数据是最小值(其实这个k值其实就是一个中介,就是暂且将最小值放到k里,最后还是要给i的) for(int j = k + 1; j < nums.length; j++){ if(nums[j]<nums[k]){//最小值和其他的值相比较,如果比最小值还小 k = j;//将最小值的下标给k。知道找到整个待排序序列的最小值的下标。 } } //将最小的值赋给nums[i]记录。 if(i!=k){ int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } } for(int n : nums){ System.out.print(n); }}