排序

135 阅读3分钟

冒泡排序

算法描述

  1. 比较相邻的元素。如果第一个比第二个大,则交换。
  2. 对每一对相邻的元素做同样的工作,从开始的第一对到结尾的最后一对,这样最后的元素就是最大的数
  3. 针对所有元素重复以上的步骤,除了最后一个
  4. 重复1-3的步骤,直到排序完成

动画演示

算法实现

@Slf4j
public class BubbleSort {

    public static void main(String[] args) {
        int[] nums = {9,3, 1, 4, 2, 7,8,6,5};
        buddle(nums);
        for (int number : nums) {
            System.out.println(number);
        }
    }

    private static void buddle(int[] nums) {
        for (int i = 0; i < nums.length - 1; i++) {
            for (int j = 0; j < nums.length - 1 - i; j++) {
                if (nums[j] > nums[j + 1]) {
                    int temp = nums[j + 1];
                    nums[j + 1] = nums[j];
                    nums[j] = temp;
                }
            }
            log.info("第{}轮冒泡结束,数组为{}",i+1, Arrays.toString(nums));
        }
    }
}

//  第1轮冒泡结束,数组为[3, 1, 4, 2, 7, 8, 6, 5, 9]
//  第2轮冒泡结束,数组为[1, 3, 2, 4, 7, 6, 5, 8, 9]
//  第3轮冒泡结束,数组为[1, 2, 3, 4, 6, 5, 7, 8, 9]
//  第4轮冒泡结束,数组为[1, 2, 3, 4, 5, 6, 7, 8, 9]
//  第5轮冒泡结束,数组为[1, 2, 3, 4, 5, 6, 7, 8, 9]
//  第6轮冒泡结束,数组为[1, 2, 3, 4, 5, 6, 7, 8, 9]
//  第7轮冒泡结束,数组为[1, 2, 3, 4, 5, 6, 7, 8, 9]
//  第8轮冒泡结束,数组为[1, 2, 3, 4, 5, 6, 7, 8, 9]

//优化点1:如果某一轮冒泡没有发生交换,则表示所有数据有序,可以结束外层循环
 private static void buddle_v1(int[] nums) {
        for (int i = 0; i < nums.length - 1; i++) {
            boolean swapped = false;
            for (int j = 0; j < nums.length - 1 - i; j++) {
                if (nums[j] > nums[j + 1]) {
                    swapped = true;
                    int temp = nums[j + 1];
                    nums[j + 1] = nums[j];
                    nums[j] = temp;
                }
            }
            log.info("第{}轮冒泡结束,数组为{}",i+1, Arrays.toString(nums));
            if (!swapped) {
                break;
            }
        }
    }
//第1轮冒泡结束,数组为[3, 1, 4, 2, 7, 8, 6, 5, 9]
//第2轮冒泡结束,数组为[1, 3, 2, 4, 7, 6, 5, 8, 9]
//第3轮冒泡结束,数组为[1, 2, 3, 4, 6, 5, 7, 8, 9]
//第4轮冒泡结束,数组为[1, 2, 3, 4, 5, 6, 7, 8, 9]
//第5轮冒泡结束,数组为[1, 2, 3, 4, 5, 6, 7, 8, 9]

//每轮冒泡时,最后一次交换索引可以作为下一轮冒泡的比较次数,减少每轮冒泡次数
 private static void buddle_v2(int[] nums) {
        int n = nums.length - 1;
        //== while(true)
        for (int i = 0; i < nums.length - 1; i++) {
            int last = 0;
            for (int j = 0; j < n; j++) {
                if (nums[j] > nums[j + 1]) {
                    last = j;
                    int temp = nums[j + 1];
                    nums[j + 1] = nums[j];
                    nums[j] = temp;
                }
            }
            n = last;
            if (n == 0) {
                break;
            }
        }
    }

选择排序

算法描述

  1. 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集
  2. 重复以上步骤,直到整个数组有序

动画演示

算法实现

@Slf4j
public class SelectionSort {

    public static void main(String[] args) {
        int[] nums = {9,3, 1, 4, 2, 7,8,6,5};
        selection(nums);
        log.info("最后的数组为{}", Arrays.toString(nums));
    }

    private static  void selection(int[] nums) {
        for (int i = 0; i < nums.length - 1; i++) {
            int min = i;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[min]) {
                    min = j;
                }
            }
            if (min != i) {
                //交换
                int temp = nums[min];
                nums[min] = nums[i];
                nums[i] = temp;
            }
        }

    }
}

与冒泡排序比较

  1. 二者平均时间复杂度都是 O(n^2)

  2. 选择排序一般要快于冒泡,因为其交换次数少

  3. 但如果集合有序度高,冒泡优于选择

  4. 冒泡属于稳定排序算法,而选择属于不稳定排序

    稳定排序指,按对象中不同字段进行多次排序,不会打乱同值元素的顺序

    不稳定排序则反之

插入排序

算法描述

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中比较(从后向前扫描)
  3. 如果当前元素小于已排序比较的元素,已排序往后移动一位
  4. 已排序的比较元素往前移动,重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 新元素插入当前位置
  6. 重复2-5的步骤

动画演示

算法实现

@Slf4j
public class InsertionSort {

    public static void main(String[] args) {
        int[] nums = {9,3, 1, 4, 2, 7,8,6,5};
        insertion(nums);
        log.info("最后的数组为{}", Arrays.toString(nums));
    }

    private static void insertion(int[] nums) {

        for (int i = 1; i < nums.length; i++) {
            int current = nums[i];
            int j = i;
            while (j > 0) {
                if (current < nums[j - 1]) {
                    nums[j] = nums[j - 1];
                    j--;
                } else {
                    break;
                }
            }
            nums[j] = current;
        }
    }
}

希尔排序

算法描述

希尔排序是插入排序的一种,又称“缩小增量排序”,是插入排序算法的一种更高效的改进版本。

  1. 首先选取一个间隙序列,如 (n/2,n/4 … 1),n 为数组长度

  2. 每一轮将间隙相等的元素视为一组,对组内元素进行插入排序,目的有二

    ① 少量元素插入排序速度很快

    ② 让组内值较大的元素更快地移动到后方

  3. 当间隙逐渐减少,直至为 1 时,即可完成排序

动画演示

希尔排序.gif

算法实现

@Slf4j
public class ShellSort {

    public static void main(String[] args) {
        int[] nums = {9, 3, 1, 4, 2, 7, 8, 6, 5};
        shell(nums);
        log.info("最后的数组为{}", Arrays.toString(nums));

    }

    private static void shell(int[] nums) {
        int length = nums.length;
        for (int gap = length / 2; gap >= 1; gap = gap / 2) {
            // 注意:这里和动图演示的不一样,动图是分组执行,实际操作是多个分组交替执行
            for (int i = gap; i < nums.length; i++) {
                int current = nums[i];
                int j = i;
                while (j > 0) {
                    if (nums[j] < nums[j - gap]) {
                        nums[j] = nums[j - gap];
                        j = j - gap;
                    } else {
                        break;
                    }
                }
                nums[j] = current;
            }
        }
    }
}

归并排序

算法描述

  1. 尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止。
  2. 将相邻的两个子组进行合并成一个有序的大组。
  3. 不断的重复步骤2,直到最终只有一个组为止。

动画演示

算法实现

@Slf4j
public class MergeSort {

    public static void main(String[] args) {
        int[] nums = {9, 3, 1, 4, 2, 7, 8, 6, 5};
        sort(nums);
        log.info("最后的数组为{}", Arrays.toString(nums));

    }

    public static void sort(int[] nums) {
        int[] temp = new int[nums.length];
        sort(nums, 0, nums.length - 1, temp);
    }

    private static void sort(int[] nums, int left, int right, int[] temp) {

        if (left < right) {
            int mid = (left + right) >>> 1;
            sort(nums, left, mid, temp);
            sort(nums, mid + 1, right, temp);
            merge(nums, left, mid, right, temp);
        }
    }

    //3个指针的实现
    private static void merge(int[] nums, int left, int mid, int right, int[] temp) {
        int lo = left;//左序列指针
        int hi = mid + 1;//右序列指针
        int tempIndex = left;//临时数组指针
        while (lo <= mid && hi <= right) {
            if (nums[lo] < nums[hi]) {
                temp[tempIndex++] = nums[lo++];
            } else {
                temp[tempIndex++] = nums[hi++];
            }
        }
        //上面的循环结束后,如果退出循环的条件是i<=mid,则证明左边小组中的数据已经归并完毕,如果退出循环的条件是j<=right,则证明右边小组的数据已经填充完毕;
        //如果左边还有剩下的,将左边剩余元素填充进temp中
        while (lo <= mid) {
            temp[tempIndex++]  = nums[lo++];
        }
        //如果右边还有剩下的,将右边剩余元素填充进temp中
        while (hi <= right) {
            temp[tempIndex++]  = nums[hi++];
        }

        log.info("temp数组为{}", Arrays.toString(temp));
        //将temp中的元素全部拷贝到原数组中
        for (int index = left; index <= right; index++) {
            nums[index] = temp[index];
        }

    }

}


快速排序

算法描述

  1. 首先设定一个分界值,通过该分解值将数组分成左右两部分
  2. 将大于或等于分界值的数据放在数据右边,小于分界值的数据放到数组的左边。
  3. 左边和右边的数据各自做排序。对于左侧的数组,又可以取一个分界值,将数组分成左右两部分,左边放较小值,右边放较大值。右边也同样
  4. 重复上面过程。

动画演示

算法实现

@Slf4j
public class QuickSort {

    public static void main(String[] args) {
        int[] nums = {9, 3, 1, 4, 2, 7, 8, 6, 5};
        quick(nums, 0, nums.length - 1);
        log.info("最后的数组为{}", Arrays.toString(nums));

    }

    public static void quick(int[] a, int l, int h) {
        if (l >= h) {
            return;
        }
        int p = partition(a, l, h); // p 索引值
        quick(a, l, p - 1); // 左边分区的范围确定
        quick(a, p + 1, h); // 左边分区的范围确定
    }

    private static int partition(int[] a, int l, int h) {
        int pv = a[l];//左边作为基本点
        int left = l;
        int right = h;
        while (left < right) {
            // right 从右找小的
            while (left < right && a[right] > pv) {
                right--;
            }
            // left 从左找大的
            while (left < right && a[left] <= pv) {
                left++;
            }
            swap(a, left, right);
        }
        swap(a, l, right);
        log.info(Arrays.toString(a) + " right=" + right);
        return right;
    }
}

参考资料

www.cnblogs.com/onepixel/p/… 十大经典排序算法(动图演示)

www.cnblogs.com/chengxiao/p… 图解排序算法

www.bilibili.com/video/BV1iJ… java 数据结构和算法

www.bilibili.com/video/BV15b… java 面试宝典