排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。
假设含有n个记录的序列为{r1,r2,·······,rn},,其相应的关键字分别为{k1,k2,·······,kn}。
需要确定1,2·····,n的一种排列 p1,p2,·······,pn ,使其相应的关键字满足kp1 ≤ kp2···· ≤kpn 非递增(非递减)关系,即使得序列成为一个按关键字有序的序列{r1,r2,·······,rn},这样的操作称为排序。
假设 ki = kj (1 ≤ i ≤ n ,1 ≤ j ≤ n,i ≠ j),且在排序之前在序列中ri领先于rj (i < j)。如果排序完成后,序列中ri仍然领先于rj (i < j),那么我们就说排序方法是稳定的,反之,则该排序算法就不睡稳定的。简单点说就说班级排名,现在张三和李四分数一样,没有排序前李四在张三的前面,排序完成后,李四还是在在张三的前面,那么该排序算法就说稳定的。
事后统计的方法
这种方法可行,但是有两个问题:一是要想对设计的算法的运行性能进行评测,需要实际运行该程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,这种方式,要在同一台计算机的相同状态下运行,才能比较哪个算法速度更快。
事前估算的方法
通过分析某个算法的时间复杂度来判断哪个算法更优。
public class test1 {
public static void main(String[] args) {
add1();
add2();
}
public static void add1(){
int total = 0;
int end = 100;
//使用for循环计算 T(n) = n + 1 , +1是执行最后一次的判断
for (int i = 1; i <= end; i++) {
total = total + i;
}
System.out.println("total = " + total);
}
public static void add2(){
int total = 0;
int end = 100;
//直接计算 T(n) = 1 等差数列和=(首项+末项)×项数÷2
total = (1 + end) * end / 2;
System.out.println("total = " + total);
}
}
忽略常数项 结论:
忽略低次项 结论:
结论:
常见的时间复杂度。
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是o(1)
public static void fun1(){
int i = 1;
int j = 2;
++i;
++j;
int m = i + j ;
System.out.println(m);
}
上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类码有多长,即使有几万几十万行,都可以用o(1)来表示它的时间复杂度。
/**
* 对数阶O(log2n)
*/
public static void fun2(int n){
int i = 1;
while (i < n){
i = i * 2;
}
}
说明:
在while循环里面,每次都将i乘以2,乘完之后,i距离n就越来越近了。
假设循环x次之后,i就大于2了,此时这个循环就退出了,也就是说2的x次方等于n,那么x=log2n也就是说当循环log2n次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(log2n)。O(log2n)的这个2时间上是根据代码变化的,i=i*3,则是O(log3n)。
如果N= ax(a>0,a≠1),即a的x次方等于N(a>0,且a≠1),那么数x叫做以a为底N的对数( logarithm),记作x = loa,N。其中,a叫做对数的底数,N叫做真数,x叫做以a为底N的对数。
/**
* O(n)线性阶
*/
public static void fun3(int n){
int j = 0;
for (int i = 0; i < n; i++) {
j = i;
j++;
}
}
说明:
这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用o(n)来表示它的时间复杂度。
/**
* 线性对数阶O(nlog~2~n)
*/
public static void fun4(int n){
int j = 1;
for (int i = 0; i < n; i++) {
j = i;
while (i < n){
i = i * 2;
}
}
}
说明:
线性对数阶o(nlogN)其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的活,那么它的时间复杂度就是n* O(logN),也就是了O(nlogN)
/**
* 平方阶O(n2)
*/
public static void fun5(int n){
int j = 1;
for (int i = 0; i < n; i++) {
for (int k = 0; k < i; k++) {
j = i;
j++;
}
}
}
说明:
平方阶o(n2)就更容易理解了,如果把o(n)的代码再嵌套循环一遍,它的时间复杂度就是o(n2),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是O(n*n)。
即o(n2)如果将其中一层循环的n改成m,那它的时间复杂度就变成了O(m*n)
说明:参考上面的O(n2)去理解就好了,O(n3)相当于三层n循环,其它的类似。
冒泡排序(Bubble Sorting)的基本思想是:
通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。
小结上面的图解过程:
package sort.bubbleSort;
import java.util.Arrays;
public class bubbleSort {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,22,18};
bubbleSort(arr);
}
/**
* 冒泡排序(稳定)
* 每一趟排序找到序列中最大的一个值,将最大的数,交换到序列最后的位置。
*/
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean isSwaped = false;
for (int j = 0; j < arr.length - i - 1; j++) {
//如果前面的数比后面的数大,进行位置交换。
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
isSwaped = true;
}
}
System.out.println("第"+ i + " 趟:"+ Arrays.toString(arr));
if (!isSwaped) {
// 内层没有元素交换,此时整个数组已经有序
break;
}
}
}
static void swap(int[] arr,int i,int min){
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
输出结果
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。
选择排序(select sorting)也是一种简单的排序方法。
它的基本思想是:第一次从arr[0]~ arr[n-1]中选取最小值,与arr[0]交换,第二次从 arr[1]~ ar[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]~ arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i-1]~ arr[n-1]中选取最小值,与arr[i-1]交换,…,第n-1次从arr[n-2]~ arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
package sort.selectionSort;
import java.util.Arrays;
/**
* 直接选择排序(不稳定)
*
* 每次从无序区间选择一个最大或最小值的一个元素放在无序区间的最后或者最前,直到待排序的所有元素排序完毕。
*
* 最开始时,待排序数组(无序区间),取值:[i..n)。
* 已排序的数组(有序区间),取值:[],区间中没有任何元素。
* 进行第一次排序:选择无序区间的最小值,放在无序区间的最开始位置。
*
* 选择排序在排序过程中无法保证相同元素的先后顺序,故不是一个稳定性的算法。
*/
public class selectionSort {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,22,18};
selectionSort(arr);
//System.out.println(Arrays.toString(arr));
}
/**
* 选择排序
*/
public static void selectionSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
int min = i;//假设第一个下标就是最小的
boolean flag = false;
//每次从arr[0] ~ arr[n-1]中找到最小元素的下标
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]){
min = j;
flag = true;
}
}
if (flag){//减少交换次数
swap(arr,i,min);
System.out.println("第" +(i+1) + "次:" + Arrays.toString(arr));
}
}
}
}
输出结果
双向查找,每次从无序区间中选出最小值和最大值,存放在无序区间的最开始和最后面位置,重复上述过程。
package sort.selectionSort;
import java.util.Arrays;
/**
* 直接选择排序(不稳定)
*
* 每次从无序区间选择一个最大或最小值的一个元素放在无序区间的最后或者最前,直到待排序的所有元素排序完毕。
*
* 最开始时,待排序数组(无序区间),取值:[i..n)。
* 已排序的数组(有序区间),取值:[],区间中没有任何元素。
* 进行第一次排序:选择无序区间的最小值,放在无序区间的最开始位置。
*
* 选择排序在排序过程中无法保证相同元素的先后顺序,故不是一个稳定性的算法。
*/
public class selectionSort1 {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,22,18};
selectionSortOP(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 双向选择排序
*
* 每次从无序区间中选出最小值和最大值,存放在无序区间的最开始和最后面位置,重复上述过程
*/
public static void selectionSortOP(int[] arr) {
int low = 0;
int high = arr.length - 1;
// low == high => 无序区间只剩下最后一个元素,其实整个数组已经有序了。
while (low < high) {
int min = low; // 假设最小的是第一个元素下标
int max = low; // 假设最大的是第一个元素下标
/**
* 遍历序列
* 1, 0+1
* 2:1+1
*/
for (int i = low + 1; i <= high; i++) {
if (arr[i] < arr[min]) { //找到最小值下标
min = i;
}
if (arr[i] > arr[max]) { //找到最大值下标
max = i;
}
}
// 此时min对应了最小值索引,交换到无序区间的最前面
swap(arr, min, low);
// 边界条件 low == max
if (max == low) {
//此时max还在low这个位置,若交换high与max索引的元素会导致已排序的"2"被移到high处需要将min值赋给max
max = min;
}
swap(arr, max, high);
low++;
high--;
}
}
static void swap(int[] arr,int i,int min){
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
插入排序(Insertion Sorting)的基本思想是:
把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
两个方法都可
package sort.insertionSort;
import java.util.Arrays;
/**
* 直接插入排序 (稳定)
*
* 插入排序:类似打牌码牌的过程。
* 直接插入排序:将待排序的集合看做两部分,已排序的区间[0..i);待排序的区间[i...n);
*
* 每次选择无序区间的第一个元素插入到有序区间的合适位置,直到整个数组有序。
*
* 初始数据越接近有序,插入排序效率越高,经常作为高阶排序算法优化手段。
*/
public class insertionSort {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,22,18};
insertionSort1(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 直接插入排序
* 已排序区间[0..i) => 默认第一个元素就是已经排好序的区间
* 待排序区间[i...n)
*/
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
// 已排序区间[0...1)
// 待排序区间[i ..n)
// 选择无序区间的第一个元素,不断向前看
// 注意看内层循环的终止条件 j >= 1而不是 j >= 0 ?
// 因为此时arr[j] 不断向前看一个元素。 j - 1 要合法 j - 1 >= 0
for (int j = i; j >= 1 && arr[j] < arr[j - 1]; j--) {
//找到了合适的位置才交换
swap(arr, j, j - 1);
}
}
}
public static void insertionSort1(int[] arr) {
int insertVal = 0;
int insertIndex = 0;
for (int i = 1; i < arr.length; i++) {
insertVal = arr[i]; //待插入的数,暂存要插入的数
insertIndex = i - 1; //arr[i]的前面这个数的下标
/**
* 给insertVal找到合适的插入位置
*
* insertIndex >= 0 确保insertVal的插入位置,不越界。
* insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置。
*将arr[insertIndex]后移
*/
while (insertIndex >= 0 && insertVal < arr[insertIndex]){ // 12 < 88
arr[insertIndex + 1] = arr[insertIndex]; // arr[2] = arr[1]
insertIndex--;
}
/**
* 退出循环,说明找到了插入位置,insertIndex + 1
*/
if (insertIndex + 1 != i){
arr[insertIndex + 1] = insertVal; //arr[1] = 12
}
}
}
static void swap(int[] arr,int i,int min){
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
思考一下,直接插入的排序的主要耗时在哪个步骤?我觉得是给待插入的值找到合适的插入位置比较合适,所以采用二分查找,来优化前面在有序的序列中逐步查找插入位置的办法。
使用二分查找优化插入位置的查找次数。
package sort.insertionSort;
import java.util.Arrays;
public class insertionSort {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,22,18};
//insertionSort1(arr);
insertionSortBS(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 二分插入排序,*使用二分查找优化插入位置的查找次数。
* @param arr
*/
public static void insertionSortBS(int[] arr) {
// 有序区间[0..i)
// 无序区间[i..n)
for (int i = 0; i < arr.length; i++) {
int val = arr[i];
// 有序区间[left...right)
int left = 0;
int right = i;
while (left < right) {
int mid = (left + right) / 2;
if (val < arr[mid]) {
right = mid;
}else {
// val >= arr[mid]
left = mid + 1;
}
}
// 搬移[left..i)的元素
for (int j = i; j > left ; j--) {
arr[j] = arr[j - 1];
}
// left就是待插入的位置
arr[left] = val;
}
}
}
我们看简单的插入排序可能存在的问题.
数组arr = {2,3,4,5,6,1}这时需要插入的数1(最小),
这样的过程是:
结论:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.
希尔排序是希尔〈Donald ShelI)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
可以把希尔排序理解成整体插入排序之前的预处理,就是为了咋插入排序之时,序列更加有序,然后插入排序效率就会更高。
稳定性:不稳定,再按照gap对子数组进行排序时,有可能值相等的两个元素被分到了两个不同的数组,他们的顺序可能交换。如下图,相等的5,在第一趟排序就交换了位置。
希尔排序时,对有序序列在插入时采用交换法,并测试排序速度.
public static void shellSorSwap(int[] arr) {
/**
* 第一轮排序,将10个数据分成10组 arr.length/2
*/
for (int i = 5; i < arr.length; i++) {
//遍历各组中所有的元素(共五组。每组两个元素),步长为5
for (int j = i - 5; j >= 0; j -= 5) {
//如果当前元素大于加上步长后的那个元素,则交换元素位置
if (arr[j] > arr[j + 5]){
swap(arr,j,j + 5);
}
}
}
System.out.println("第一轮" + Arrays.toString(arr));
//第二轮
for (int i = 2; i < arr.length; i++) {
//遍历各组中所有的元素(共2组。每组两个元素),步长为2
for (int j = i - 2; j >= 0; j -= 2) {
//如果当前元素大于加上步长后的那个元素,则交换元素位置
if (arr[j] > arr[j + 2]){
swap(arr,j,j + 2);
}
}
}
System.out.println("第二轮" + Arrays.toString(arr));
//第三轮
for (int i = 1; i < arr.length; i++) {
//遍历各组中所有的元素(共2组。每组两个元素),步长为2
for (int j = i - 1; j >= 0; j -= 1) {
//如果当前元素大于加上步长后的那个元素,则交换元素位置
if (arr[j] > arr[j + 1]){
swap(arr,j,j + 1);
}
}
}
System.out.println("第三轮" + Arrays.toString(arr));
}
输出结果
可以看到第三轮排序完成后,数组已经基本有序了,在最后进行一次插入排序即可。但是测试后你会发现效率并没有直接插入的效率高,因为采用的是交换法,交换的次数太多了,解决的办法就是使用移动法。
package sort.shellSort;
import com.sun.prism.shader.Solid_RadialGradient_REFLECT_AlphaTest_Loader;
import java.awt.geom.GeneralPath;
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,18};
shellSorSwap(arr);
}
/**
* 希尔排序 - 缩小增量排序,交换法
* 按照gap将原数组分为gap个子数组,子数组内部先排序,不断缩小gap值,直到gap = 1
* 当gap = 1时,整个数组已经近乎有序,只需要最后来一次插入排序即可。
*
* 回顾一下冒泡和插入,冒泡排序是每次循环把最大和最小的数字移动到最边上,然后对剩下的接着冒泡。
* 插入排序是把数组分成两组,每次从无序组中拿出一个数插入有序数组。
* 这里i为分界线,i左边是有序数组,从i右边拿出一个数插入到有序数组合适位置,然后i+1
* i有两个作用,i+1既可以改变分组,也可以在下次i+gap后从无序数组中拿元素插入本组。
*/
public static void shellSorSwap(int[] arr) {
int count = 0;
for (int gap = arr.length / 2; gap > 0 ; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
//遍历各组中所有的元素(共gap组。每组个元素),步长为gap
for (int j = i - gap; j >= 0; j -= gap) {
//如果当前元素大于加上步长后的那个元素,则交换元素位置
if (arr[j] > arr[j + gap]){
swap(arr,j,j + gap);
}
}
}
System.out.println("第"+(++count)+"轮"+Arrays.toString(arr));
}
/*
*//**
* 第一轮排序,将10个数据分成10组 arr.length/2
*//*
for (int i = 5; i < arr.length; i++) {
//遍历各组中所有的元素(共五组。每组两个元素),步长为5
for (int j = i - 5; j >= 0; j -= 5) {
//如果当前元素大于加上步长后的那个元素,则交换元素位置
if (arr[j] > arr[j + 5]){
swap(arr,j,j + 5);
}
}
}
System.out.println("第一轮" + Arrays.toString(arr));
//第二轮
for (int i = 2; i < arr.length; i++) {
//遍历各组中所有的元素(共2组。每组两个元素),步长为2
for (int j = i - 2; j >= 0; j -= 2) {
//如果当前元素大于加上步长后的那个元素,则交换元素位置
if (arr[j] > arr[j + 2]){
swap(arr,j,j + 2);
}
}
}
System.out.println("第二轮" + Arrays.toString(arr));
//第三轮
for (int i = 1; i < arr.length; i++) {
//遍历各组中所有的元素(共2组。每组两个元素),步长为2
for (int j = i - 1; j >= 0; j -= 1) {
//如果当前元素大于加上步长后的那个元素,则交换元素位置
if (arr[j] > arr[j + 1]){
swap(arr,j,j + 1);
}
}
}
System.out.println("第三轮" + Arrays.toString(arr));*/
}
static void swap(int[] arr,int i,int min){
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
希尔排序时,对有序序列在插入时采用移动法。
package sort.shellSort;
import com.sun.prism.shader.Solid_RadialGradient_REFLECT_AlphaTest_Loader;
import java.awt.geom.GeneralPath;
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,18};
//shellSorSwap(arr);
shellSorMove(arr);
}
/**
* 希尔排序 移动法
*/
private static void shellSorMove(int[] arr) {
int count = 0;
for (int gap = arr.length / 2; gap > 0 ; gap /= 2) { //分组
for (int i = gap; i < arr.length; i++) { //逐步对gap开始的所有元素进行插入排序
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]){ //从前往后,所以每一次前面都是有序的,这样如果temp>最后一个元素就不排了
while (j - gap >= 0 && temp < arr[j - gap]){ //处理当前分组当前元素下的选择插入排序
//移动
arr[j] = arr[j - gap];
j -= gap;
}
//退出循环说明找打位置
arr[j] = temp;
}
}
System.out.println("第"+(++count)+"轮"+Arrays.toString(arr));
}
}
static void swap(int[] arr,int i,int min){
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
快速排序(Quicksort)是对冒泡排序的一种改进。
基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
核心思路:分区
分区值:默认选择最左侧元素 pivot
从无序区间选择一个值作为分界点 pivot ,开始扫描原集合,将数组中所有 < 该pivot的元素放在分界点左侧;>= 该元素的值放在分区点的右则。
经过本轮交换,pivot放在了最终位置,pivot的左侧都是小于该值的元素,pivot的右侧都是大于该值的元素,在这两个子区间重复上述过程,直到整个集合有序。
import java.util.Arrays;
/**
* 快速排序
* **快速排序(Quicksort)是对冒泡排序的一种改进。**
* **基本思想**:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
*
* **核心思路**:分区
* **分区值**:默认选择最左侧元素 pivot
* 从无序区间选择一个值作为分界点 pivot ,开始扫描原集合,将数组中所有 < 该pivot的元素放在分界点左侧;>= 该元素的值放在分区点的右则。
*
* 经过本轮交换,pivot放在了最终位置,pivot的左侧都是小于该值的元素,pivot的右侧都是大于该值的元素,在这两个子区间重复上述过程,直到整个集合有序。
*/
public class quickSortTest {
public static void main(String[] args) {
int[] arr = new int[]{1,88,12,34,11,22,35,36,39,100,55,18};
quickSort(arr,0,arr.length - 1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr,int left,int right){
// 左下标
int l = left;
// 右下标
int r = right;
int temp = 0;
// 我这里取中轴数为基准数,基准数可随机。
int pivot = arr[(left + right) / 2];
// 比pivot小的值放在左边,比pivot大的值放在右边
while (l < r){
//在pivot左边一直找到大于等于pivot的值
while (arr[l] < pivot){
l += 1;
}
//在pivot左边一直找到小于于等于pivot的值
while (arr[r] > pivot){
r -= 1;
}
//在pivot左右两边的值,已经按照左边全部是小于等于pivot的值,
// 右边是大于等于pivot的值
if (l >= r){
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//交换完成后,发现arr[left] = pivot 相等,前移 r--
if (arr[l] == pivot){
r -= 1;
}
//交换完成后,发现arr[right] = pivot 相等,l后移 l--
if (arr[r] == pivot){
l += 1;
}
}
//如果l == r,必须l++,r--,否则出现栈溢出
if (l == r){
l += 1;
r -= 1;
}
//左递归
if (left < r){
quickSort(arr,left,r);
}
//右递归
if (right > l){
quickSort(arr,l,right);
}
}
}
输出结果
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起即分而治之)。
对上图说明:
可以看到结构很像一颗二叉树,本文的归并排序采用递归去实现,(也可以采用迭代的方式去实现)分阶段可以理解为就是递归拆分子序列的过程。
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤
package sort.merge;
import java.util.Arrays;
/**
* @BelongsProject: data-structure-java
* @BelongsPackage: sort.merge
* @Author: xj
* @CreateTime: 2022-08-04 15:18
* @Description: 归并排序
* 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,
* 该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,
* 而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起即分而治之)。
* @Version: 1.0
*/
public class MergeSort {
public static void main(String[] args) {
int arr[] ={8,4,5,7,1,3,6,2};
System.out.println("归并排序前:" + Arrays.toString(arr));
int[] temp = new int[arr.length];
sort(arr,0,arr.length - 1, temp);
System.out.println("归并排序前:" + Arrays.toString(arr));
}
public static void sort(int[] arr,int left,int right,int temp[]){
if (left < right){
// 中间索引
int mid = (left + right) / 2;
//左递归
sort(arr,left,mid,temp);
// 右递归
sort(arr,mid + 1,right,temp);
//合并
merge(arr,left,mid,right,temp);
}
}
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
static void merge(int[] arr,int left,int mid,int right,int temp[]){
// 初始化i,左边有序序列的初始索引
int i = left;
// 初始化j,右边有序序列的初始化索引
int j = mid + 1;
// 指向temp数组的临时索引
int t = 0;
/*
第一步
先把左右两边的有序数据按照规则填充到temp数组
直到两边的有序序列,有一边处理完成。
*/
while (i <= mid && j <= right){
/*
若左边的有序序列的当前元素,小于等于右边有序序列的当前元素
将左边的单前元素,赋值给temp数组
然后t++ ,i++
*/
if (arr[i] < arr[j]){
temp[t] = arr[i];
t++;
i++;
}else {
temp[t] = arr[j];
t++;
j++;
}
}
/*
第二步
把有剩余数据的一边的数据依次全部填充到temp
*/
while (i <= mid){
temp[t] = arr[i];
i++;
t++;
}
while (j <= right){
temp[t] = arr[j];
j++;
t++;
}
/*
第三步
将temp数组的元素拷贝到arr
*/
t = 0;
int tempLeft = left;
while (tempLeft <= right){
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
}
输出结果
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
package sort.RadixSort;
import javax.xml.transform.Source;
import java.util.Arrays;
/**
* @BelongsProject: data-structure-java
* @BelongsPackage: sort.RadisSort
* @Author: xj
* @CreateTime: 2022-08-05 10:47
* @Description: 基数排序
* @Version: 1.0
*/
public class RadixSort {
public static void main(String[] args) {
int[] arr = new int[]{53,3,542,748,14,214};
System.out.println("排序前:"+ Arrays.toString(arr));
radixSort(arr);
System.out.println("排序后:"+ Arrays.toString(arr));
}
static void radixSort(int[] arr){
//获取到数组中最大的数的位数
int max =arr[0]; //假设第一个数就是最大数
for (int ele : arr){
if (ele > max){
max = ele;
}
}
int maxLength = (max + "").length();
//第一次排序,针对每个元素的个位数进行排序处理。
// 为了防止元素入桶的时候数据溢出,给我每个桶的大小设置为arr.length
//定义二维数组,表示10个桶,每个桶就是一个一维数组。空间换时间的经典算法
int[][] bucket =new int[10][arr.length];
//记录每个桶中实际存放的元素个数,定义一个一维数组记录每个桶的元素个数
//bucketElementCount[0] 记录bucket[0]的数据个数
int[] bucketElementCount = new int[10];
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;
//放入到对应桶
bucket[digitOfElement][bucketElementCount[digitOfElement]] = arr[j];
bucketElementCount[digitOfElement]++;
}
//按照桶的顺序,根据一维数组的下标依次取出数据,放入到原数组
int index = 0;
//遍历每个bucket,并将桶中的元素放入到原数组。
for (int k = 0; k < bucketElementCount.length; k++) {
//如果桶中有数据,放入到原数组
if (bucketElementCount[k] != 0){
//遍历该桶元素
for (int l = 0; l < bucketElementCount[k]; l++) {
//取出元素放入到arr
arr[index] = bucket[k][l];
index++;
}
}
bucketElementCount[k] = 0;
}
}
}
}
输出结果
插播小广告,我的公众号,试运行哈哈哈,虽然还没有内容,但是爱学习你一定会给我点个关注吧