归并排序的思想
1.分而治之,采用递归的思想进行分化治理,这就是归并排序的一个基本思想。
不停的往下分化
从中间节点开始分化治理,一直到分到只有一个的时候
分化方法很简答,
单数个数据,就/2,得到那个下标前面的全部放一组
偶数也是/2得到的数据下标前面的一组后面的一组。
一直划分到只有一个为止。
怎么拆开的再怎么进行合并回去
合并的过程中要进行比较,合并的时候,让合并的数字进行比较,每组数字都要进行比较,将前面的数字放前面。
怎么拆的就怎么合并回去,回去时候,还要记得进行排序
每次将两个组合并到一起的时候,都要记得将里面数据进行排序、
说白了,就是先分化,然后归并回去进行排序
package org.example.sort;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {1,4,7,8,3,6,9};
//这是两个数组,从中间开始分割的两个数组
//合并时候,两个数组需要的是,找到中间分割那个为止
sort(arr,0,arr.length - 1);
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
}
public static void sort(int[] arr,int left,int right){
if(left == right) return;//左右相等直接就返回了
//直接去找到中点,找到下标的中间点
int mid = (right - left) / 2 + left;//起始点加偏移量定位到中点坐标
//分成两个部分后对左右进行排序
sort(arr,left,mid);//左边界部分排序
sort(arr,mid + 1,right);//右边排序
merge(arr,left,right);//直接将左右告诉它就行自己去找中点进行排序
}
//这里是给递归回来的,已经好了的数组进行排序
//指定一下,左指针,右指针,右指针其实就是指向中间,还要有右边界,毕竟这些一会都是要发生变化的,我们是要递归的
private static void merge(int[] arr,int leftPtr,int rightBound) {//边界是指
//这里我们需要假定前面数据虽然分为两段,但是每一段数据都是被排好序的
//因为递归返回来的结果就是两段数据本身就是被排好序的
int mid = (rightBound - leftPtr) / 2 + leftPtr;//起始点加偏移量定位到中点坐标
//找到中间分割的位置
int[] temp = new int[rightBound - leftPtr + 1];
//需要做的事就是将整个数组进行分解,
//前面一半数据,后面一半数据,然后遍历前后数据,进行排序合并
int i = leftPtr;
int j = mid + 1;//将数组从中间分割的位置分成两个数组
//遍历前后两个数组用的
int k = 0;//将数组整合成新的排好序的数组
while (i <= mid && j <= rightBound){
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
//跑完了循环然后没完成的部分接上
while (i <= mid) temp[k++] = arr[i++];
while (j <= rightBound ) temp[k++] = arr[j++];
//将数据都存在临时temp数组里了,所以我们要讲temp替换到arr里面去
for(int value = 0;value < temp.length;value++){
arr[leftPtr++] = temp[value];
//将temp的值全部注入到arr中去。这样相当于一段一段修改arr的值
}
//
}
}
正整理一下思路,
先分而治之方法,每次都沿着中间去将数据分成两个部分
每次分成两个,每次分成两个,一直到
数据被分成一个一个
使用递归去分
不断递归下去,将数组分成两份,每一份都,左边一份右边一份
每一份都要进行排序
第二步是将左右排序好的序列,合并到一起,
然后这两个序列合并前需要再重新拍一下队。这样排完之后,再进行递归。
//这里是给递归回来的,已经好了的数组进行排序
//指定一下,左指针,右指针,右指针其实就是指向中间,还要有右边界,毕竟这些一会都是要发生变化的,我们是要递归的
private static void merge(int[] arr,int leftPtr,int rightBound) {//边界是指
//这里我们需要假定前面数据虽然分为两段,但是每一段数据都是被排好序的
//因为递归返回来的结果就是两段数据本身就是被排好序的
int mid = (rightBound - leftPtr) / 2 + leftPtr;//起始点加偏移量定位到中点坐标
//找到中间分割的位置
int[] temp = new int[rightBound - leftPtr + 1];
//需要做的事就是将整个数组进行分解,
//前面一半数据,后面一半数据,然后遍历前后数据,进行排序合并
int i = leftPtr;
int j = mid + 1;//将数组从中间分割的位置分成两个数组
//遍历前后两个数组用的
int k = 0;//将数组整合成新的排好序的数组
while (i <= mid && j <= rightBound){
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
//跑完了循环然后没完成的部分接上
while (i <= mid) temp[k++] = arr[i++];
while (j <= rightBound ) temp[k++] = arr[j++];
//将数据都存在临时temp数组里了,所以我们要讲temp替换到arr里面去
for(int value = 0;value < temp.length;value++){
arr[leftPtr++] = temp[value];
//将temp的值全部注入到arr中去。这样相当于一段一段修改arr的值
}
这里是合并代码部分,递归返回来的其实是一个整个序列,只不过这个序列左右都排好了,我们以中点为分界线,左边是拍好的,右边也是拍好的,但是左右整体是没拍好的,我们需要排好左右
或者我自己写了一个代码也ok
package org.example.sort;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {1,4,7,8,3,6,9};
//这是两个数组,从中间开始分割的两个数组
//合并时候,两个数组需要的是,找到中间分割那个为止
sort(arr,0,arr.length - 1);
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
}
public static int[] sort(int[] arr,int left,int right){
if(left == right) return arr;//左右相等直接就返回了
//直接去找到中点,找到下标的中间点
int mid = (right - left) / 2 + left;//起始点加偏移量定位到中点坐标
//分成两个部分后对左右进行排序
sort(arr,left,mid);//左边界部分排序
sort(arr,mid + 1,right);//右边排序
//这里是给递归回来的,已经好了的数组进行排序
//指定一下,左指针,右指针,右指针其实就是指向中间,还要有右边界,毕竟这些一会都是要发生变化的,我们是要递归的
//这里我们需要假定前面数据虽然分为两段,但是每一段数据都是被排好序的
//因为递归返回来的结果就是两段数据本身就是被排好序的
//找到中间分割的位置
int[] temp = new int[right - left + 1];
//需要做的事就是将整个数组进行分解,
//前面一半数据,后面一半数据,然后遍历前后数据,进行排序合并
int i = left;
int j = mid + 1;//将数组从中间分割的位置分成两个数组
//遍历前后两个数组用的
int k = 0;//将数组整合成新的排好序的数组
while (i <= mid && j <= right){
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
//跑完了循环然后没完成的部分接上
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
//将数据都存在临时temp数组里了,所以我们要讲temp替换到arr里面去
for(int value = 0;value < temp.length;value++){
arr[left++] = temp[value];
//将temp的值全部注入到arr中去。这样相当于一段一段修改arr的值
}
//排序好
return temp;
}
//这里是给递归回来的,已经好了的数组进行排序
//指定一下,左指针,右指针,右指针其实就是指向中间,还要有右边界,毕竟这些一会都是要发生变化的,我们是要递归的
private static void merge(int[] arr,int leftPtr,int rightBound) {//边界是指
//
}
}
package org.example.sort;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MergeSort {
public static void main(String[] args) {
int[] nums = new int[]{10,3,5,11,12,45,67,10,4};
Merge(nums,0,nums.length - 1);
//左右递归界限放进去
for (int i:nums
) {
System.out.println(i);
}
}
public static void Merge(int[] nums,int left,int right){
if(left == right){
return;
}
//归并排序
//先进行拆分,找到中点进行拆分
int mid = (left + right) / 2;//找到中间点
//中间点到左边的向左递归
Merge(nums,left,mid);
Merge(nums,mid + 1,right);//中间点到最后向右递归
//本质就是递归回来后
//左边从nums数组里从left 到 Mid已经被排好顺序了
//右边mid + 1到right也已经排好顺序了
//我们需要将这两个排好顺序的再次进行排序
int pL = left;
int pR = mid + 1;//两个指针进行比较排序
int[] result = new int[nums.length];//result作为临时存储数组,本质存储的是left到right这一段的
//nums排序的值
int i = left;
while (pL <= mid && pR <= right){
if(nums[pL] <= nums[pR]){
result[i++] = nums[pL++];
}
else {
result[i++] = nums[pR++];
}
}
while (pL <= mid){
result[i++] = nums[pL++];
}
while (pR <= right){
result[i++] = nums[pR++];
}
//result其实是nums中的一段,这一段的范围是
//left到right
for(int j = left;j <= right;j++){
//nums是一段一段
//result中包含的值只是nums中的一段
//这一段起自left,也就是左边界,重点是右边界,
nums[j] = result[j];
}
//或者我们这样处理,让result和nums一样,然后,将result值只存放left到rigth这个区间段的
}
}
新写的归并代码
归并排序
8个数据会Megre 8 -1 次
基数排序
桶排序思路中一种
计数排序
数据量大,但是范围小
数组下标是数据,然后每个下标位置上数据是这个下标重复次数。
简单来说就是将
出现的数据
比如数据是1 - 15,将1到15按照下标做成一个数组
数组中去记录,每个下标出现的次数
比如11出现 3次,那在数组arr[11]这个位置,就是3
比如7出现2次,那在数组arr[7]这位置就是2
有一个统计数组
还有一个返回数组
返回数组根据统计数组数据,恢复成一个完成数组。
package org.example.sort;
import java.util.Arrays;
public class MergeSort {
private static int[] arr = {1,1,2,3,4,5,6,1,5,14};;
public static void main(String[] args) {
//这是两个数组,从中间开始分割的两个数组
//合并时候,两个数组需要的是,找到中间分割那个为止
int[] sort = sort();
System.out.println(" ");
for (int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
}
public static int[] sort(){
int[] counts = new int[15];
//数据范围是0 - 14,计数数组用来统计每个数据重复多少次
int[] result = new int[arr.length];//用来返回结果
for(int i = 0;i < arr.length;i++){
//遍历数据放进计数数组
counts[arr[i]]++;//计数数组下标就是要放的数据的位置
}
for(int i = 0;i < counts.length;i++){
System.out.print(counts[i] + " ");
}
//由计数数组恢复出来
int j = 0;
for(int i = 0;i < counts.length;i++){
while(counts[i] > 0){
//计数数组里,没有的数,都会为0,比如下标为9的数,如果没有,那么a[9] = 0,这种没有的数我们就不便利了
arr[j++] = i;
counts[i]--;//每提交一个数据,统计存量时候就会下降一下,一直到这个数据被完全释放完了
//比如counts[3] = 5,说明3这个数据重复了五次,那么我们就需要将5个3放进arr里
//这个时候,i,和j不能同事处理,毕竟I是用来遍历counts的,J是用来往arr中加数据的,j遇到那种重复数据要动好几下,i才会动
}
}
return arr;
}
}
计数排序有一个严重问题,就是当数据是很大范围很大,比如
10 到 100000时候,计数下标太大了。
基数排序
先按照个位数进行排序
然后按照十位数进行排序
最后按照百位数进行排序
然后是千位数排序
每次排序都使用一个下标为
0 – 9的一维数组做底层,然后
然后每次设计都是二维数组桶
桶[][]前面那个取值范围是0 – 9代表数据下标排序范围
后面那个括号里面放的是,这个序号下数据有多少个
说白了就是建立10个桶
桶下标就是存放依据
每个桶是每次遍历排序的依据,遍历时候将数据放进对应桶里
放完之后将数据又重新按照桶中位置码放好。
package org.example.sort;
import java.util.Arrays;
public class MergeSort {
private static int[] arr = {18,48,77,767,6,6,91,1,14};;
public static void main(String[] args) {
//这是两个数组,从中间开始分割的两个数组
//合并时候,两个数组需要的是,找到中间分割那个为止
int[] sort = sort(3);
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
}
public static int[] sort(int input){
int[][] bucket = new int[10][arr.length];//一个二维数组,因为每次分类,你不知道一个桶里会有多少数据
int[] bucketElementCounts = new int[10];
//用于统计某一个位置上数据有多少个
//记录每个桶放了多少了数据
//排序轮数跟最大值位数有关
for(int n = 1, l = 1; l <= input;n = n * 10,l++) {//用来控制循环多少轮
//第一轮
for (int i = 0; i < arr.length; i++) {
int digOfElment = arr[i] / n % 10;//循环多少轮拿到那个数据
bucket[digOfElment][bucketElementCounts[digOfElment]++] = arr[i];
//将数组中某一个数组位置上数据
}
//将桶中数据,放回到原来数组中
//i,j用来遍历桶,k用来存放arr中数据
int k = 0;
for (int i = 0; i < 10; i++) {
//遍历上面那个统计数组,还原数据排序
//统计数组中,为数值为0的下标不参与排序,数值为0就说明,这个下标没有数据
//bucketElementCounts里放的是当某一个位置上数据有多少个
//比如bucketElementCounts[1]
if (bucketElementCounts[i] > 0) {//遍历每个桶,找到有数据那个桶
//当前遍历的行里,桶里有数据
int j = 0;
while (j < bucketElementCounts[i]) {
//只要这个位置上数据还是大于1,就会让循环继续下去。
arr[k] = bucket[i][j];//将下标交给数据,交出一个数据自己值减少一次
bucket[i][j] = 0;//交出数据之后自己这个位置的值为0
j++;
k++;
}
}
bucketElementCounts[i] = 0;
//一个列检查完了之后让所有计算每列数据那个也为零
}
}
return arr;
}
}
第二种微微改正方式
package org.example.sort;
import java.util.Arrays;
public class MergeSort {
private static int[] arr = {18,48,77,767,6,6,91,1,14};;
public static void main(String[] args) {
//这是两个数组,从中间开始分割的两个数组
//合并时候,两个数组需要的是,找到中间分割那个为止
int[] sort = sort(3);
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
}
public static int[] sort(int input){
//第二种改法,将所有数据当做临时变量,可以节省空间,这样不用特意去把桶清空
//排序轮数跟最大值位数有关
for(int n = 1, l = 1; l <= input;n = n * 10,l++) {//用来控制循环多少轮
int[][] bucket = new int[10][arr.length];//一个二维数组,因为每次分类,你不知道一个桶里会有多少数据
int[] bucketElementCounts = new int[10];
//用于统计某一个位置上数据有多少个
//记录每个桶放了多少了数据
//第一轮
for (int i = 0; i < arr.length; i++) {
int digOfElment = arr[i] / n % 10;//循环多少轮拿到那个数据
bucket[digOfElment][bucketElementCounts[digOfElment]++] = arr[i];
//将数组中某一个数组位置上数据
}
//将桶中数据,放回到原来数组中
//i,j用来遍历桶,k用来存放arr中数据
int k = 0;
for (int i = 0; i < 10; i++) {
//遍历上面那个统计数组,还原数据排序
//统计数组中,为数值为0的下标不参与排序,数值为0就说明,这个下标没有数据
//bucketElementCounts里放的是当某一个位置上数据有多少个
//比如bucketElementCounts[1]
if (bucketElementCounts[i] > 0) {//遍历每个桶,找到有数据那个桶
//当前遍历的行里,桶里有数据
int j = 0;
while (j < bucketElementCounts[i]) {
//只要这个位置上数据还是大于1,就会让循环继续下去。
arr[k] = bucket[i][j];//将下标交给数据,交出一个数据自己值减少一次
j++;
k++;
}
}
//一个列检查完了之后让所有计算每列数据那个也为零
}
}
return arr;
}
}