常用排序算法


个人觉得会写排序算法并没啥用,如果只是单纯使用排序算法,一般都是直接调用包写好的算法,绝对要比你写的好,而且一般包里面的排序算法都可能数据情况,使用不同的排序算法, 主要是学习各种排序算法中的思想,所以本文除了写各种排序算法,还会写如何用排序算法来解决实际问题。

排序算法总结

在这里插入图片描述
先解释下排序的稳定性:
在这里插入图片描述如果排序前的第一个8 和第二个8 在排序后,还是保持这个顺序,就说明排序是稳定,否则就是不稳定的(下面会具体解释每一个算法是否稳定)。那么排序的稳定性有什么用呢?

如果一个类是下面的3个属性

 class Student{
	private String name;   //名称
	private int class_id;   //班级id
	private int score;   //分数
}

如果现在已经将对象根据score分数经行排序了,然后又使用class_id班级id进行排序,这个时候相同班级的会被排序到一起来,如果排序算法是稳点的,这个时候每一个班级中的人的分数还是按照有序排列。


各种排序算法

选择排序,冒泡排序

冒泡排序执行流程:

  1. 从头开始比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置 ,(执行完一轮后,最末尾就是那个元素的最大的元素)
  2. 忽略1中曾经找到的最大元素,重复执行步骤一,直到全部元素有序
//冒泡排序
public void bubSort(int nums[]){
  if(nums==null  || nums.length<2)
     return;
   for(int end=nums.length-1;end>0;end--){
   //每一轮循环将没排序的数据中的最大值放在最后一个位置
   	 for(int i=1;i<=end;i++){
   	 		if(a[i-1]>a[i]){
   	 		  //交换这两个数据,将大的数据放在向后面放
   	 		  swap(nums,i-1,i);
   	 		}
   	 }
   }
}

冒泡排序优化一:如果序列已经完全有序,可以提前终止冒泡排序

   //冒泡排序
public void bubSort(int nums[]){
  if(nums==null  || nums.length<2)
     return;
   for(int end=nums.length-1;end>0;end--){
   //每一轮循环将没排序的数据中的最大值放在最后一个位置
   boolean sorted=true;
   	 for(int i=1;i<=end;i++){
   	 		if(a[i-1]>a[i]){
   	 		  //交换这两个数据,将大的数据放在向后面放
   	 		  sorted=true;
   	 		  swap(nums,i-1,i);
   	 		}
   	 }
   	 if(sorted)
   	 break;
   }
}

冒泡排序优化二:如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数

   //冒泡排序
public void bubSort(int nums[]){
  if(nums==null  || nums.length<2)
     return;
   for(int end=nums.length-1;end>0;end--){
   //每一轮循环将没排序的数据中的最大值放在最后一个位置
     int sortedIndex=1;
   	 for(int i=1;i<=end;i++){
   	 		if(a[i-1]>a[i]){
   	 		  //交换这两个数据,将大的数据放在向后面放
   	 		 sortedIndex=i;
   	 		  swap(nums,i-1,i);
   	 		}
   	 }
   	 end =sortedIndex;
   }
}

时间复杂度: 冒泡排序中,如果某一轮的交换没有交换数据,说明数据是已经排序好了的,最好的情况就是数据本来就是有序的,所以最好的情况就是将数据遍历一遍,时间复杂度也就是O(n);

稳定性:冒泡可以做到稳定性,当然你也可以将排序算法写成不稳定的,也就是当前一个数据后一个数据相等的时候,你也让这两个数据经行交换。

选择排序

  1. 从序列中找出最小的那个元素,然后与数组最开始元素交换位置(执行完一轮后,第一个开始的那个元素就是最大的元素)
  2. 忽略1中曾经找到的最小元素,重复执行步骤1
//选择排序
public void SelectSort(int nums[]){
  if(nums==null  || nums.length<2)
     return;
  for(int i=0;i<nums.length-1;i++){
       int min_index=i;
       for(int j=i+1;j<arr.length;j++){
         	min_index=nums[min_index]>nums[j]?j:min_index;
       }
       if(i!=min_index)
       swap(nums,min_index,i);
 }
		
}
  • 选择排序的交换次数要远远少于冒泡排序,平均性能优于冒泡排序
  • 选择排序,不可能会稳定
    在这里插入图片描述
    所以选择排序不管怎么做,都不能满足稳定性

插入排序

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动 。

执行流程:

  • 在执行过程中,插入排序会将序列分为2部分(头部是排好序的,尾部是待排序的)
  • 从头开始扫描每一个元素(每当扫描到一个元素,就将它插入到合适的位置,使得头部数据依然保持有序)

插入排序也比较简单,但是不代表并不使用它,一般当数据个数小于60的时候,使用它和使用O(nlgn)的区别并不大,而且它的常数项比较低,而且是稳定的

    public void insertSort(int [] nums){
       if(nums==null  || nums.length<2)
        return;
     for(int i=1;i<nums.length;i++){
         int temp=nums[i];
		for(int j=i-1;j>=0  && temp<nums[i];j--){
				nums[j+1]=nums[j];
		}
		nums[j+1]=temp;
   }
   }
  • 插入排序的时间复杂度与逆序对的数量成正比关系
    逆序对的数量越多,插入排序的时间复杂度越高
    插入排序 可以使用二分搜索优化 :使用二分搜索后,只是减少了比较次数,插入排序的时间复杂度依然不变
 private static void insertionSort(int []arr){
   for(int i=1;i<arr.length;i++){
     insert(arr,i,search(arr,i));
   }
}
private static void insert(int [] arr,int i,int search){
   int temp=arr[i];
   for(int j=i;j>search;j--){
      arr[i]=arr[i-1];
   }
   arr[search]=temp;
}

 //返回第一个大于等于 arr[i]的位置的坐标
private static int search(int []arr,int i){
   int begin=0;
   int end=i;
   	while(begin<end){
      int mid=begin+(end-begin)>>1;
      if(arr[i]<arr[mid]){
        end=mid;
      }else{
        begin=mid+1;
      }
     
    }
	return begin;
}


时间复杂度:O(n^2) ,如果数据本来就是有序排列的,也就是第二层的循环不会进行,这个时候最好的时间的复杂度是O(n)
稳定的,和冒牌排序一个道理。


归并排序

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

「归并排序」是分治思想的典型应用,它包含这样三个步骤:

分解: 待排序的区间为 [l, r] ,令 m =(l + r)/2 ,我们把 [l, r]分成 [l, m] 和 [m + 1, r]
解决: 使用归并排序递归地排序两个子序列
合并: 把两个已经排好序的子序列 [l, m] 和 [m + 1, r]合并起来

在这里插入图片描述

   public void  void mergeSort(int [] arr){
      if(arr==null || arr.length<2)
      		return;
      		//排序数组
      	sortProcess(arr,0,arr.length-1);
   }
 public void sortProcess(int [] arr,int L,int R){
    if(L==R) 
    	return;
    int mid=L+((R-L)>>1);
    //将数组左边排序     
    sortProcess(arr,L,mid);
    //将数组右边排序
    sortProcess(arr,mid+1,R);
    //通过排序好的左边,右边,将整个数组排序好
    merge(arr,L,mid,R);
 }
private void merge(int [] arr,int l,int mid,int r){
		int help[]=new int[r-l+1];
		int i=0;
		int p1=l;
		int p2=mid+1;
		//将数组中左右部分中较小的部分赋值给Help
		while(p1<=mid  && p2<=r){
				help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
		}
		//如果左边部分没有遍历完,将左边部分中剩下的给Help就好了
		while(p1<=mid){
		  help[i++]=arr[p1++];
		}
		while(p2<=r){
		help[i++]=arr[p2++];
       }
	for(int j=0;j<help.length;j++)
	   arr[l+j]=help[j];
}

时间复杂度为 O(nlgn) ,这个使用主定理可以算出
空间复杂度为O(n),因为使用外部排序,因为归并排序需要用到一个临时数组。
稳定性:完全在排序的时候做到稳定性


先来个简单题目题目leetcode 88 合并两个有序数组
这个题目,可以使用归并排序中的merge的过程。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
            int l=0;
            int r=0;
            int i=0;

            int [] help=new int[m+n];
            while(l<m && r<n){
                help[i++]=nums1[l] <= nums2[r]? nums1[l++] :nums2[r++];
            }
            while(l<m){
                help[i++]=nums1[l++];
            }
            while(r<n){
                help[i++]=nums2[r++];
            }
        for( i=0;i<help.length;i++)
            nums1[i]=help[i];
    }
}

题目leetcode 剑指offer 51 数组中的逆序对

思路:在归并排序中merge的过程中,会比较数组左边部分和右边部分的大小,所以可以计算出逆序对
在这里插入图片描述
同理可以得到,以后的逆序对。

class Solution {
        private int count=0;
    public int reversePairs(int[] nums) {
        if(nums==null || nums.length<2)
            return 0;
           process(nums,0,nums.length-1);
           return  count;
    }

        private void process(int[] nums, int L, int R) {
              if(L==R)
                  return;
              int mid=L+((R-L)>>1);
              //对左边部分进行排序
               process(nums,L,mid);
               //对右边部分经行排序   
               process(nums,mid+1,R);
               merge(nums,L,mid,R);
        }
		//合并左边和右边的部分
        private void merge(int[] nums, int l, int mid, int r) {
                 int []helper=new int[r-l+1];
                int p1=l;
                int p2=mid+1;
                int i=0;
                while(p1<=mid && p2<= r){
                    if(nums[p1]<=nums[p2]){
                        helper[i++]=nums[p1++];
                    }else{
                        helper[i++]=nums[p2++];
                        //当找到右边的值小于左边的值时候,看左边有多少数字是大于这个数字的
                        count+=(mid-p1+1);
                    }
                }
                while(p1<=mid){
                helper[i++]=nums[p1++];
            }
            while(p2<=r){
                helper[i++]=nums[p2++];
            }
            for (int j = 0; j <helper.length ; j++) {
                nums[l+j]=helper[j];
            }
        }
    }

堆排序

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序的基本思想:将待排序的数组构成一个大顶堆.这个时候数组的最大值就是大顶堆的根节点。将它和末尾的元素经行交换,这个时候末尾就是最大值了,然后将剩下的n-1个元素重新构造成为一个大顶堆,这样会得到n-1个元素中最的最大值,如此就能得到一个有序数组了。

数组28, 95, 14, 8, 17, 53, 5, 26, 87, 48对应的大顶堆,小顶堆如下
也就是满足大顶堆: arr[i]>= arr[2i+1] && arr[i]>=arr[2i+2]
小顶堆:arr[i]<=arr[2i+1] &&arr[i] <=arr[2i+2]
在这里插入图片描述
在这里插入图片描述

如数组 4 ,2,10,5,8的排序过程

  • 首先建立大顶堆,下面的二叉树是逻辑中的,实际上在物理中是使用数组来存储和交换的
  1. 最开始只有一个节点4
    在这里插入图片描述
  2. 插入2,2比4小,所有是4的子树
    在这里插入图片描述
  3. 插入10,10比4大,交换4和10,这个时候数组是
    10,2,4,5,8

在这里插入图片描述
4. 插入5 ,5比2大,交换5和2
现在数组是 10,5,4,2,8
在这里插入图片描述
5. 插入8 ,8比5大,交换8和5
现在数组是10,8,4,2,5
在这里插入图片描述
可以知道现在数组的最大值在堆顶,而且满足父节点不小于任何一个子节点

然后将数组第一个位置和最后一个位置的数字交换,数变成了[5,8,4,2,10] ,最大值放在组后面,对应的逻辑堆图变成了如下,所以需要将树5,8,4,2堆化,也就是将5和左子树和右子树中最大的值比较,如果有比5大的子树,即交换位置

在这里插入图片描述
结果为
在这里插入图片描述
其他的同理这个过程,最后所有的数组会变得有序化

import java.util.Arrays;

/**
 * Created by chengxiao on 2016/12/17.
 * 堆排序demo
 */
public class HeapSort {
    public static void main(String []args){
        int []arr = {9,8,7,6,5,4,3,2,1};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void sort(int []arr){
        //1.构建大顶堆
        for(int i=arr.length/2-1;i>=0;i--){
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr,i,arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for(int j=arr.length-1;j>0;j--){
            swap(arr,0,j);//将堆顶元素与末尾元素进行交换
            adjustHeap(arr,0,j);//重新对堆进行调整
        }

    }

    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     * @param arr
     * @param i
     * @param length
     */
    public static void adjustHeap(int []arr,int i,int length){
        int temp = arr[i];//先取出当前元素i
        for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
            if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
                k++;
            }
            if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }
        arr[i] = temp;//将temp值放到最终的位置
    }

    /**
     * 交换元素
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int []arr,int a ,int b){
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

时间复杂度为 O(nlgn) ,
空间复杂度为O(1),
稳定性:堆排序应该建堆和堆化的过程,无法保证稳定性。

堆排序相关的题目,求数组中的第k个最大元素
可以使用堆排序来解决这个问题——建立一个大根堆,做 k−1 次删除操作后堆顶元素就是我们要找的答案

class Solution {
             public int findKthLargest(int[] nums, int k) {
                //建立大顶堆
                for (int i = nums.length/2-1; i >= 0; i--) {
                  heapify(nums,i,nums.length);
                }
                //堆化
                int heaSize=nums.length;
                while(--k>0){
                    swap(nums,0,--heaSize);   //交换数据
                    heapify(nums,0,heaSize);
                }
                return nums[0];

            }

        private void heapify(int[] nums, int i, int heaSize) {
                int left=i*2+1;
                int temp=nums[i];
                while(left<heaSize){
                    int large_index=left+1<heaSize&& nums[left+1]>nums[left]
                            ?left+1:left;
                    if(nums[large_index]>temp){
                        nums[i]=nums[large_index];
                        
                    }else{
                        break;
                    }
                    i=large_index;
                    left=i*2+1;
                }
                nums[i]=temp;

        }
            private static void swap(int []arr,int a ,int b){
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
  
        }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值