目录
五、归并排序(MergeSort)
归并排序思想:
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略,简单理解就是将大问题变为小问题,然后把所有小问题都解决掉,大问题就迎刃而解了,Java对象排序专用TIM SORT就是改进的归并排序,所以归并排序还是应用比较广泛。其中主要包括两个步骤:
- 切分步骤:将大问题变为小问题,通过递归解决更小的子问题。
- 解决步骤:将小问题的结果合并,以此找到大问题的答案。
基本思路:先使每个子序列有序,然后将有序的子序列合并,最后使整个序列有序。使用递归将原始数组递归对半分隔,直到不能再分(只剩下一个元素)后,开始从最小的数组向上归并排序
分而治之
使用递归不断对数组进行分割,当分割到每组都只有1个元素时,对于每一组来说当然是有序的,然后往上归并。
- 向上归并排序的时候,需要一个暂存数组用来排序,
- 将待合并的两个数组,从第一位开始比较,小的放到暂存数组,指针向后移,
- 直到一个数组空,这时,不用判断哪个数组空了,直接将两个数组剩下的元素追加到暂存数组里,
- 再将暂存数组排序后的元素放到原数组里,两个数组合成一个,这一趟结束。
以部分数组 [1,4,7,8,3,6,9]为例,当成2个半截数组来排,前半截数组[1,4,6,7,10]为有序的,后半截数组[2,3,5,8,9]为有序的,i指针指前 半截数组的第一个位置,j指针指后半截数组的第一个位置,我们新开辟一个空间temp,k指针指temp的第一个位置。然后i位置与j位置进行比较,谁小就把谁挪到k位置上,然后i或者j往后移动,k往后移动。不过会出现一种情况,可能最后前半截或者后半截会有多的,最后我们直接加入到temp后面去。
把一个两个有序部分合并到一起
public static void main(String[] args) {
int[] arr = {8, 4, 5, 7, 1, 3, 6, 2, 10, 3, 2, 543, 23, 44, 1, 3, 43, 4};
mergeSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
//分+治
public static void mergeSort(int[] arr,int left,int right){
if (left == right) return;
int mid = (left + right) >> 1;
mergeSort(arr,left,mid);//向左递归分解
mergeSort(arr,mid+1,right);//向右递归分解
merge(arr,left,mid,right);//合并
}
//治:合并2个有序数组
public static void merge(int[] arr,int left,int mid,int right){
int[] temp = new int[right-left+1];
int i = left;//左边有序序列的初始索引
int j = mid + 1;//右边有序序列的初始索引
int k = 0;//暂存数组下标
//把左边右两边的数据填充到temp数组,直到左右两边的有序序列,有一边处理完。
while (i <= mid && j <= right) temp[k++] = arr[i]<=arr[j] ? arr[i++] : arr[j++];
//以下2个while只有一个会执行,左边或右边有剩余元素全部直接填充到暂存数组后面
while (i<=mid) temp[k++] = arr[i++];
while (j<=right) temp[k++] = arr[j++];
//把最终排序结果复制到原数组
k = 0;
while (k<temp.length) arr[left++] = temp[k++];
}
六、快速排序(QuickSort)
快速排序思想:
会先把数组中的一个数当做基准数,一般会把数组中最左边的数当做基准数。然后从两边进行检索。先从右边检索(辅助指针i)比基准数小的。 再从左边检索(辅助指针j)比基准数大的。如果检索到了,就停下,然后交换这两个元素。然后再继续检索。
i和j一旦相遇,就停止检索。把基准数和相遇位置的元素交换,基准数和相遇位置的数交换完成,表示第一轮排序结束。
特点:基准数左边比它小,基准数右边比它大,先排基准数左边,排完之后再排基准数右边。方式和第一轮一样。
/*
快排思路:
会先把数组中的一一个数当做基准数,一般会把数组中最左边的数当做基准数。
然后从两边进行检索。先从右边检索比基准数小的。 再从左边检索比基准数大的。
如果检索到了,就停下,然后交换这两个元素。然后再继续检索。
i和j一旦相遇,就停止检索。把基准数和相遇位置的元素交换
基准数和相遇位置的数交换完成,表示第一轮排序结束
特点:基准数左边比它小,基准数右边比它大,先排基准数左边,排完之后再排基准数右边。方式和第一轮一样。
*/
/**
*
* @param arr
* @param left 表示从哪个位置开始排
* @param right 表示排到哪个位置
*/
public static void quickSort(int[] arr,int left,int right){
//进行判断,如果左边索引比右边索引要大,是不合法的,直接结束这个方法
if (left>right)return;
//定义变量保存基础数
int base = arr[left];
//定义变量i指向最左边
int i = left;
//定义变量j,指向最右边
int j = right;
//当i和j不相遇的时候,在循环中进行检索
while (i != j){
//先由j从右往左检索比基准数小的,如果检索到比基准数小的就停下。
// 如果检索到比基准数大或者相等的,就继续检索
while (arr[j]>=base && i < j){
j--;//j从右往左移动
}
//i从左往右检索/此时arr[j]小于基准数,此时j停下
while (arr[i]<=base && i < j){//
i++;
}
//代码走到这里。i停下,j也停下,然后交换i和j位置的元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//如果上面while循环的条件不成立,会跳出这个循环,往下执行
//如果这个条件不成立说明i和j相遇了
//如果i和j相遇了,就交换基准数这个元素和相遇位置的元素
//把相遇位置的元素赋值给基准数这个位置的元素
arr[left] = arr[i];
//把基准数赋值给相遇位置的元素
arr[i] = base;
//基准数在这里就归位了,左边的数字都比它小,右边的都比它大。
//疯狂套娃
//排基准数的左边
quickSort(arr,left,i-1);
//排基准数的右边
quickSort(arr,i+1,right);
}
七、基数排序(RadixSort)
基数排序思想:
基数排序( radix sort )属于“分配式排序”( distribution sort ),又称“桶子法”( bucket sort )或 bin sort ,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列
下面是推导过程
public static void radix(int[] arr){
//定义一个二维数组,表示10个桶
//1.10表示10个桶
//2.表示桶的容量,为了防止在放入数的时候,数据溢出,大小定为arr.length,每个桶最多可装arr.length个元素
//显然基数排序是典型的空间换时间的算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,也方便从每个桶取出,所以必须定义一个一维数组来记录每个桶的每次放入的数据个数
int[] bucketElementCounts = new int[10];//比如bucketElementCounts[0]记录的就是第一个桶bucket[0]里面元素的个数
/* 第一轮 */
for (int i = 0;i<arr.length;i++){
//取出每个元素的个位
int digitOfElement = arr[i] % 10;
//放入到对应的桶中
//bucketElementCounts[digitOfElement] 正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[i];
}
//按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一个桶并将桶中的数据放入到原数组
for (int j = 0;j < 10;j++){
//如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[j] != 0){
//循环该桶即第k个桶(即第k个一维数组)
for (int k = 0; k < bucketElementCounts[j]; k++) {
//取出元素放入到arr中
arr[index++] = bucket[j][k];
}
}
bucketElementCounts[j] = 0;//必须将表示桶中个数的计数数组清空,超级重要的一步
}
System.out.println("第一轮"+Arrays.toString(arr));
/* 第二轮 */
for (int i = 0;i<arr.length;i++){
//取出每个元素的十位
int digitOfElement = arr[i] / 10 % 10;
//放入到对应的桶中
//bucketElementCounts[digitOfElement] 正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[i];
}
//按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index = 0;
//遍历每一个桶并将桶中的数据放入到原数组
for (int j = 0;j < 10;j++){
//如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[j] != 0){
//循环该桶即第k个桶(即第k个一维数组)
for (int k = 0; k < bucketElementCounts[j]; k++) {
//取出元素放入到arr中
arr[index++] = bucket[j][k];
}
}
bucketElementCounts[j] = 0;//必须将表示桶中个数的计数数组清空,超级重要的一步
}
System.out.println("第二轮"+Arrays.toString(arr));
/* 第三轮 */
for (int i = 0;i<arr.length;i++){
//取出每个元素的百位
int digitOfElement = arr[i] / 100 % 10;
//放入到对应的桶中
//bucketElementCounts[digitOfElement] 正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[i];
}
//按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index = 0;
//遍历每一个桶并将桶中的数据放入到原数组
for (int j = 0;j < 10;j++){
//如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[j] != 0){
//循环该桶即第k个桶(即第k个一维数组)
for (int k = 0; k < bucketElementCounts[j]; k++) {
//取出元素放入到arr中
arr[index++] = bucket[j][k];
}
}
bucketElementCounts[j] = 0;//必须将表示桶中个数的计数数组清空,超级重要的一步
}
System.out.println("第三轮"+Arrays.toString(arr));
}
然后如何找出进行的轮数,显然是根据该数组中最大的值决定的,如果最大数有5位,则就要进行5轮排序。
下面加入最外层循环,进行改进。
public static void radix(int[] arr) {
//定义一个二维数组,表示10个桶
//1.10表示10个桶
//2.表示桶的容量,为了防止在放入数的时候,数据溢出,大小定为arr.length,每个桶最多可装arr.length个元素
//显然基数排序是典型的空间换时间的算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,也方便从每个桶取出,所以必须定义一个一维数组来记录每个桶的每次放入的数据个数
int[] bucketElementCounts = new int[10];//比如bucketElementCounts[0]记录的就是第一个桶bucket[0]里面元素的个数
//找到最大值
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) max = arr[i];
}
//得到最大数是几位数
int maxLength = (max + "").length();
//真正的基数排序开始
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
for (int j = 0; j < arr.length; j++) {
//取出每个元素的个位、十位、百位......
int digitOfElement = arr[j] / n % 10;
//放入到对应的桶中
//bucketElementCounts[digitOfElement] 正好初始默认值为0,可以用于记录每个桶里面元素的个数,加入一个,自增一次,方便下次加入
bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[j];
}
//按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一个桶并将桶中的数据放入到原数组
for (int j = 0; j < 10; j++) {
//如果桶中有数据,我们才放入到原数组
if (bucketElementCounts[j] != 0) {
//循环该桶即第k个桶(即第k个一维数组)
for (int k = 0; k < bucketElementCounts[j]; k++) {
//取出元素放入到arr中
arr[index++] = bucket[j][k];
}
}
//必须将表示桶中个数的计数数组清空,超级重要的一步
bucketElementCounts[j] = 0;
}
System.out.println("第"+(i+1)+"轮排序处理"+Arrays.toString(arr));
}
}