• 排序算法


    交换排序

    冒泡

    思路:
    1、外层循环是遍历每个元素,每次都放置好一个元素。
    2、内层循环将剩余序列中所有元素两两比较,将较大的放在后面。
    3、重复第二步,直到只剩下一个数。
    如果有n个元素,则第一次循环进行n-1次比较,第二次循环进行n-2次,…………,第n-j次循环过后,会按大小顺序排出j个较大的数。

    写成代码:
    1、设置循环次数。
    2、设置开始比较的位数,和结束的位数。
    3、两两比较,将最小的放到前面去。
    4、重复2、3步,直到循环次数完毕。

    	/**
    	 * 最优的情况也就是开始就已经排序好序了,这是循环n-1次每次没有交换动作而退出,则时间花销为:n(n-1)/2;所以最优的情况时间复杂度为:O( n^2 ); 
             * 最坏是完全逆序(其中比上面最优的情况所花的时间就是在于交换元素的三个步骤),即需要加上交换移动次数
    	 * 3n(n-1)/2次,所以时间复杂度也是O(N^2)。
    	 * 
    	 * 该算法中空间复杂度一般是看交换元素时所使用的辅助空间,即交换元素时那个临时变量所占的内存空间。
    	 * 最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为:0;
    	 * 最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为:O(n); 
    	 * 平均的空间复杂度为:O(1);
    	 * 
    	 * PS:想想使最优的时间复杂度为:O(n)的写法?
    	 * https://blog.csdn.net/yuzhihui_no1/article/details/44339711
    	 */
    	@Test
    	public void bubble(){
    		int[] a = {3,5,12,4,1,4,1,8};
    		for(int i=0; i<a.length-1; i++){
    			for(int j=0; j<a.length-1-i; j++){
    				if(a[j] > a[j+1]){
    		            ArrUtil.swap(a, j+1, j);
    				}
    				System.out.println(Arrays.toString(a));
    			}
    		}
    	}
    

    快速排序

    思路:
    1、选择第一个数为p,称为 "基准"(pivot);
    2、指针i一步一步从左往右寻找大于基数p的数字,指针j从右往左寻找小于基数p的数字,i、j都找到后将两个位置的数字进行交换,然后继续前进比较、交换,直到i>=j结束循环,然后将基准数 p和i(或j)位置的数进行交换,即完成一趟快排,这个称为分区(partition)操作;
    此时数组被划分为小于基数p、基数p、大于基数p的三部分;
    3、递归地(recursive)把小于基数p的子数列和大于基数p的子数列按上面的步骤排序,直到不能再递归。
    代码实现的写法也有好多种
    代码1
    来自维基的,分割写法有点不一样,访问要梯子

    选择排序

    简单选择排序

    常用于取序列中最大最小的几个数时。(如果每次比较都交换,那么就是冒泡;如果每次比较完一个循环再交换,就是简单选择排序。)
    1、遍历整个序列,每次将选出来的最小的数放在前面。
    2、遍历剩下的序列,依次与哨兵进行比较,选出最小的数。
    3、重复第二步,直到只剩下一个数。

    写成代码:
    1、首先确定循环次数,并且记住当前数字和当前位置。
    2、将当前位置后面所有的数与当前数字进行对比,小数赋值给key并记住小数的位置。
    3、比对完成后,将最小的值与第一个数的值交换。
    4、重复2、3步。

    	/**
    	 * 选择排序的时间复杂度:简单选择排序的比较次数与序列的初始排序无关。
    	 * 假设待排序的序列有 N 个元素,则比较次数永远都是N (N - 1)/2。
    	 * 而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0,最多会发生 N - 1次交换移动。
    	 * 所以,综上,简单排序的时间复杂度为 O(N^2)。
    	 * 
    	 * 空间复杂度为:O(1);与冒泡同理。
    	 * 
    	 * 虽然选择排序和冒泡排序的时间复杂度一样,但实际上,选择排序进行的交换操作很少。
    	 *
    	 * 缺点是不稳定:相同的两个数,位置可能会变。
    	 */
    	@Test
    	public void selectSorted() {
    		int[] a = { 3, 5, 1, 6, 8, 2, 5 };
    
    		for (int i = 0; i < a.length - 1; i++) {
    			int min = i;
    			for (int j = i + 1; j < a.length; j++) {
    				if (a[min] > a[j]) {
    					min = j;
    				}
    			}
    			if (min != i) {
    				ArrUtil.swap(a, i, min);
    			}
    		}
    		System.out.println(Arrays.toString(a));
    	}
    

    堆排序

    对简单选择排序的优化。
    1、将序列构建成大顶堆。
    2、将根节点与最后一个节点交换,然后断开最后一个节点。
    3、重复第一、二步,直到所有节点断开。

    	/**
    	 * 堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法。一般升序采用大顶堆,降序采用小顶堆
    	 * 把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
    	 * 
    	 *
    	 * 缺点是不稳定:相同的两个数,位置可能会变。
    	 */
    	@Test
    	public void heapSort() {
    		int[] a = { 4,9,7,6,5,4,2,8,6,7,9,10,12};
    		
    		// 循环建堆
    		for (int i = 0; i < a.length - 1; i++) {
    			// 建堆
    //			buildMaxHeap(a, a.length - 1 - i);
    			buildMaxHeap1(a, a.length - 1 - i);
    			// 交换堆顶和最后一个元素
    			ArrUtil.swap(a, 0, a.length - 1 - i);
    			System.out.println(Arrays.toString(a));
    		}
    	}
    	
        //从第一个非叶子结点从下至上,从右至左建大顶堆
        private void buildMaxHeap1(int[] data, int lastIndex) {
            // TODO Auto-generated method stub  
            //从lastIndex处节点(最后一个节点)的父节点开始  
            for(int i=(lastIndex-1)/2;i>=0;i--){
                //k保存正在判断的节点  
                int k=i;
                //如果当前k节点的子节点存在  
                while(k*2+1<=lastIndex){
                    //k节点的左子节点的索引  
                    int biggerIndex=2*k+1;
                    //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在  
                    if(biggerIndex<lastIndex){
                        //若果右子节点的值较大  
                        if(data[biggerIndex]<data[biggerIndex+1]){
                            //biggerIndex总是记录较大子节点的索引  
                            biggerIndex++;
                        }
                    }
                    //如果k节点的值小于其较大的子节点的值  
                    if(data[k]<data[biggerIndex]){
                        //交换他们  
                    	ArrUtil.swap(data,k,biggerIndex);
                        //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值  
                        k=biggerIndex;
                    }else{
                        break;
                    }
                }
            }
        }
    
    

    插入排序

    直接插入排序

    把新的数据插入到已经排好的数据列中。
    1、将第一个数和第二个数排序,然后构成一个有序序列
    2、将第三个数插入进去,构成一个新的有序序列。
    3、对第四个数、第五个数……直到最后一个数,重复第二步。
    写成代码:
    1、首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。
    2、设定插入数和得到已经排好序列的最后一个数的位数。insertNum和j=i-1。
    3、从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。
    4、将当前数放置到空着的位置,即j+1。

    希尔排序

    对于直接插入排序问题,数据量巨大时。
    1、将数的个数设为n,取奇数k=n/2,将下标差值为k的书分为一组,构成有序序列。
    2、再取k=k/2 ,将下标差值为k的书分为一组,构成有序序列。
    3、重复第二步,直到k=1执行简单插入排序。
    写成代码:
    1、首先确定分的组数。
    2、然后对组中元素进行插入排序。
    3、然后将length/2,重复1,2步,直到length=0为止。

    归并排序

    速度仅次于快排,内存少的时候使用,可以进行并行计算的时候使用。
    1、选择相邻两个数组成一个有序序列。
    2、选择相邻的两个有序序列组成一个有序序列。
    3、重复第二步,直到全部组成一个有序序列。

    分配排序

    基数排序

    用于大量数,很长的数进行排序时。
    1、将所有的数的个位数取出,按照个位数进行排序,构成一个序列。
    2、将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。

  • 相关阅读:
    后端程序员之路 6、Python fabric
    后端程序员之路 5、.conf、libconfig
    后端程序员之路 4、一种monitor的做法
    后端程序员之路 3、fastcgi、fastcgi++
    后端程序员之路 2、nginx、php
    后端程序员之路 1、linux、centos
    F#之旅9
    F#之旅8
    F#之旅7
    F#之旅6
  • 原文地址:https://www.cnblogs.com/cashew/p/10512279.html
Copyright © 2020-2023  润新知