出现的必要性
简单的插入排序可能存在的问题.
数组 arr = {2,3,4,5,6,1} 这时需要插入的数 1(最小), 这样的过程是:
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
结论: 当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响。
——尚硅谷
基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含
的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
希尔排序有两种方式:交换法,移动法。
——尚硅谷
简单来说:分而治之+插入排序
动画演示
希尔排序示例
希尔排序-交换法
Java代码:
package sort;
import java.util.Arrays;
import java.util.Scanner;
public class ShellSort {
public static void main(String[] args) {
int[] arr = inputArray();
System.out.printf("输入的数组为:%s\n",Arrays.toString(arr));
shellSortExchange(arr);
}
/**
* 输入数组
* @return
*/
public static int[] inputArray(){
int s;
System.out.printf("请输入数组的元素个数:");
Scanner scanner = new Scanner(System.in);
s = scanner.nextInt();
int[] arr = new int[s];
System.out.println("请输入数组的数值,以回车结束一个值的输入:");
for (int i = 0; i < s; i++) {
arr[i] = scanner.nextInt();
}
return arr;
}
/**
* 希尔排序-交换法
* @param arr
*/
public static void shellSortExchange(int[] arr){
int temp,count=0;
for (int page = arr.length/2; page > 0; page/=2) {//确定增量
count++;
for (int i = page; i < arr.length; i++) {//确定每一轮的循环次数
for (int j = i-page; j >= 0; j-=page) {//确定要进行对比的组中的每一个元素
if(arr[j] > arr[j+page]){
temp = arr[j];
arr[j] = arr[j+page];
arr[j+page] = temp;
}
}
System.out.printf("第%d轮第%d次的排序:%s\n",count,i-page+1,Arrays.toString(arr));
}
System.out.printf("第%d轮排序完成后:%s\n\n",count,Arrays.toString(arr));
}
}
}
/* 输入的测试数据
8
9
1
7
2
3
5
4
6
0
*/
希尔排序-位移法
java代码:
package sort;
import java.util.Arrays;
import java.util.Scanner;
public class ShellSort {
public static void main(String[] args) {
int[] arr = inputArray();
System.out.printf("输入的数组为:%s\n",Arrays.toString(arr));
shellSortMove(arr);
}
/**
* 输入数组
* @return
*/
public static int[] inputArray(){
int s;
System.out.printf("请输入数组的元素个数:");
Scanner scanner = new Scanner(System.in);
s = scanner.nextInt();
int[] arr = new int[s];
System.out.println("请输入数组的数值,以回车结束一个值的输入:");
for (int i = 0; i < s; i++) {
arr[i] = scanner.nextInt();
}
return arr;
}
/**
* 希尔排序-位移法
* @param arr
*/
public static void shellSortMove(int[] arr){
int count=0;
for (int page = arr.length/2; page > 0; page/=2) {
count++;
for (int i = page; i < arr.length; i++) {
int tempIndex = i;
int temp = arr[tempIndex];
while (tempIndex-page>=0 && temp < arr[tempIndex-page]){
arr[tempIndex] = arr[tempIndex-page];
tempIndex-=page;
}
arr[tempIndex]=temp;
}
System.out.printf("第%d轮排序完成后:%s\n",count,Arrays.toString(arr));
}
}
}
/* 输入的测试数据
8
9
1
7
2
3
5
4
6
0
*/
代码分析
/**
* 希尔排序-位移法
* @param arr
*/
public static void shellSortMove(int[] arr){
int count=0;
for (int page = arr.length/2; page > 0; page/=2) {
count++;
for (int i = page; i < arr.length; i++) {
int tempIndex = i;
int temp = arr[tempIndex];
while (tempIndex-page>=0 && temp < arr[tempIndex-page]){
arr[tempIndex] = arr[tempIndex-page];
tempIndex-=page;
}
arr[tempIndex]=temp;
}
System.out.printf("第%d轮排序完成后:%s\n",count,Arrays.toString(arr));
}
}
两种希尔的思想是一样的,但是第二种方式会更能体现出插入排序的思想,第一种是每比较一个元素若是逆序状态就会进行交换,而第二种是沿用了插入排序的思想,直到直到合适的位置才进行交换。
在第一层循环中,用于确定每一次增量,就是说多少位多少位一组,以数组长度除2的方式确定每一次的增量。
第三层循环中确定每一个增量需要进行的循环次数。
第三层循:每增加一个元素都会对一整组的数据进行插入排序。例如有一个数组有100个元素,此时的增量为2,那么希尔排序会将这100个元素分为2组,那么1,3,5,7,9,11…99会处于一组,然后在1与3中要进行一次插入排序,在1,3,5中也要进行一次插入排序(因为新增进来的元素可能会对于整个队列种产生影响,所以整组的元素都要进行比较)
时间复杂度:O(n3)
空间复杂度:O(1)
稳定性:不稳定
两个值虽然会相同,但是在构造组的时,本来处于后面位置的元素可能跑到前面去。
时间测试
package sort;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class ShellSort {
public static void main(String[] args) {
int num;//数组个数
int[] arr, arr2;
Scanner scanner = new Scanner(System.in);
System.out.printf("请输入要随机生成数组的个数:");
num = scanner.nextInt();
arr = randomArrays(num);
arr2 = Arrays.copyOf(arr,arr.length);
System.out.printf("希尔插入排序前的时间:");
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(now.format(format));
shellSortExchange(arr);
System.out.printf("希尔插入排序后的时间:");
now = LocalDateTime.now();
System.out.println(now.format(format));
System.out.printf("\n希尔移动排序前的时间:");
now = LocalDateTime.now();
System.out.println(now.format(format));
shellSortMove(arr2);
System.out.printf("希尔移动排序后的时间:");
now = LocalDateTime.now();
System.out.println(now.format(format));
}
/**
* 随机生成数组函数
* @param num 数组个数
* @return
*/
public static int[] randomArrays(int num){
Random rd = new Random();
int[] arr = rd.ints(num).toArray();
return arr;
}
/**
* 希尔排序-交换法
* @param arr
*/
public static void shellSortExchange(int[] arr){
int temp,count=0;
for (int page = arr.length/2; page > 0; page/=2) {//确定增量
count++;
for (int i = page; i < arr.length; i++) {//确定每一轮的循环次数
for (int j = i-page; j >= 0; j-=page) {//确定要进行对比的组中的每一个元素
if(arr[j] > arr[j+page]){
temp = arr[j];
arr[j] = arr[j+page];
arr[j+page] = temp;
}
}
}
}
}
/**
* 希尔排序-位移法
* @param arr
*/
public static void shellSortMove(int[] arr){
int count=0;
for (int page = arr.length/2; page > 0; page/=2) {
count++;
for (int i = page; i < arr.length; i++) {
int tempIndex = i;
int temp = arr[tempIndex];
while (tempIndex-page>=0 && temp < arr[tempIndex-page]){
arr[tempIndex] = arr[tempIndex-page];
tempIndex-=page;
}
arr[tempIndex]=temp;
}
}
}
}
从排序时间前后的,时间上能够看出移动的速度明显能够比插入要快很多,数据越多差距越明显。