2021-10-28 六、排序(中:简单插入、希尔、快排)

1. 简单插入排序

1.1 基本介绍

对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。

1.2 设计思路

  1. 首先把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素。(有序表在前,无序表在后
  2. 排序过程中每次从无序表中取出第一个元素,把它依次与有序表元素进行比较,然后将它插入到有序表中的适当位置,使之成为新的有序表。
    在这里插入图片描述

1.3 代码实现

public class Demo18 extends TimeTemplate {

    public static void main(String[] args) {
        int[] arr = {3, 9, -1, 10, 20};
        new Demo18().sendTime(arr, "插入排序");
        System.out.println(Arrays.toString(arr));

        // 性能测试
        int[] arr2 = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr2[i] = (int) (Math.random() * 8000000);
        }
        new Demo18().sendTime(arr2, "插入排序");
    }

    /**
     * 插入排序 时间复杂度:O(N^2)
     */
    @Override
    public void code(int[] arr) {
        int tempArr;
        int tempIndex;
        for (int i = 1; i < arr.length; i++) {
            // 存放无序数组第一个数
            tempArr = arr[i];
            // 存放有序数组最后一个数字的坐标
            tempIndex = i - 1;
            /*
              循环条件:
              1. tempIndex >= 0 判断坐标的临界值
              2. tempArr <= arr[tempIndex] 判断无序数组第一个数字是否小于等于有序数组
              如果两个条件成立,有序数组就后移
             */
            while (tempIndex >= 0 && tempArr < arr[tempIndex]) {
                arr[tempIndex + 1] = arr[tempIndex];
                tempIndex--;
            }
            // 如果经过while循环,则需要把tempIndex--这步复原,否则不动
            if (tempIndex + 1 != i) {
                arr[tempIndex + 1] = tempArr;
            }
        }
    }

}

2. 希尔排序

2.1 基本介绍

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。

2.2 设计思路

引入步长(gap)的概念,通过步长把原始数组(长度为n)分为(n/gap)个小组。

  1. 第一次分组的步长gap = n/2,把小组内的数元素进行排序
  2. 缩小步长gap = gap/2,再进行组内排序…
  3. 直到整体为一组的时候进行最终排序在这里插入图片描述在这里插入图片描述

2.3 代码实现(交换法)

public class Demo19 extends TimeTemplate {
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        new Demo19().sendTime(arr, "希尔排序(交换法)");

		// 性能测试
        int[] arr2 = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr2[i] = (int) (Math.random() * 8000000);
        }
        new Demo19().sendTime(arr2, "希尔排序(交换法)");
    }

    /**
     * 希尔排序 时间复杂度:O(N*log2N)
     */
    @Override
    public void code(int[] arr) {
        int temp;
        // 定义步长每次都是之前的一半
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            // 通过步长把数组进行分组,在组内进行排序
            for (int i = gap; i < arr.length; i++) {
                for (int j = i - gap; j >= 0; j -= gap) {
                    // 如果小组内前面的元素比较大则进行交换
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
        }
    }
}

2.4 代码实现(移位法)

通过上面的测试,我们发现使用交换法的希尔排序甚至比简单插入排序更慢,主要是因为我们每次发现需要排序的数字就进行交换,这里需要大量的时间开销。因此我们尝试通过移位的方式,把待交换数据直接放在最终的位置。

public class Demo20 extends TimeTemplate {
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        new Demo20().sendTime(arr, "希尔排序(移位法)");
        int[] arr2 = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr2[i] = (int) (Math.random() * 8000000);
        }
        new Demo20().sendTime(arr2, "希尔排序(移位法)");
    }


    /**
     * 希尔排序 时间复杂度:O(N*log2N)
     */
    @Override
    public void code(int[] arr) {
        int temp, j;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {
                // 可以参考直接插入排序
                // 保存待插入的索引
                j = i;
                // 保存待插入的索引的值
                temp = arr[j];
                // 只有 当前数 小于 前面的数 才进入循环
                if (arr[j] < arr[j - gap]) {
                    while (j - gap >= 0 && temp < arr[j - gap]) {
                        // 前面的数在组内后移,因为已经提前把待插入数据放到temp,所以不用担心被覆盖
                        arr[j] = arr[j - gap];
                        // 继续往前探
                        j -= gap;
                    }
                    // 等比当前数大的数都后移了之后,再把当前数放在最终的位置
                    arr[j] = temp;
                }
            }
        }
    }
}

3. 快速排序

3.1 基本介绍

快速排序(Quicksort)是对冒泡排序的一种改进。因为老韩讲得不太行,所以这里基本是自己的思路,如有不妥之处还请大佬们指出。

3.2 设计思路

  1. 设置最左的位置为基准值
  2. 分别从左到右、从右到左扫描整个数组,进行排序,从而将数组分成两部分,一部分比基准值小,一部分比基准值大,
  3. 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

3.3 代码实现(交换法)

交换法是使用了冒泡的思想

  1. 首先设置最左的位置为基准值
  2. 循环查找要交换的位置:
    2.1 从左到右扫描数组,直到找到比基准值大的数组下标
    2.2 从右到左扫描数组,直到找到比基准值小的数组下标
    2.3 如果left小于right说明left下标的数应该比right下标的数小,进行交换
    2.4 否则就是 left>=right ,说明以当前基准值比较的数组已经将数组根据left(right)将数组分成了比基准值小和比基准值大两个部分。[start,left-1] 这部分比基准值小; [right+1,end] 这部分比基准值大
  3. 跳出循环之后,right指向的是比基准值小的数,所以将基准值与right进行交换
  4. 递归调用
public class Demo21 {
    /**
     * 交换方法
     */
    private static void swap(int[] data, int i, int j) {
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    /**
     * 快速排序,交换法
     */
    public static void quickSort2(int[] data, int start, int end) {
        if (start < end) {
            // 以最左边的数为基准值
            int base = data[start];
            // 设置最左下标
            int left = start + 1;
            // 设置最右下标
            int right = end;
            // 循环查找要交换的位置
            while (true) {
                // 从左到右扫描数组,直到找到比基准值大的数组下标
                while (left < end && data[left] <= base) {
                    left++;
                }
                // 从右到左扫描数组,直到找到比基准值小的数组下标
                while (right > start && data[right] >= base) {
                    right--;
                }
                // 如果left小于right说明left下标的数应该比right下标的数小,进行交换
                if (left < right) {
                    swap(data, left, right);
                } else {
                    // left>=right 说明以当前基准值比较的数组已经将数组根据left(right)
                    // 将数组分成了比基准值小和比基准值大两个部分
                    // [start,left-1] 这部分比基准值小; [right+1,end] 这部分比基准值大
                    break;
                }
            }
            // 因为跳出循环之后,right指向的是比基准值小的数,所以将基准值与right进行交换
            swap(data, start, right);
            //递归调用
            quickSort2(data, start, right - 1);
            quickSort2(data, right + 1, end);
        }
    }
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 2, 2, 3, 5, 4, 6, 0};
        quickSort2(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
}

3.4 代码实现(插值法)

交换法和希尔排序一样,也会造成多次交换,浪费时间,这里借鉴插入法的思想,用插值来代替交换。

  1. 首先设置最左的位置为基准值
  2. 循环查找数组元素进行插值
    2.1 从右到左扫描数组,直到找到比基准值小的数组下标
    2.2 如果left<right 将右边比中枢值小的数放到左边,并将left的下标后移,保证right后面的数比基准值大
    2.3 从左到右扫描数组,直到找到比基准值大的数组下标
    2.4 将左边比中枢值大的数放到右边,并将right的下标前移
  3. 当left == right的时候,将一开始取出的中枢值放入
  4. 递归调用
示例:
 a[0] a[1] a[2] a[3] a[4] a[5]
 (30)  40   60   10   20   50   初始状态,pivot = 30
 '30l' 40   60   10  (20r) 50  第一趟,从右往左找到小于pivot的数,将值放到left的位置,并把left的下标++ right = 4, left = 1
  20  (40l) 60   10  '20r' 50  第二趟,从左往右找到大于pivot的数,将值放到right的位置,并把right的下标-- right = 3, left = 1
  20  '40l' 60  (10r) 40   50  第三趟,右往左找到小于pivot的数,将值放到left的位置,并把left的下标++ right = 3, left = 2
  20   10  (60l)'10r' 40   50  第四趟,从左往右找到大于pivot的数,将值放到right的位置,并把right的下标-- right = 2, left = 2
  20   10  (60lr)60   40   50  第五趟,right == left  把pivot值放入到当前位置(因为当前的值已经让入到其他位置)
  20   10   30(p)60   40   50 保证左边比pivot小,右边比pivot大,然后左右分别递归
public class Demo21 {
    /**
     * 快速排序,插值法
     */
    public static void quickSort(int[] a, int l, int r) {
        if (l < r) {
            int left, right, pivot;
            left = l;
            right = r;
            // 中枢位置的值为第一个 这个比较简单
            pivot = a[left];
            while (left < right) {
                // 从右到左扫描数组,直到找到比基准值小的数组下标
                while (left < right && a[right] > pivot) {
                    right--;
                }
                // 将右边比中枢值小的数放到左边,并将left的下标后移
                if (left < right) {
                    a[left++] = a[right];
                }
                // 从左到右扫描数组,直到找到比基准值大的数组下标
                while (left < right && a[left] < pivot) {
                    left++;
                }
                // 将左边比中枢值大的数放到右边,并将right的下标前移
                if (left < right) {
                    a[right--] = a[left];
                }
            }
            // 这里是当left == right的时候,将一开始取出的中枢值放入
            a[left] = pivot;
            // 递归调用
            quickSort(a, l, left - 1);
            quickSort(a, left + 1, r);
        }
    }
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 2, 2, 3, 5, 4, 6, 0};
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
}

3.5 代码实现(中值法)

这个方法需要考虑的情况太多,所以不太推荐这个方法来实现快排。
具体的思路都在代码注释中,偷个懒就不整理到外面来了,思路和上面的交换法差不多,只是基准值的位置变了。

public class Demo21 {
    /**
     * 快速排序 中值交换法
     */
    public static void code(int[] arr, int left, int right) {
        if (left < right) {
            // 左下标
            int l = left;
            // 右下标
            int r = right;
            // pivot 基准值,这里设置为中轴
            int pivot = arr[(right + left) / 2];
            // 左下标始终小于右下标
            while (l < r) {
                // 从左往右遍历数组,直到找到大于等于pivot的值
                while (arr[l] < pivot) {
                    l++;
                }
                // 从右往左遍历数组,直到找到小于等于pivot的值
                while (arr[r] > pivot) {
                    r--;
                }
                // 如果l<r,则左边大于pivot的数应该和右边小于pivot的数进行交换,否则直接跳出循环
                if (l < r) {
                    swap(arr, l, r);
                } else {
                    break;
                }
                // 若交换后发现arr[l] = arr[pivot](说明下一个循环l不会--),r需要前移,
                // 因为不前移的话如果r的前一个为pivot值或者小于pivot的值就会陷入死循环
                if (arr[l] == pivot) {
                    r--;
                }
                // 同理,若r前移后发现:arr[r] = arr[pivot](说明下一个循环r不会--)则后移l,
                // 否则会陷入死循环
                if (arr[r] == pivot) {
                    l++;
                }
            }

            // 若 l==r,这种情况说明[left,l]和[r,right]已经按照规则分为了两个部分
            // 但是这时必须将l和r错开,缩减下面递归[left,r]和[l,right]的范围,
            // 否则会递归过程中会死循环导致栈溢出。
            // 这里可能不好理解,建议打断点自己多看几次
            if (l == r) {
                l++;
                r--;
            }
            // 向左递归
            code(arr, left, r);
            // 向右递归
            code(arr, l, right);
        }
    }
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 2, 2, 3, 5, 4, 6, 0};
        code(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
}
上一篇总目录下一篇
五、排序(上:冒泡、简单选择)数据结构篇(Java)目录七、排序(下:归并、基数)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值