1.9.6 希尔排序
介绍:
- 实现大体分为交换法实现和插入法实现
- 希尔排序是将原数组划分为多组,然后对每一组进行排序。循环多次,最终让整个数组有序
交换法实现:
-
交换法咋一看好像是有点像冒泡,但是千万别将这个与冒泡弄混了。冒泡是在一次for循环内就确定一个极值,并将该极值放置到一极(最左或最右)经过arrays.length-1次循环最终将数组排序好。
-
而交换法排序是三次for循环,外层循环负责调整gap分组次数,中层for循环负责对原数组每个元素进行遍历,指针i指向当前遍历元素,最重要的内层循环每次对中层循环i指向的那个分数组,自index=0到index=i-gap下标的元素进行排序。
- 内层循环的一个例子
// 排序使用交换法 比如一组元素[3,5,1,6,2]
// 1. index = 0,3 与 5 比较[3,5,1,6,2] currentIndex=0
// 2. index = 1,5 与 1 比较交换[3,1,5,6,2],currentIndex=1;再currentIndex-1回退进行3与1比较交换 [1,3,5,6,2] 直到currentIndex=0为止
// 3. index = 2,5 与 6 比较[1,3,5,6,2],回退3与5比较,再回退1与3比较 直到currentIndex=0
// 4. index = 3,6 与 2比较交换[1,3,5,2,6],回退比较交换[1,3,2,5,6].......直到[1,2,3,5,6] currentIndex=0为止
- gap:初值为array.length/2 每次循环gap /= 2,直到gap <= 0为止
- gap值既是每次分组的组数也是每一组组内元素之间的步长
import java.util.Arrays;
public class shellSort {
public static void sort(int[] array){
int temp;
int count = 0;
// 每轮排序完gap都会/2,gap既是一轮划分数组个数也是同组元素之间的步长
for (int gap=array.length/2;gap > 0;gap /= 2){
// 接下来的嵌套循环是为了遍历各组元素并对各组元素进行排序 保证该组元素有序
for (int i=gap;i<array.length;i++){
// 排序使用交换法 比如一组元素[3,5,1,6,2]
// 1. index = 0,3 与 5 比较[3,5,1,6,2] currentIndex=0
// 2. index = 1,5 与 1 比较交换[3,1,5,6,2],currentIndex=1;再currentIndex-1回退进行3与1比较交换[1,3,5,6,2] 直到currentIndex=0为止
// 3. index = 2,5 与 6 比较[1,3,5,6,2],回退3与5比较,再回退1与3比较 直到currentIndex=0
// 4. index = 3,6 与 2比较交换[1,3,5,2,6],回退比较交换[1,3,2,5,6].......直到[1,2,3,5,6] currentIndex=0为止
for(int j=i-gap;j >= 0;j -= 5){
if(array[j] > array[j+gap]){
temp = array[j];
array[j] = array[j+gap];
array[j+gap] = temp;
}
}
}
System.out.println("希尔排序第"+(++count)+"趟排序: "+ Arrays.toString(array)+" gap = "+gap);
}
}
public static void main(String[] args) {
int[] array = {8,9,1,7,2,3,5,4,6,0};
sort(array);
}
}
输出结果:
array1: [8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
希尔排序第1趟排序: [3, 5, 1, 6, 0, 8, 9, 4, 7, 2] gap = 5
希尔排序第2趟排序: [0, 5, 1, 6, 3, 4, 7, 2, 9, 8] gap = 2
希尔排序第3趟排序: [0, 1, 3, 4, 5, 6, 2, 7, 8, 9] gap = 1
插入法实现:
对分组的每一组的组内进行直接插入排序,得到有序数组
public static void shellSort2(int[] array){
int count = 0;
for(int gap=array.length/2;gap>0;gap /= 2){
for (int i=gap;i<array.length;i++){
int j=i;
int temp = array[j];
if(array[j] < array[j-gap]){ // 只有当后一个元素小于前一个元素才有移位的意义
while (j-gap >=0 && temp < array[j-gap]){
// 将前一位元素后移一位
array[j] = array[j-gap];
j -= gap;
}
// 跳出循环后代表找到插入元素位置了 直接赋值插入
array[j] = temp;
}
}
System.out.println("希尔排序第"+(++count)+"趟排序: "+ Arrays.toString(array)+" gap = "+gap);
}
}
测试用例与输出结果:
array2: [18, 19, 11, 17, 12, 13, 15, 14, 16, 10]
希尔排序第1趟排序: [13, 15, 11, 16, 10, 18, 19, 14, 17, 12] gap = 5
希尔排序第2趟排序: [10, 12, 11, 14, 13, 15, 17, 16, 19, 18] gap = 2
希尔排序第3趟排序: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] gap = 1
1.9.7 快速排序
介绍:
实现代码与输出结果:
- 个人认为难点在于当array[tempLeft/tempRight] == pivot时的指针移动 和 在划分结束判断tempLeft == tempRight这两部分
import java.util.Arrays;
// 个人认为难点在于当array[tempLeft/tempRight] == pivot时的指针移动 和 在划分结束判断tempLeft == tempRight这两部分
public class quicklySortDemo {
public static void quicklySort(int[] array,int left,int right){
int tempLeft = left; // 左指针
int tempRight = right; //右指针
int pivot = array[(left+right)/2]; // 中间值
while (tempLeft < tempRight){
while (array[tempLeft] < pivot)
tempLeft++;
while (array[tempRight] > pivot)
tempRight--;
// 判断是否划分结束,跳出循环
if (tempLeft >= tempRight)
break;
// 交换异常数据
int temp = array[tempLeft];
array[tempLeft] = array[tempRight];
array[tempRight] = temp;
// 判断交换完数据是否为pivot 否则要进行指针移位 相等的话那指针就不会动了 这可以不行 事情还没做完呢
if (array[tempLeft] == pivot)
tempRight--; // 右指针前移
if (array[tempRight] == pivot)
tempLeft++; // 左指针右移
}
System.out.println("划分: "+ Arrays.toString(array));
// 需要先判断下l==r 否则stack会溢出(左递归和右递归有部分范围重合了)
if(tempLeft == tempRight){
tempRight--;
tempLeft++;
}
// 跳出外层循环后 pivot原数组已划分好 开始对两侧数组进行递归划分
if(left < tempRight){ // 左递归
quicklySort(array,left,tempRight);
}
if (right > tempLeft) // 右递归
quicklySort(array,tempLeft,right);
}
public static void main(String[] args) {
int[] array2 = {18,19,11,17,12,13,15,14,16,10};
quicklySort(array2,0,array2.length-1);
}
}
输出结果:
划分: [10, 11, 12, 17, 19, 13, 15, 14, 16, 18]
划分: [10, 11, 12, 17, 19, 13, 15, 14, 16, 18]
划分: [10, 11, 12, 14, 13, 15, 19, 17, 16, 18]
划分: [10, 11, 12, 13, 14, 15, 19, 17, 16, 18]
划分: [10, 11, 12, 13, 14, 15, 16, 17, 19, 18]
划分: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
1.9.10 归并排序
介绍:
-
一次合并示例
思路: -
由于需要反复拆分合并,初步选择采用递归来实现
-
根据归并排序的思想,先要将原数组全部拆分后才能开始合并,所以应该写一个方法专门处理合并
-
递归调用的是拆分方法,在拆分函数的最后调用合并merge方法(合并完一次刚好递归返回到上一层继续合并),这样可以实现从头到位拆分,再从尾到头合并
-
如下图
-
在合并过程中使用一个额外数组temp来承载排序后的合并数组,然后将temp数组对应的元素拷贝到原数组内
-
对数组的拆分与合并在实现上实际并不是真正的将元素拆分为其他数组然后再进行合并,这样对空间的浪费过于巨大,本质上是对指针的巧妙运用,只需要提供一个额外temp数组的空间就可以实现排序
实现代码与输出结果:
import java.util.Arrays;
public class mergeSortDemo {
private int[] array;
public mergeSortDemo(int[] array){
this.array = array;
}
public void mergeSort(int left,int right,int[] temp){
if(left < right){
int mid = (left+right)/2; // 开始拆分数组
// 开始左递归
mergeSort(left,mid,temp);
// 开始右递归
mergeSort(mid+1,right,temp);
// 递归结束 开始合并
merge(left,mid,right,temp);
}
// 当递归到left >= right 会自动结束递归 然后开始返回
}
/**
* 实现分解后数组的合并
* @param left 指向数组的左指针
* @param mid 指向数组的中间指针,指向右数组的前一位
* @param right 指向右数组的最后一位
* @param temp 临时数组,用于临时存放合后的数组
*/
public void merge(int left,int mid,int right,int[] temp){
int i = left;
int j = mid+1;
int t=0; // 指向temp数组下标的指针
while (i <= mid && j <= right){
if(array[i] <= array[j]){
temp[t] = array[i];
i++;
}else {
temp[t] = array[j];
j++;
}
t++;
}
while (i <= mid){
temp[t] = array[i];
i++;
t++;
}
while (j <= right){
temp[t] = array[j];
j++;
t++;
}
// 开始拷贝数据到array
int tempLeft = left;
t = 0;
System.out.print("合并结果: [ ");
while (tempLeft <= right){
array[tempLeft] = temp[t];
System.out.print(array[tempLeft]+" ");
t++;
tempLeft++;
}
System.out.print("] \n");
}
public static void main(String[] args) {
int[] array = new int[]{3,1,9,2,8,4,7,6,5,100,12,11};
mergeSortDemo mergeSortDemo1 = new mergeSortDemo(array);
mergeSortDemo1.mergeSort(0,array.length-1,new int[array.length]);
System.out.println(Arrays.toString(array));
}
}
输出结果:
合并结果: [ 1 3 ]
合并结果: [ 1 3 9 ]
合并结果: [ 2 8 ]
合并结果: [ 2 4 8 ]
合并结果: [ 1 2 3 4 8 9 ]
合并结果: [ 6 7 ]
合并结果: [ 5 6 7 ]
合并结果: [ 12 100 ]
合并结果: [ 11 12 100 ]
合并结果: [ 5 6 7 11 12 100 ]
合并结果: [ 1 2 3 4 5 6 7 8 9 11 12 100 ]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 100]
1.9.11 基数排序
介绍:
思路:
- 下图是一轮排序的示例图
- 第二轮第三轮…第n轮同理,n为原数组中最大元素的位数
实现思路:
- 1.得到数组中位数最大数的位数n,n=基数排序需要排序的轮数
- 2.定义一个二维数组buckets[10][array.length]每一行是一个一维数组对应下标从0-9的数字 将每一行理解为一个桶,桶内存放着对应元素
- 3.定义一个一维数组bucketsCount[10] 用于存储二维数组中对应行数的桶内当前元素的个数
- 4.开始多轮排序for(int i=0;i<n;i++)
- 遍历原数组,获取原数组的每个元素的倒数第i位数字**(个,十,百,千,万…)**的的值, p = x / 10^i % 10
- 根据数字值的不同将元素加入到对应的桶内
- 原数组遍历结束,将在桶内元素按照桶的排序将元素重新加入到原数组内,实现一次排序
- 每轮循环结束 都需要将bucketCount清空
- 注意事项:
- 因为基数排序算法是牺牲空间来换取时间的。所以所以如果排序的数据过于巨大(比如上亿个数据之类的),将会导致java的JVM虚拟机报错内存不足。
- 本代码实例的基数排序只能排序非负整数
实现代码与输出结果:
import java.util.Arrays;
public class RadixSort {
public static void radixSort(int[] array){
// 1. 得到数组中最大元素的位数
int max = array[0];
int n = 0; // 数组最大元素的位数
for (int value : array) {
if (value > max)
max = value;
}
while (max > 0){
max /= 10;
n++;
}
// System.out.println(n);
// 2. 定义桶与记录桶内元素个数的一维数组
int[][] buckets = new int[10][array.length];
int[] bucketCounts = new int[10];
int tempN;
int index;
for(int i=0;i < n;i++){
// 获取原数组每个元素的倒数第i个数字
for (int k : array) {
tempN = (k / (int) Math.pow(10,i)) % 10;
// 将该数字根据大小加入到对应的桶内 桶内元素计数也+1
buckets[tempN][bucketCounts[tempN]] = k;
bucketCounts[tempN]++;
}
index = 0;
// 加入元素结束 再将所有元素按照桶的顺序加入到原数组内
for (int j=0;j<bucketCounts.length;j++){
for(int p=0;p<bucketCounts[j];p++){
array[index] = buckets[j][p];
index++;
}
}
// 每轮循环结束 都需要将bucketCount清空
for(int p=0;p<10;p++)
bucketCounts[p] = 0;
System.out.println("第"+i+"轮排序: "+ Arrays.toString(array));
}
}
public static void main(String[] args) {
int[] array = new int[]{9,1,30,23,456,8546,154,1111,1223};
radixSort(array);
}
}
输出结果:
第0轮排序: [30, 1, 1111, 23, 1223, 154, 456, 8546, 9]
第1轮排序: [1, 9, 1111, 23, 1223, 30, 8546, 154, 456]
第2轮排序: [1, 9, 23, 30, 1111, 154, 1223, 456, 8546]
第3轮排序: [1, 9, 23, 30, 154, 456, 1111, 1223, 8546]