排序算法总结

 

排序算法的思想

什么是排序算法的稳定性?

    指两个相同的元素,在多次排序过程中,排序先后的相对位置是否会发生变化。主要用在排序时有多个排序规则的情况。【一般情况下认为,如果两个元素进行相邻的交换,可以认为该算法是稳定的】

1.选择排序

思想:nums[i],遍历数组,第一次从数组中找到最小的元素,与数组第一个元素进行交换;第二次遍历数组查找第二小元素,与数组第二个元素进行交换;总之,每次选择未排序最小元素与未排序元素的首部进行交换

    稳定性:由于每次选择的最小元素都与未排序的首部元素进行交换,跨距离交换,有可能会破坏元素之间的相对位置,因此不稳定    

时间复杂度:n个元素;要查找n次最小值进行交换,o(n*2)

      public void sort(T[] nums) {
		//1.求出元素大小
		int N=nums.length;		
		//2.循环遍历
		for(int i=0;i<N-1;i++) {
			//2.1定义最小元素的索引
			int min=i;
			//2.2 开始遍历查找最小元素
			for(int j=i+1;j<N;j++) {
				if(less(nums[j],nums[min])) {
					min=j;
				}
			}
			swap(nums,i,min);
		}
	}

2.冒泡排序

    每次比较相邻逆序的元素,进行交换,第一轮结束后最大值会在最右侧;相邻排序;稳定算法

    在第一轮循环中,如果没有排序没说明数组已经是有序的(优化冒泡排序)

    平均:o(n*2)

    最好:N-1次比较,0次交换

            public void sort(T[] nums) {
		int N=nums.length;
		boolean hasSorted=false;
		//	每次都把最大元素移到最后一位未排序元素,有N个元素需要排序;
		for(int i=N-1;i>0 && !hasSorted;i--) {
			hasSorted=true;
			for(int j=0;j<i;j++) {
				if(less(nums[j+1],nums[j])) {
					hasSorted=false;
					swap(nums,j,j+1);					
				}
			}
		}

3.插入排序

     每次都把新元素插入到左侧已经排序好的数组中,插入排序的次数与数组中相邻逆序数组个数有关;每次调整一组逆序数组,每次都是相邻元素交换,具有稳定性    

     复杂度:最好:正向有序,只需要比较N次,不需要移动

                   最差:逆向有序,每一个元素比较n次,有n个元素o(n*2)

 

平均:o(n^2)

              public void sort(T[] nums) {
		int N=nums.length;	
		for(int i=1;i<N;i++) {
			for(int j=i;j>0 && less(nums[j],nums[j-1]);j--) {
				swap(nums,j-1,j);
			}
		}

4.希尔排序

改进的插入排序;交换不相邻的元素,使逆序数量减少次数大于1;使用间隔h进行交换;不断减小h,最后令h=1 

    public void sort(T[] nums) {
		int N=nums.length;	
		int h=1;		
		while(h<N/3) {
			h= 3*h+1;
		}		
		while(h>0) {
			for(int i=h;i<N;i++) {
				for(int j=i;j>= h && less(nums[j],nums[j-h]);j-=h) {
					swap(nums,j-h,j);
				}
			}
			h = h/3;
		}	
	}

5.快速排序

    选择切分元素;使该元素左边的元素都小于它,右边的元素都大于他,然后开始递归排序左边、右边

    类似于二分法思想,因此时间复杂度o(nlogn)

    性能分析:

    快排是原地排序,不需要辅助数组,但是递归调用需要辅助栈

    最好的情况是每次都能将数组对半分,这样递归调用次数最少,复杂度O(NlogN)

    最坏情况:第一次从最小的元素开始切分,第二次从第二小;需要比较N^2/2次;

    为了防止数组一开始就是有序的,可以先将数组打乱 

public class QuickSort {

         public void sort(T[] nums) {
		shuffle(nums);
		sort(nums,0,nums.length-1);
		
	}

	private void sort(T[] nums, int l, int h) {
		while(h<=l) {
			return;
		}
		//j表示第一次切分元素之后,数组中的第j大元素;左边的数都小于他;右边都大于他
		int j=partition(nums,l,h);
		sort(nums,l,j-1);
		sort(nums,j+1,h);
		
	}

	//一次快速排序
	private int partition(T[] nums, int l, int h) {
		int i=l,j=h+1;
		T v=nums[l];//切分元素
		while(true) {
			while(less(nums[++i],v) && i!=h);
			while(less(v,nums[--j]) && j!=l);
			
			//此时,i<j;且nums[i]>v;nums[j]<v;进行交换
			if(i>=j) {
				break;
			}
			swap(nums,i,j);
		}
		swap(nums,l,j);
		return j;
	}

	private void shuffle(T[] nums) {
		List<Comparable> list=Arrays.asList(nums);
		Collections.shuffle(list);
		list.toArray(nums);
		
	}
	
	/*基于切分的快速选择算法 找到排序后数组中的第K个元素 nums[k] */
	public T select(T[] nums,int k) {
		int l=0,h=nums.length-1;
		while(h>l) {
			int j=partition(nums, l, h);
			
			if(j==k) {
				return nums[k];
			}else if(j>k) {
				h=j-1;
			}else {
				l=j+1;
			}
		}		
		return nums[k];
	}
}

 

 

6.归并排序

    将数组分成两部分,分别排序,然后将两个有序的数组进行归并   

    元素融合过程中;注意索引越界

         protected T[] aux;
	
	protected void merge(T[] nums,int l,int m,int h ) {
		// i 表示左边数组要归并的元素索引  j 表示右边
		int i=l,j=m+1;
		
		//	复制数组到辅助栈
		for(int k=l;k<=h;k++) {
			aux[k]=nums[k];
		}
		
		//	开始归并
		for(int k=l;k<=h;k++) {		
			//当左边元素索引大于中间元素索引,表示左边元素全部已经排好序在数组中
			if(i>m) {
				nums[k]=aux[j++];
			}else if(j>h) {
				nums[k]=aux[i++];
			}else if(aux[i].compareTo(nums[j]) <= 0 ) {
				nums[k]=aux[i++];
			}else {
				nums[k]=aux[j++];
			}
		}

	}

	@Override
	public void sort(T[] nums) {
		aux=(T[]) new Comparable[nums.length];
		sort(nums,0,nums.length-1);
	}

	private void sort(T[] nums, int l, int h) {		
		if(h<=l) {
			return;
		}		
		int mid=l+(h-l)/2;
		sort(nums,l,mid);
		sort(nums,mid+1,h);
		merge(nums,l,mid,h);
	}

 

7.堆排序

    堆中某个节点的值总是大于等于其子节点的值;并且是一颗完全二叉树

    可以用数组来表示, 位置K的节点的父节点k/2-1;子节点 2k+1,2k+2;

    数组索引从0开始

  •   上浮和下沉都是调节数组元素为堆的操作

    上浮:在堆中,当子节点比父节点大,进行上浮操作,如果比更新之后父节点还大,继续上浮  k>0

    下沉:在堆中,当子节点比父节点大,就要进行下沉操作,并且选择左右子节点最大的那个进行交换下沉   2k+2<N

    插入元素:将新元素放在数组末尾,然后上浮

    删除最大元素:从数组顶端删除最大元素,并将数组最后一个元素放到顶端,下沉

堆排序:

    构建堆:首先要把一个无序数组构建成一个符合堆结构的数组,可以选择从左到右上浮一个个元素,可以选择下沉,如果一个节点的两个节点已经是堆有序,那么进行下沉操作可以使这个节点为根点的堆有序

    因为叶子节点不需要下沉,只需要操作一半的元素。排序时,可以把堆中的第一个元素一直与后面的元素进行交换,然后下沉新元素,重新构造堆 

    分析:一个堆的高度为logN,因此在堆中插入和删除元素的复杂度都为logN

           对于堆排序:由于要对N个节点进行操作,因此复杂度o(NlogN)

           原地排序,没有利用额外空间   

/**
     * 2.堆排序:交换堆顶元素和堆树中最后一个元素;不删除;然后把第一个元素下沉到合适的位置;
     * 在交换堆顶元素和堆数组倒数第二的元素...以此类推;最后数组就是一个递增序列
     * 最大堆 排序之后  从小到大
     * @param array
     */
    public void sort(int[] array){
        int N=array.length;
        int K=N-1;
        /**
         * array数组第0个元素开始
         * 87 69 12 45 46 
         * 12 45 46 69 87 
         */
        //1. 首先构建一个堆数组  使用下沉一半元素  从n/2-1下沉元素到合适的位置;叶子节点占了一般半元素;不需要下沉   
        //父节点:k/2-1  子节点  :  2*k+1  2*k+2
        // 0   1  2    3     4        5
        for(int i=N/2-1;i>=0;i--){
            sink(array,i,N);
        }
        for(int num:array){
            System.out.print(num+" ");
        }
        System.out.println();
        //2 开始进行堆排序 数组
      while(K>0){
          //交换堆顶元素和堆中要排序的最后一个元素         swap(0 k)
          swap(array,0,K--);
          // 把第一个元素下沉到合适的位置()
          sink(array,0,K+1);       length   比索引位置多一位
      }
    }

    /**
     * 下沉元素
     * @param array
     * @param k
     * @param length
     */
    private void sink(int[] array, int k, int length) {
        while( 2*k +1 <length){
            int j=2*k +1;
            if((j+1)<length && array[j+1]> array[j]){
                j++;
            }
            if(array[k]> array[j]){
                break;
            }
            swap(array,k,j);
            k=j;
        }
    }

    private void swap(int[] array, int i, int j){
        int temp=array[i];
        array[i]=array[j];
        array[j]=temp;
    }

  https://www.cnblogs.com/onepixel/articles/7674659.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值