几种排序的总结(包括含有重复数字的快速排序)

文章目录

1.冒泡排序

冒泡排序(Bubble Sort):一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
假设一个无序数组的长度N,利用冒泡排序将其从小到到进行排序:
基本思路:
1)要进行N – 1趟,此处趟数用i来表示
2)每一趟中要进行N – i次(i表示第几趟,从而表明了趟数 + 每一趟中进行交换的次数 == N
3)进行前后比较,如果顺序错误,那么就要将其交换过来
《几种排序的总结(包括含有重复数字的快速排序)》由图可以知道,一共有7个数,最多进行了 7 – 1 = 6 趟,在第i趟中进行了N – i次交换

代码实现:

  for(int i = 0; i<arr.length - 1; i++){ 
              for(int j = 0; j<arr.length - i-1; j++){ 
                  if(arr[j+1] < arr[j]){ //如果顺序错误,那么进行交换
                      int temp = arr[j+1];
                      arr[j+1] = arr[j];
                      arr[j] = temp;
                  }
              }
        }

冒泡排序的优化:由上面可以知道,走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。所以只要有一个标记flag,在每一趟中都没有进行过一次交换,那么说明了数组有序了,不需要再进行下一趟的交换。

        for(int i = 0; i<arr.length - 1; i++){ 
              flag = false;//注意要将flag重置为false
              for(int j = 0; j<arr.length - i-1; j++){ 
                  if(arr[j+1] < arr[j]){ 
                      int temp = arr[j+1];
                      arr[j+1] = arr[j];
                      arr[j] = temp;
                      flag = true;//说明已经交换
                  }
              }
              if(flag == false)//如果这一趟没有进行过交换,那么就说明了这一次排序使得数字有序,退出循环
                  break;
        }

2.选择排序

选择排序(SelectSort):一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
算法步骤
1)在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2)再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3)重复第二步,直到所有元素均排序完毕。
《几种排序的总结(包括含有重复数字的快速排序)》
说明:
1)最多进行了arr.length – 1趟交换
2)在每一趟排序中,需要找到最小值,然后和当前这个数进行比较,如果当前的值比最小值要大,那么要交换,否则不用(此处是按照升序进行排序),进入下一趟排序(就好比上面图中的20,要遍历其后面的数,找到最小值,然后将其交换)

代码实现:

   int minIndex;//最小值对应的下标
   int min;//表示最小值
   for(int i = 0; i<arr.length; i++){ 
            minIndex = i;
            min = arr[i];
            for(int j = i+1; j<arr.length;j++){ 
                if(arr[j] < min) { 
                    minIndex = j;//表示最小值的下标
                    min = arr[j];//表示最小值
                }
            }
            if(minIndex != i){ 
                //如果min不是i,那么就说明当前下标的后面还有数比当前的下标对应的数字要小,所以进行交换
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
        }

3.插入排序

插入排序(InsertSort):
插入排序算法思想:每趟将一个元素,按照其关键字的大小插入到它前面已经排序的子序列中,依此重复,直到插入全部元素。
步骤:
1)将从下标为1的数字开始遍历,将当前的数字赋给临时变量temp
2)将temp和当前下标(temp对应的下标)之前的数字进行比较,如果temp大于等于前面的数字y,那么表明了y的后面就是temp要插入的位置否则就将前面的数字后移

代码实现:

//这个代码是一边输入,然后一边进行插入排序的
 for(int i = 0; i<arr.length; i++) { 
            arr[i] = (int)(Math.random()*800);//产生8000个[0,800)的随机数
            temp = arr[i];//将arr[i]赋给临时变量num,然后拿num这个临时变量和arr[i]之前的元素进行比较(要根据需求来比较)
            for(j = i - 1; j>=0; j--){ 
                if(arr[j] <= temp)//当num这个临时变量比arr[j]要大,那么就说明j后一个元素就是num
                    break;
                arr[j + 1] = arr[j];//否则就要将i前面的元素后移
            }
            arr[j+1] = num;//注意是j+1,而不是j,因为num比j下标对应的值大或者相等
        }

//先输入完毕,再进行排序
         for(int i = 1; i<arr.length; i++) { 
            num = arr[i];//将arr[i]赋给临时变量num,然后拿num这个临时变量和arr[i]之前的元素进行比较
            for(j = i - 1; j>=0; j--){ 
                if(arr[j] <= num)//当num这个临时变量比arr[j]要大,那么就说明j后一个元素就是num
                    break;
                arr[j + 1] = arr[j];//否则就要将i前面的元素后移
            }
            arr[j+1] = num;//注意是j+1,而不是j,因为num比j下标对应的值大或者相等
        }

可能大家会疑惑,为什么要将当前的数字赋给临时变量temp呢?如果不赋的话(那下面的代码说明一下),如果arr[i]不赋值给temp,那么如果arr[i]小于其前面的数字,那么会后移,arr[i]的值就变了,所以需要将arr[i]赋值给temp

4.折半插入排序

折半插入排序的原理和折半查找类似的,具体的定义可以百度进行搜索。
所以根据它的定义,我们在一个有序数组中找到新元素插入的位置t,然后将这个插入位置后的元素往后移,从而给这个新元素插入腾出一个位置,然后将新元素插入到位置t,从而实现有序.
具体的代码:

public class Demo01 { 
    public static void main(String[] args) { 
        int[] arr = new int[]{ 8,7,6,9,0,1,3,635,34232,123,89,8};
        System.out.print("没有进行排序时:");
        display(arr);
        binarySort(arr,arr.length);//调用方法,从而进行折半插入排序
        System.out.print("进行排序之后:");
        display(arr);
    }
    public static void display(int[] arr){ 
        for(int num: arr){ 
            System.out.print(num + " ");
        }
        System.out.println();
    }
    public static void binarySort(int[] arr,int n){ 
        int i,j,tmp;//tmp用于存放临时变量
        //low表示的下标为i的前面有序数组的起点下标,high表示的是下标为i之前的有序数组的起点终点
        int low,high,mid;//mid表示子数组中的中间下标
        for(i = 1; i < n; i++){ 
            tmp = arr[i];
            low = 0;
            high = i - 1;
            while(low <= high){ 
                //如果至少含有一个元素的时候就继续循环,进行比较,获得tmp要插入的位置
                mid = (low + high) / 2;
                /* 如果代码这样写的话,会怎样咧? 事实上,如果输入的数组没有重复的数字,那么的确没有什么影响 但是输入的数组含有重复数字的话,那么就没有办法更新low或者 high的值了,进而会陷入死循环中,从而造成超时 if(arr[mid] > tmp) high = mid - 1; else if(arr[mid] < tmp) low = mid + 1; */
                if(arr[mid] > tmp){  //如果tmp小于中间值,就说明tmp在左边,所以更改high的值
                    high = mid - 1;
                }else {  
                /* 否则,如果tmp大于等于中间值,说明tmp在右边,更改low的值 (一定是else,没有else if了,如果是else if的话,会造成死 循环) */
                    low = mid + 1;
               }
            }
            /*当low> high,这时候就意味着没有了元素,这时候可以退出循环 了,并且high的后面都是大于tmp的元素(也就是说high + 1这个 下标对应的数是最后一个大于tmp的数),因此需要将这些元素后移, 这里的for循环之所以是从j = i - 1开始,是因为下标为i前面的有 序数组是[0 , i - 1],那么我们现在需要将大于等于tmp的元素往后 移,给tmp插入腾出位置,所以是从i - 1开始遍历,往后移,直到 j = low - 1的时候结束后移) 当然,这里的for循环的循环判断条件也可以是j >= low,因为退出 while循环的时候,low必然大于high,并且low - high = 1(这个结 论大家可以尝试者用一组数据进行证明,例如1 2 3 4 5 6 7, 如果当前下标i对应的值是7,也就是说他前面的有序数组有6个数, 如果当前下标i对应的值是6,那么他前面的有序数组有5个数) */
            for(j = i - 1; j >= low; j--)
                arr[j + 1] = arr[j];
            arr[j+1] = tmp;

        }
    }
}

运行结果:
《几种排序的总结(包括含有重复数字的快速排序)》

4.希尔排序

希尔排序(ShellSort):也是一种插入排序,他是简单插入排序改进之后的一个更加高效的版本,也称为缩小增量排序。

算法描述:
1)将一个数据序列分成若干组,每组由若干相隔一段距离(称为增量)的元素组成,在一个组内采用直接插入排序算法进行排序。

2)增量初值通常为数据序列长度的一半,以后每趟增量减半,最后值为1,此时就成为了直接插入排序了。随着增量逐渐减小,组数也减小,组内元素个数增加,数据序列接近有序。
《几种排序的总结(包括含有重复数字的快速排序)》
当增量gap变成了1,此时就只有一组,变成了直接插入排序(增量为1),当gap 为0时,数组实现了有序。

代码实现:

        int gap= arr.length / 2;//步长
        int j,temp;
        while(gap != 0){ //当gap等于0的时候,退出循环,数组实现有序
            for(int i = gap; i<arr.length; i++){ 
                temp = arr[i];//将arr[i]赋给一个临时变量,然后和其同一组中并且在i前面的数字进行比较
                for(j = i-gap; j>=0 ; j-=gap){ //这一步近似于插入排序,将temp和i前面的而且是同一组的数字进行比较
                    if(arr[j] <= temp){ //此处是升序排序
                        break;
                    }
                    arr[j+gap] = arr[j];//如果temp小于arr[j]的值,那么后移(注意这里不是arr[j + 1] = arr[j]了哈,因为要在每一组中进行直接插入排序嘛)
                }
                arr[j+gap] = temp;//注意这一步是arr[j + length],为什么不是j呢?上面的插入排序已经说明清楚了,这里就不说了哈
            }
            gap /= 2;
        }

5.归并排序

归并的含义:将两个或两个以上的有序表合并成一个新的有序表。

归并排序(MergeSort):采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

《几种排序的总结(包括含有重复数字的快速排序)》

递归实现归并排序

其算法思路如下:

1.如果给的数组只有一个元素的话,直接返回(也就是递归到最底层的一个情况)—作为结束递归的条件

2.把整个数组分为尽可能相等的两个部分(分)

3.对于两个被分开的两个部分再次进行操作2(也就是递归)

4.把两个被分开且排好序的数组拼接在一起

代码实现:

这是我本来的代码,后来发现如果有重复的数字,就不可以,会发生报错,所以在这里标记一下,既是给自己一个提醒,也给大家一个提醒,希望大家不要像我一样犯这样的错误,哈哈哈~~~

import java.util.Scanner;

public class MergeSort { 
    public static int[] arr = new int[10];
    public static void main(String[] args){ 
        Scanner sc = new Scanner(System.in);
        for(int i = 0; i<arr.length; i++)
            arr[i] = sc.nextInt();
        merge(); //调用方法,从而新建一个新的临时数组,将子数组放到这个临时数组中,然后将临时数组赋值给原来的数组,从而排好序的
        System.out.println("进行排序之后:");
        for(int a: arr){ 
            System.out.print(a+ " ");
        }
        System.out.println();
    }
    public static void merge(){ 
        //建立一个新数组,从而将子数组复制个这个数组,将所有的子数组进行归并之后,将这个数组赋值给原来的数组即可
        int[] array = new int[arr.length];
        mergeSort(array,0,arr.length - 1);
    }
    public static void mergeSort(int[] array, int n, int m){ 
        if(n >= m){ 
            //如果n>m,那么范围不对,如果相等,就说明只有一个元素,此时不需要进行交换
            return;
        }
        int index = (n + m ) / 2;//将数组分成两部分
        mergeSort(array,n,index);
        mergeSort(array,index + 1, m);//利用递归,将一个数组分成两个子数组
        merge(array,n,index,m);//将两个子数组进行归并
    }
    public static void merge(int[] array,int lower,int mid, int upper){ 
        int n = upper - lower + 1;//统计这个子数组中有多少个元素
        int lowerBound = lower;
        int lower2 = mid + 1;//右子数组的最小下标
        int j = 0;
        while(lower <= mid && lower2 <= upper){ 
            //当两个子数组都有元素的时候,则将两者的元素进行比较,谁小的谁先插入到新的数组中
            if(arr[lower] <=arr[lower2]) { 
                array[j++] = arr[lower];
                lower++;
            }else{ 
                array[j++] = arr[lower2];
                lower2++;
            }
        }
        //考虑子数组的长度不一样的情况
        //当左子数组没有遍历完的时候
        while(lower <= mid){ 
            array[j++] = arr[lower++];
        }
        //当右子数组没有遍历完的时候
        while(lower2 <= upper){ 
            array[j++] = arr[lower2++];
        }
        //将已经排好序的新数组赋值给原来的数组
        for(j = 0; j<n; j++,lowerBound++)
            arr[lowerBound] = array[j];
    }
}

改正后,实现数组含有重复数字的情况的代码:

//有重复的数字都可以进行排序的代码(改正了)
import java.util.Scanner;

public class MergeSort { 
    public static int[] arr = new int[10];
    public static void main(String[] args){ 
        Scanner sc = new Scanner(System.in);
        for(int i = 0; i<arr.length; i++)
            arr[i] = sc.nextInt();
        merge(); //调用方法,从而新建一个新的临时数组,将子数组放到这个临时数组中,然后将临时数组赋值给原来的数组,从而排好序的
        System.out.println("进行排序之后:");
        for(int a: arr){ 
            System.out.print(a+ " ");
        }
        System.out.println();
    }
    public static void merge(){ 
        //建立一个新数组,从而将子数组复制个这个数组,将所有的子数组进行归并之后,将这个数组赋值给原来的数组即可
        int[] array = new int[arr.length];
        mergeSort(array,0,arr.length - 1);
    }
    public static void mergeSort(int[] array, int n, int m){ 
        if(n >= m){ 
            //如果n>m,那么范围不对(就好比区间[2,3],那么2肯定是比3小的,所以左边的一定比右边的小)如果相等,就说明只有一个元素,此时不需要进行交换
            return;
        }
        int index = (n + m ) / 2;//将数组分成两部分
        mergeSort(array,n,index);
        mergeSort(array,index + 1, m);//利用递归,将一个数组分成两个子数组
        merge(array,n,index,m);//将两个子数组进行归并
    }
    public static void merge(int[] array,int lower,int mid, int upper){ 
        int n = upper - lower + 1;//统计这个子数组中有多少个元素
        int lowerBound = lower;
        int lower2 = mid + 1;//右子数组的最小下标
        int j = 0;
        //将两个子数组进行归并
        while(lower <= mid && lower2 <= upper){ 
            //当两个子数组都有元素的时候,则将两者的元素进行比较,谁小的谁先插入到新的数组中,从而实现升序排序
            if(arr[lower] <=arr[lower2]) { //如果左子数组的数小于等于右子数组的数,那么先将左子数组的数插入(注意这里是小于等于,要考虑出现重复的数字的情况)
                array[j++] = arr[lower];
                lower++;
            }else { 
                array[j++] = arr[lower2];
                lower2++;
            }
        }
        //考虑子数组的长度不一样的情况
        //当左子数组没有遍历完的时候
        while(lower <= mid){ 
            array[j++] = arr[lower++];
        }
        //当右子数组没有遍历完的时候
        while(lower2 <= upper){ 
            array[j++] = arr[lower2++];
        }
        //将已经排好序的新数组赋值给原来的数组
        for(j = 0; j<n; j++,lowerBound++)
            arr[lowerBound] = array[j];
    }

非递归方式实现归并排序(C语言)

#include<stdio.h>
#define MAX_SIZE 100
int array[MAX_SIZE];
void display(int arr[],int n){ 
     int i;
     for(i = 0; i < n; i++){ 
        printf("%d ",arr[i]);
     }
     printf("\n");
}
void copy_arr(int arr[],int n){ 
     int i;
     for(i = 0; i < n; i++)
        arr[i] = array[i];
}
void merge(int arr[],int low,int left_end,int high){ 
     int index = low; //这一步是必须的,而不是从0开始
     int right_low = left_end + 1; //获取右子数组的起点下标
     while(low <= left_end && right_low <= high){ 
        if(arr[low] <= arr[right_low]){ 
            array[index++] = arr[low++];
        }else{ 
            array[index++] = arr[right_low++];
        }
     }
     while(low <= left_end){ 
        array[index++] = arr[low++];
     }
     while(right_low <= high){ 
        array[index++] = arr[right_low++];
     }
}
/* 利用迭代的方式,实现归并排序: 1、首先定义步长length = 1,表示子数组的长度。 2、从下标i为0开始遍历,在[i,i + length - 1],[i + length,i + 2 * length - 1] 这两个子数组调用merge方法,从而将这两个子数组进行归并排序 3、下标i不断自增,只是应该是 i += 2 * length,表示下一个子数组的起点下标 重复步骤2 4、重复步骤2,3,直到i > N - 2 * length 之所以N - 2 * length,是因为i = N - 2 * gap是最后一个满足这一次归并的下标, 如果i = N - 2 * length的话,那么左子数组[N - 2 * length,N - length - 1], 右子数组为[N - length,N - 1] 5、退出步骤4之后,需要比较i + gap和N的大小关系,如果i+length <= N,说明 这时候有两个子数组进行合并,需要再次进行合并,调用方法 merge,否则,就将array复制到arr,完成一次归并排序 6、重复上述步骤,直到length >= N */
void mergeSort(int arr[],int n,int length){ 
     int i;
     for(i = 0; i <= n - length * 2; i += 2 * length){ 
            //左子数组[i,i + length - 1],右子数组[i + length,i + 2 * length - 1]
       // printf("i = %d,i + length - 1 = %d, i + 2 * length - 1 = %d\n",i,i + length - 1,i + 2 * length - 1);
        merge(arr,i,i + length - 1,i + 2 * length - 1);
     }
     if(i + length < n){ 
     //如果这一步中符合条件,说明这时候有2个子数组需要进行归并排序
        merge(arr,i,i + length - 1,n - 1);
     }else{ 
       /* 这一步是必须的,如果没有这一步,那么就会导致输出的时候有一部分数据 发生丢失,这时候之所以需要这样,是因为考虑到最后有些有些数据并没有 进行归并的情况,这里例子挺好的(输入的是一个奇数个数的时候) 输入21个数:1 7 4 6 9 8 2 0 3 5 10 15 16 14 18 19 17 16 14 11 20, 如果没有这一步,那么最后结果并不是升序排序 */
       
       for(; i < n; i++){ 
         array[i] = arr[i];
       }
   }
     copy_arr(arr,n); //注意,这里不需要else,直接将array数组赋值给arr即可
     display(arr,n);
}
void sort(int arr[],int n){ 
    int length = 1;//定义子数组长度(理解这个含义,才可以明白mergeSort中调用merge为什么是那样子)
    while(length < n){ 
        //当子数组的长度小于n的时候,那么需要将子数组进行归并
      // printf("length = %d\n",length);
        mergeSort(arr,n,length);
        length *= 2;//完成一次归并之后,下一次循环中,子数组的长度就是length * 2了
    }
}
int main(){ 
  int arr[MAX_SIZE];
  int n,i,j,k,left_index,right_index,num,index;
  scanf("%d",&n);
  for(i = 0; i < n; i++)
    scanf("%d",&arr[i]);
  for(i = 0; i < n; i++)
    array[i] = 0;
  sort(arr,n);
  return 0;
}

运行结果:
《几种排序的总结(包括含有重复数字的快速排序)》

6.快速排序

快速排序(QuickSort): 快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。

步骤为:

1、挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),此处选择数组中间的那个数作为基准。即arr[index] = pivot,此处的index 是数组中间数的下标

2、分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,已经将数组分割成了两个子数组,左子数组都是小于基准的值,右子数组都是大于基准的值

那么实现分割操作呢?
基准值arr[ index ] 赋给临时变量pivot,然后分别从两边进行遍历如果左边遇到第一个比pivot大的数,右边遇到第一个比pivot小的数,那么将两者进行交换

有同学可能又会问这里为什么又要将arr[index]赋给临时变量pivot呢?
因为在分割的过程中进行了交换,可能会使arr[index]的值发生变化,从而导致错误。比如一个例子 :一个数组{5,9,2,6,0,1,2} ,那么index = (0 + 6)/2,有pivot为6,那么从两边进行遍历,从而实现分割得到{5,2,2,1,0,6,9},那么如果不将arr[index]赋给pivot,那么arr[index]对应的值会发生改变,变为1,而不再是6了

3、递归排序子序列:通过递归,将左右两个子数组进行排序(重复上面1、2操作)—–结束递归的条件是当数组只有一个元素的时候,结束递归,此时实现有序

代码实现:

这是一个片面的代码:只能将一个不含有重复数字的数组进行排序,如果数组含有重复的数字,就会发生报错,在这里做个标记,提醒自己,也希望能够提醒大家,希望大家不要像我一样犯同样错误哦,哈哈哈~~~~

import java.util.Scanner;

public class QuickSort { 
    public static int[] arr = new int[10];

    public static void main(String[] args) { 
        Scanner sc = new Scanner(System.in);
        for (int i = 0; i < arr.length; i++)
            arr[i] = sc.nextInt();
        //调用方法进行快速排序
        quickSort(0, arr.length - 1);
        for (int a : arr)
            System.out.print(a + " ");
        System.out.println();
    }

    public static void quickSort(int n, int m) { 
        if (n < m) { //因为m的下标是右边的,那么必然是大于n的,就说明不只一个元素,此时进行交换
            //定义基准的下标
            int index = (m + n) / 2;
            int pivot = arr[index];//将中间的数作为基准,并将其值赋给临时变量pivot
            int m2 = m, n2 = n;
            int partition = getPartition(n,m,pivot);//分割,其中返回的值是分割的值的下标
            //利用递归,将左右两个子数组进行排序
            quickSort(n2, partition-1);//左子数组
            quickSort(partition+1, m2);//右子数组
        }else{ 
            //如果n等于m,就说明了只有一个元素,若大于m,那么范围不对(就好比区间[2,3],那么2肯定是比3小的,所以左边的一定比右边的小)
            return;
        }
    }
    public static int getPartition(int n, int m, int pivot){ 
        //将数组分成两个子数组,其中左子数组的值小于中间枢纽pivot的值,而右子数组的值大于中间枢纽的值
        while (true) { 
            //当n>=m,说明了左边都是小于标注的值,这时候就可以退出循环
            while (n<m && arr[n] < pivot) { 
                //需要在n<m的前提下进行判断,如果左边是小于标准的值,那么就右移,直到找到第一个大于标准的值的下标
                //注意这里需要有n<m,否则有可能n一直移到最右边,那么很容易发生溢出,从而会报错
                n++;
            }
            while (n<m && arr[m] > pivot) { 
                //需要在n<m的前提下进行判断,如果右边找到了第一个小于标准的值,那么就退出循环,否则往左移
                //注意这里需要有n<m,否则有可能m一直移到最左边,那么很容易发生溢出,从而会报错
                m--;
            }
            if (n >= m) { 
                //相遇了,那么就退出循环,说明基准的左边都是小于基准的数,右边都是大于基准的数,此时基准的下标为n或m,而不是原来的index
                break;
            } else { 
                swap(n,m);
            }
        }
        return n;//返回基准的下标
    }
    public static void swap(int n , int m){ 
        //没有相遇,将二者进行交换
        int temp = arr[n];
        arr[n] = arr[m];
        arr[m] = temp;
    }
}

含有重复数字的快速排序的情况分析: 这里利用三向切分进行分析的。也就是将数组分为三部分:小于当前切分元素的部分,等于当前切分元素的部分,大于当前切分元素的部分。

步骤:
1)每次切分从数组的左边到右边遍历一次,同时第一个数对应的值作为基准pivot,然后遍历,将基准后一个数字进行比较
①如果基准比其后的一个数字要大,那么直接将两者进行交换,之后将基准的下标更改,随之的其后的数字的下标也要更改
②如果基准比其后的一个数字要小,那么将其后的数字和数组最后的数字要交换,此时要将原来数组最后一个数字的下标要更改,然后再进行比较,如果还是基准的值小,那么重复②的操作,直到出现另外两种情况
③如果基准的值和其后一个数字相等,那么将基准后一个数的下标后移,注意这里的后移并不是指基准的下标的后移,而是其后的数字进行后移,下面的图可以解释

《几种排序的总结(包括含有重复数字的快速排序)》
2)每次切分之后,位于gt指针和lt指针之间的元素的位置都已经被排定,不需要再去移动了。之后将(lo,lt-1),(gt+1,hi)分别作为处理左子数组和右子数组的递归函数的参数传入,递归结束,整个算法也就结束。
《几种排序的总结(包括含有重复数字的快速排序)》
这时候我们对左子数组进行分析:
《几种排序的总结(包括含有重复数字的快速排序)》

代码实现:

import java.util.Scanner;

public class QuickSort2 { 
    public static int[] arr = new int[10];
    public static void main(String[] args){ 
        Scanner sc = new Scanner(System.in);
        for(int i = 0; i<arr.length; i++)
            arr[i] = sc.nextInt();
        System.out.println("进行快速排序之后:");
        quickSort(0,arr.length - 1);
        for(int a: arr){ 
            System.out.print(a+" ");
        }
        System.out.println();
    }
    public static void quickSort(int n, int m){ 
        if(n >= m){ 
            //如果只有一个元素或者范围不正确,就结束递归
            return;
        }else{ 
            int lo = n, lt = lo + 1, gt = m;//lo表示基准的下标,lt表示基准后的数字得下标,gt表示数组最后一个数的下标
            int pivot = arr[lo];//将每一个数组的第一个数作为基准
            //遍历基准后的数,如果比基准小的,就直接交换两者的值,如果比基准大的数,那么就将这个数和数组最后面的数替换,再让基准和当前的数字进行比较
            //如果相等,就将基准后一个数后移,即lt++,通过这个操作实现了基准左边都是小于基准的数,右边都是大于基准的数字
            while(lt <= gt){ 
                if(arr[lt] > pivot)//如果当前的数字大于基准,那么就将当前的数字和数组最后一个数字进行替换
                    swap(lt,gt--);
                else if(arr[lt] < pivot)//如果当前的数字小于基准,那么就将当前的数字和基准进行替换
                    swap(lt++,lo++);
                else
                    lt++;//如果当前的数字等于基准,那么就直接基准后一个数的下标后移
            }
            quickSort(n,lo - 1);//对左子数组进行快速排序
            quickSort(gt + 1, m);//对右子数组进行快速排序
        }
    }
    public static void swap(int n, int m){ 
        int temp = arr[n];
        arr[n] = arr[m];
        arr[m] = temp;
    }
}

7.基数排序

基数排序(RadixSort):主要思路是,将所有待比较数值(注意,必须是正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次稳定排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.

图解:
《几种排序的总结(包括含有重复数字的快速排序)》
方法一:通过数组来实现:
思路(个人的思路,有什么疑惑的地方请指教哈,哈哈哈):
①通过给出的数组arr,获得最大值max与最小值min,从而max – min的值有多少位数,进而可以知道要进行多少次递归(当然也可以通过循环来实现)
②新建一个数组,统计0 – 9 这10个数字分别出现了多少次
同时新建一个二维数组,其中有10行,arr.length列,来储存数组对应的数字,为什么是10行arr.length呢?因为每一行的下标对应的就是0 – 9这是个数字,列有arr.length是为了避免溢出,因为如果数字很多的情况呢,所以我觉得有arr.length列是避免溢出的情况。
遍历数组arr,从而可以知道对应的0-9这10个数字出现的次数,并将对应的值赋给二维数组对应的行中去
④二维数组复制完毕之后,将遍历二维数组,将其值赋给原来的数组arr,然后重复上述操作max – min次,从而实现有序

考虑到数组中有负数的情况,那么要怎么解决呢?–通过将数组转换成含有非负数的数组。
将数组中的值都减去min这个值,从而将数组转化成含有非负数的数组,然后再将进行上述操作,不过注意的是,将二维数组的值赋给arr以后,arr并不是原来的数组了,因为转化成非负数的时候所有的数字都减去了min,所以还要遍历arr,将所有的数都加上min,才是要求的

代码实现:

import java.util.Scanner;

public class RadixSort { 
    public static int[] arr = new int[10];
    public static int k = 1;
    public static void main(String[] args){ 
        Scanner sc = new Scanner(System.in);
        int max = 0;
        int min = 0;
        //输入数字,同时获得最大值、最小值
        for(int i = 0; i<arr.length;i++) { 
            arr[i] = sc.nextInt();
            if(max < arr[i])
                max = arr[i];
            if(min > arr[i])
                min = arr[i];
        }
        //获取数组中最多有多少位数字 ---由于有了负数的存在,那么最大值的位数需要改变,因为下面的将数组化为整数的时候,最大值要改变,所以此时
        //含有负数的数组化为非负整数的时候,最大值发生了改变,为max - min,如果数组是非负整数,因为下面的将数组化为整数的时候,最大值要改变
        //所以此时的最大值还是max - min
        int count = getCount(max - min);
        getRadixSort(arr,count,min);//调用方法,进行基数排序
        System.out.println("进行基数排序之后:");
        for(int a : arr)
           System.out.print(a+" ");
        System.out.println();
    }
    public static int  getCount(int max){ 
        int count = 1;
        max /= 10;
        while(max != 0){ 
            count++;
            max /= 10;
        }
        return count;
    }
    public static void getRadixSort(int[] arr, int total,int min){ 
        //利用递归来求解
        if(total <= 0){ 
            return;
        }else{ 
            int[] count = new int[10]; //新建一维数组,来统计每个数位出现的次数
            int[][] array = new int[10][arr.length];//定义一个二维数组
            for(int i = 0; i<arr.length; i++){ 
                //获得一个数的个位、十位等数字
                int digit = (arr[i] - min) / k % 10;//注意这里要是arr[i] - min,以防止负数的存在
                array[digit][count[digit]]= (arr[i] - min);//将数组中的值赋值到有序数组中
                count[digit] ++;
            }
            //将二位数组中的值赋给原来的数组
            int m = 0;
            for(int i = 0; i<10; i++){ 
                    if(count[i] != 0){ 
                        for(int j = 0; j<count[i]; j++){ 
                            arr[m++] = array[i][j];
                        }
                    }
            }
            //因为防止有负数,本来将数组的值都减去了min,所以要变成原来的数组,要将所有的数字都加上min才可以
            for(int i = 0; i<arr.length; i++)
                    arr[i] += min;
            k *= 10;
            total--;
            getRadixSort(arr,total,min);
        }
        //利用循环来实现
// while(total > 0){ 
// //统计每个数位出现的次数
// int[] count = new int[10];
// int[][] array = new int[10][arr.length];//定义一个有序数组
// for(int i = 0; i<arr.length; i++){ 
// int digit = (arr[i] - min) / k % 10;//获得最小值
// array[digit][count[digit]]= (arr[i] - min);//将数组中的值赋值到有序数组中
// count[digit] ++;
// }
// //将有序数组中的值赋给原来的数组
// int m = 0;
// for(int i = 0; i<10; i++){ 
// if(count[i] != 0){ 
// for(int j = 0; j<count[i]; j++){ 
// arr[m++] = array[i][j];
// }
// }
// }
// for(int i = 0; i<arr.length; i++)
// arr[i] += min;
// k *= 10;
// total--;
// }
    }
}

通过链表来实现的代码:

/** * 通过链表来实现基数排序 * 思路: * 1、新建链表数组,从而表示有序数组 * 2、获得arr中的最大值、最小值,从而知道arr要转换成有序链表要循环max - min次 * 3、获得arr数组中的每一项中每一个位数出现的次数,然后将这个数放在链表数组对应的位置中 * 4、获得链表数组之后,遍历这个数组,然后将值赋给arr * 5、获得arr并不是要求的有序数组,需要遍历这个数组,将每一项都要加上min才是题目要求的 * 为什么要怎么做?因为要考虑负数存在的情况,因为有负数,所以进行基数排序,我们可以通过将 * 数组转换成最小值为0的数组(注意只是改变其值而已,而不是新建的数组),而要实现将数组转换成 * 含有非负数的,那么将每一项的值都减去min * 6、重复上述操作3、4、5,直至循环max - min次 */
package sort;

import java.util.Scanner;

public class RadixSort2 { 
    public static int[] arr = new int[10];
    public static int k = 1;
    public static void main(String[] args){ 
        Scanner sc = new Scanner(System.in);
        int max = 0, min = 0;
        //输入数组,同时获得数组中的最大值、最小值
        for(int i = 0; i<arr.length; i++) { 
            arr[i] = sc.nextInt();
            if(max < arr[i])
                max = arr[i];
            if(min > arr[i])
                min = arr[i];
        }
        int count = getCount(max - min);//获得数组arr转换成有序数组循环的次数
        getRadixSort(count,min);
        System.out.println("进行基数排序之后:");
        for(int a: arr)
            System.out.print(a+ " ");
        System.out.println();
    }

    /** * 获得arr要转换成有序数组循环的次数 --- 也就是数组max - min的值有多少位数 * @param max * @return */
    public static int getCount(int max){ 
        int count = 1;
        max /= 10;
        while(max != 0){ 
            count++;
            max /= 10;
        }
        return count;
    }

    /** * 通过链表来实现基数排序 * @param total 表示要转换的次数 * @param min 表述数组中的最小值 */
    public static void getRadixSort(int total, int min){ 
    //通过循环来实现
        while(total > 0){ 
            LinkedList[] list = new LinkedList[10];//新建一个链表数组,长度为10,对应的是0-9这10个数字
            for(int i = 0; i<list.length; i++)
                list[i] = new LinkedList();//新建链表!!!注意这一步必须要有,否则没有办法初始化链表,也就没有实现其链表的相关操作
            for(int i = 0; i<arr.length; i++){ 
                int digit = ((arr[i] - min) / k) % 10;//将arr[i] - min以防数组中含有负数,从而将负数的数组转换成非负数的数组
                list[digit].insert(arr[i] - min);//将对应的数插入到链表数组对应的位置 -- 减去min是避免了存在负数的情况
            }
            int m = 0;
            //将链表数组中的值赋值给arr
            for(int i = 0; i<list.length; i++){ 
                if(!list[i].isEmpty()){ 
                    Node current = list[i].head.next;//因为定义的是带假节点的链表,所以链表真正的头节点就是head.next
                    //遍历链表,将值赋给数组
                    while(current != null){ 
                        arr[m++] = current.value;
                        current = current.next;
                    }
                }
            }
            //这里和上面所说的一样的道理,都是避免负数的存在,转化非负数是将所有的值减去min,所以这里转化成原来的数组要将所有的值都加min才可以
            for(int i = 0; i<arr.length; i++){ 
                arr[i] += min;
            }
            k *= 10;
            total--;
        }
    }
}

8.堆排序

思路:
①首先按照要求将数组构建成大顶堆或者小顶堆。所谓的大顶堆,就是根节点是数组的最大值,反之,小顶堆的根节点是数组的最小值。
《几种排序的总结(包括含有重复数字的快速排序)》

②然后在构建完毕之后,将根节点和数组的最后一个节点进行交换,交换完毕之后,数组的个数要减1,从而在之后的操作中不在涉及到最后的数,保证了最大或最小的数在最后。
③然后通过递归,重复上面的操作①②,直到数组的个数为1结束递归

public class SortApp { 
		public static int[] arr = new int[10];//定义一个数组
		public static int count = arr.length;//元素的个数
		public static void main(String[] args) { 
			for(int i = 0; i<arr.length; i++) { 
				arr[i] = (int)(Math.random() * 100);//随机产生10个随机数
			}
			System.out.print("未进行堆排序之前:");
			display();
			for(int i = 1; i<arr.length;i++)
				tickUp(i);//构建大顶堆
			sort();//进行堆排序
			System.out.print("进行堆排序之后:");
			display();//输出
		}
		public static void display() { 
			for(int a: arr)
				System.out.print(a +" ");
			System.out.println();
		}
		  public static void sort() { 
			if(count == 1)//如果只有一个元素,那么结束递归
    		  return;
    	  swap(0,count - 1);//将根节点和最后一个节点进行交换,然后将元素个数减1,从而将最后一个数固定
    	  count --;
    	  /* 不知道细心的小伙伴有没有发现如果这样写的话会存在一个bug, 那就是如果一开始数组就只有一个元素,那么这时候经过swap之后 count的值变成了0,此时不等于1,如果这样写的话,那么就会 继续递归,此时就会发生报错,因为-1发生越界了,所以 需要将这一步代码放到swap之前,或者判断是count <= 1,但明显放到 前面会好些,因为不需要进行swap这一步操作 if(count == 1)//如果只有一个元素,那么结束递归 return; */
			tickDown(0);//由上面可以知道,根节点和最后一个节点交换了,那么此时根节点不一定是最大的,那么需要和它的子节点进行比较,从而再次构建大顶堆
			sort();//构造完毕之后,再次递归,进行堆排序
		}
		  public static void swap(int i, int j) { 
			   int temp = arr[i];
			   arr[i]  = arr[j];
			   arr[j] = temp;//将根节点和最后一个节点进行交换
		  }
		/** * 向下筛选,从而再次构建大顶堆 * @param index */
		public static void tickDown(int index) { 
			int larger;
			int temp = arr[index];//将父节点赋给一个临时变量,然后和它的子节点进行比较,如果大于,就说明他的位置是对的,否则进行交换
			while(index < count / 2) { 
				//循环条件必须是index < count / 2,否则再计算左右子节点的时候发生报错
				int leftChild = index * 2 + 1;
				int rightChild = index * 2 + 2;
				if(rightChild < count && arr[leftChild] < arr[rightChild]) { 
					//如果含有右子节点,而且左右子节点中右子节点最大,那么larger = rightChild
					larger = rightChild;
				}else { 
					//这里有两种情况,首先如果没有右子节点,那么larger 就是leftChild,其次如果有右子节点,而且左子节点大于右子节点,那么larger = leftChild
					larger = leftChild;
				}
				if(arr[larger] <= temp) { 
					//如果父节点大于等于子节点,那么这个位置就是当前节点的位置,退出循环,注意不可以在这里写arr[index] = temp,然后在结束循环的时候不写这一步了
					//因为有可能一直是arr[larger]一直大于temp,直到index = count / 2,退出循环,从而没有办法插入temp,导致数组中有两个相同的数
					break;
				}else { 
					arr[index] = arr[larger];//如果父节点小于子节点,那么子节点替换到其父节点的位置,父节点替换到其子节点的位置
					index = larger;
				}
				arr[index] = temp;//这里有两种情况,首先第一种是index = count / 2,从而结束循环,第二种是arr[larger] <= temp,从而结束循环
			}
		}
		/** * 通过向上筛选进行将节点插入到堆中 * @param index */
		public static void tickUp(int index) { 
			int temp = arr[index];
			int parent = (index - 1) / 2;//获取其父节点
			while(index > 0 && arr[parent] < temp) { 
				//当父节点小于子节点的时候,将其进行交换
				arr[index] = arr[parent];
				index = parent;
				parent = (parent - 1) / 2;
			}
			arr[index] = temp;
		}

}

通过参考其他大佬的代码,将只保留tickDown方法:

public class Sorting { 
      public static int[] arr = new int[10];
      public static int count = arr.length;//定义数组的个数
      public static void main(String[] args) { 
    	  for(int i = 0; i<count; i++)
    		  arr[i] = (int)(Math.random()*100);
    	  System.out.println("没有进行堆排序之前:");
    	  display();
    	  heapify();//首先将一个无序数组构建成一个大顶堆
    	  sort();//进行堆排序
    	  System.out.println("进行堆排序之后:");
    	  display();
    	  
    	  
      }
      /** * 遍历数组 */
      public static void display() { 
    	  for(int a:arr)
    		  System.out.print(a+" ");
    	  System.out.println();
      }
      public static void sort() { 
          if(count == 1)//如果只有一个元素,那么结束递归
    		  return;
    	  swap(0,count - 1);//将根节点和最后一个节点进行交换,然后将元素个数减1,从而将最后一个数固定
    	  count --;
    	  /* 不知道细心的小伙伴有没有发现如果这样写的话会存在一个bug, 那就是如果一开始数组就只有一个元素,那么这时候经过swap之后 count的值变成了0,此时不等于1,如果这样写的话,那么就会 继续递归,此时就会发生报错,因为-1发生越界了,所以 需要将这一步代码放到swap之前,或者判断是count <= 1,但明显放到 前面会好些,因为不需要进行swap这一步操作 if(count == 1)//如果只有一个元素,那么结束递归 return; */
    	  adjustHeap(0);//再次构建最大堆
    	  sort();//递归,从而再次将根节点和最后一个节点交换
      }
      public static void swap(int i, int j) { 
    	  int temp = arr[i];
    	  arr[i] = arr[j];
    	  arr[j] = temp;
      }
      public static void heapify() { 
    	  //从最后一个非叶子节点开始遍历,将其和它的子节点进行比较,如果自身小于它的子节点,那么就进行交换,从而构建大顶堆
    	  for(int i = arr.length / 2 - 1; i >= 0; i --)
    		  adjustHeap(i);
      }
      public static void adjustHeap(int index) { 
    	  int temp = arr[index];//将父节点的值赋给临时变量,将其和它的子节点进行比较
    	  int larger;//标记左右子节点中哪一个子节点的值更大,然后将其和temp进行比较,如果temp大,那么这个父节点的位置是正确的,否则进行交换
    	  while(index < count / 2) { 
    		  int leftChild = index * 2 + 1;
    		  int rightChild = index * 2 + 2;
    		  if(rightChild < count && arr[leftChild] < arr[rightChild]) { 
    			  //这里必须要有rightChild,因为考虑到当index 为count / 2 - 1时,只有一个左子节点
    			  //当存在右子节点时,那么将左右子节点进行比较,从而获得两者中的最大值
    			  larger = rightChild;
    		  }else { 
    			  //当没有右子节点时,那么larger 为leftChild,当有右子节点,而且两个节点中,左子节点最大,那么larger 为leftChild
    			  larger = leftChild;
    		  }
    		  if(arr[larger] <= temp) { 
    			  //当父节点的值比其左右子节点都大,那么就退出循环
    			  break;
    		  }else { 
    		  //当父节点的值小于其子节点的值,那么进行交换
    			  arr[index] = arr[larger];
    			  index = larger;
    		  }
    			  
    	  }
    	  arr[index] = temp;
      }
}

我是第一次写博客的,有写的不好的地方,还请大家原谅啊,哈哈哈,当然也请大家指正,下次一定会改正的哦。

    原文作者:奋斗的龙猫
    原文地址: https://blog.csdn.net/weixin_46544385/article/details/107131501
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞