第十一章
最常用的:冒泡排序、插入排序、选择排序、归并排序、快速排序、计数排序、基数排序、桶排序。
1.冒泡排序
当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作
代码
/**
* @author: xuxu
* @date 2020/2/27 9:29
* @Description: 冒泡排序
*/
public class BubbleSort {
public static void bubbleSort(int[] arr){
//控制第几次排序
for(int i=0;i<arr.length;i++){
//因为冒泡排序 如果有一次没有发生排序证明已经完成排序,设置跳出的标志位,默认没发送排序
boolean isSort=false;
//控制每次排序数据间的比较和换位
//最后一个位置不需要发生比较所以-1,每进行一次后最后稳定的部分新增1不需要比较所以-i
for (int j=0;j<arr.length-i-1;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
isSort=true;
}
}
if(!isSort){
System.out.print("第"+(i+1)+"次排序后数组无变化退出");
break;
}
//遍历
System.out.print("第"+(i+1)+"次排序后数组为:");
for (int k=0;k<arr.length;k++){
System.out.print(arr[k]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] arr = {6,3,2,1,4};
bubbleSort(arr);
}
}
2.插入排序
首先,我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
插入排序也包含两种操作,一种是元素的比较,一种是元素的移动。当我们需要将一个数据 a 插入到已排序区间时,需要拿 a 与已排序区间的元素依次比较大小,找到合适的插入位置。找到插入点之后,我们还需要将插入点之后的元素顺序往后移动一位,这样才能腾出位置给元素 a 插入。
/**
* @author: xuxu
* @date 2020/2/27 9:56
* @Description:插入排序
*/
public class InsertSort {
public static void insertSort(int[] arr){
//这里循环的是未排序部分
for(int i=1;i<arr.length;i++){
//本次循环要比较插入的数字
int insertNum = arr[i];
//这里是循环已排序部分,从后往前比较
int j=i-1;
for(;j>=0;j--){
//如果前一个数字大于要插入的数字则向后移动让出位置
if(arr[j]>insertNum){
arr[j+1] = arr[j];
}else{
//一旦他找到对的位置,则不需要向前继续找,因为前面是排好序的
break;
}
}
//将要插入的数放入前面找到的插入的位置 这里j+1是因为前面j--了
arr[j+1]=insertNum;
//遍历
System.out.print("第"+(i)+"次排序后数组为:");
for (int k=0;k<arr.length;k++){
System.out.print(arr[k]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] arr = {6,3,2,1,4};
insertSort(arr);
}
}
3.选择排序
选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
/**
* @author: xuxu
* @date 2020/2/27 11:02
* @Description: 选择排序
*/
public class SelectSort {
public static void selectSort(int[] arr){
for (int i=0;i<arr.length;i++){
int min=i;
for(int j=i;j<arr.length-1;j++){
if(arr[min]>arr[min+1]){
//先找出未排序区最小的一个下标
min = min+1;
}
}
//将min放入已排序区的最后面,同时原位置的元素放在min处
int temp = arr[i];
arr[i]=arr[min];
arr[min] = temp;
//遍历
System.out.print("第"+(i+1)+"次排序后数组为:");
for (int k=0;k<arr.length;k++){
System.out.print(arr[k]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] arr = {6,3,2,1,4};
selectSort(arr);
}
}
从推荐使用的角度看 插入>冒泡>选择
冒泡排序、插入排序、选择排序这三种排序算法,它们的时间复杂度都是 O(n2),比较高,适合小规模数据的排序。
归并排序和快速排序两种时间复杂度为 O(nlogn) 。这两种排序算法适合大规模的数据排序
归并排序和快速排序都用到了分治思想
4.归并排序
分治算法一般都是用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧,这两者并不冲突。
递归公式
递推公式:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
终止条件:
p >= r 不用再继续分解
merge_sort(p…r) 表示,给下标从 p 到 r 之间的数组排序。我们将这个排序问题转化为了两个子问题,merge_sort(p…q) 和 merge_sort(q+1…r),其中下标 q 等于 p 和 r 的中间位置,也就是 (p+r)/2。当下标从 p 到 q 和从 q+1 到 r 这两个子数组都排好序之后,我们再将两个有序的子数组合并在一起,这样下标从 p 到 r 之间的数据就也排好序了
合并过程
我们申请一个临时数组 tmp,大小与 A[p…r] 相同。我们用两个游标 i 和 j,分别指向 A[p…q] 和 A[q+1…r] 的第一个元素。比较这两个元素 A[i] 和 A[j],如果 A[i]<=A[j],我们就把 A[i] 放入到临时数组 tmp,并且 i 后移一位,否则将 A[j] 放入到数组 tmp,j 后移一位。
继续上述比较过程,直到其中一个子数组中的所有数据都放入临时数组中,再把另一个数组中的数据依次加入到临时数组的末尾,这个时候,临时数组中存储的就是两个子数组合并之后的结果了。最后再把临时数组 tmp 中的数据拷贝到原数组 A[p…r] 中。
/**
* @author: xuxu
* @date 2020/2/27 14:32
* @Description: 归并排序
* f(a,p) =f(a,q)+f(q+1,p)
*/
public class MergeSort {
public static void mergeSort(int[] arr,int begin,int end ){
if(begin>=end){
return ;
}
//中间位置
int mid = (begin+end)/2;
//递归分解合并
mergeSort(arr, begin, mid);
mergeSort(arr, mid+1 ,end);
merge(arr,begin,mid,end);
}
/**
* 合并操作
*/
public static void merge(int[] arr, int begin, int mid, int end){
int[] mergeArr=new int[arr.length];
//数组1的指针
int p1 = begin;
//数组2的指针
int p2 = mid+1;
//合并的数组起始位置
int index=begin;
while(p1<=mid && p2<=end){
if(arr[p1]>=arr[p2]){
mergeArr[index++]=arr[p2++];
}else{
mergeArr[index++]=arr[p1++];
}
}
while(p1<=mid){
mergeArr[index++] = arr[p1++];
}
while(p2<=end){
mergeArr[index++] = arr[p2++];
}
for(int i=begin;i<=end;i++){
arr[i] = mergeArr[i];
}
}
public static void main(String[] args) {
int[] arr = {6,5,3,4,2,1,7};
mergeSort(arr, 0, arr.length-1);
System.out.print("排序后数组元素为:");
for (int i=0;i<arr.length;i++){
System.out.print(arr[i]);
}
}
}
5.快速排序
基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分
实现:
public class QuickSort {
public static int getMid(int[] nums,int low,int high){
int temp=nums[low];
while(low<high){
while(low<high && nums[high]>=temp){
high--;
}
nums[low]=nums[high];//如果右边小于中间数 移动到左边
while(low<high && nums[low]<=temp){
low++;
}
nums[high]=nums[low]; //如果左边有大于中间数 的数移动到右边
}
nums[low]=temp; //赋值中间数
return low;
}
public static void quick(int[] nums , int low,int high){
if(low<high){
int mid = getMid(nums,low,high);
//递归调用左右两边继续排序
quick(nums,low,mid-1);
quick(nums,mid+1,high);
}
}
public static void main(String[] args) {
int[] nums = {22,11,33,66,77,55,44,99,88};
quick(nums,0,nums.length-1);
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i]+" ");
}
}
}