常见排序算法Java实现

105 阅读7分钟

常用的排序算法有冒泡排序、插入排序和快速排序等

  • 冒泡排序

  • 简单选择排序

  • 直接插入排序

  • 希尔排序

  • 堆排序

  • 归并排序

  • 快速排序

冒泡排序(n^2)

冒泡排序的特点是,每一轮循环后,最大的一个数被交换到末尾,因此,下一轮循环就可以“刨除”最后的数,每一轮循环都比上一轮循环的结束位置靠前一位。

代码实现

import java.util.Arrays;

public class Main {
public static void main(String[] args) {
   int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
   // 排序前:
   System.out.println(Arrays.toString(ns));
   for (int i = 0; i < ns.length - 1; i++) {
       for (int j = 0; j < ns.length - i - 1; j++) {
           if (ns[j] > ns[j+1]) {
               // 交换ns[j]和ns[j+1]:
               int tmp = ns[j];
               ns[j] = ns[j+1];
               ns[j+1] = tmp;
           }
       }
   }
   // 排序后:
   System.out.println(Arrays.toString(ns));
}
}

快速排序(nlogn)

基本思路:(从后往前找小于关键字的,从前往后找大于,找到的数挪到上一轮挪走数的位置上)

一次快排

  1. 先找到一个关键字,并将该关键字赋值给tmp ,通常选择第一个值作为关键字,tmp=array[0] (为接下来的操作腾出一个地方,将比关键字小的值放在该处,保证该关键字不会被覆盖)

  2. 定义一个low(即数组的第二个值 array[1]),和 height(即数组的最后一个值array[n])

  3. 第一次,从数组尾部开始向前查找(height--),判断前一个值是否小于设置的关键字tmp,比关键字大的略过,比关键字小的数放到tmp位置上( array[0] = array[height]),即将 找到比关键字小的,放到位上。

  4. 然后 height不变,开始从前往后找(low++) ,找到比关键字小的略过,找到比关键字大的,将该值放到height(上一轮该位置的值被挪走,现在将找到的值填补到被挪走的)位置上,即 array[height] = array[low]

  5. 然后从后往前查找比关键字小的,重复3步骤,如此前后前后循环迭代

  6. 直到low与height相等时,将关键字tmp的值放到该位置上 即 array[low] = tmp;

一次快排后 关键字左边均小于关键字,右边均大于关键字,递归的对左右两边进行上述排序,直到顺序全部正确为止

/**
 * 快速排序:通过一趟排序将待排记录分割成独立的两部分,其中一部分均比另一部分小,则可分别对这两部分继续进行排序,已达到整个序列有序的目的
 *
 * @author 张
 */
public class QuickSortDemo {

    public static void main(String[] args) {

        int[] arr = {50, 10, 90, 30, 70, 40, 80, 60, 20};
        System.out.println(Arrays.toString(arr));
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }

    private static void quickSort(int[] arr, int low, int height) {
//        关键字的下标 即low和height重合时的下标
        int pivot;
        if (low < height) {
//          将数组分割为两部分
            pivot = partition(arr, low, height);
//          递归对左子数组和右子数组进行排序
            quickSort(arr, low, pivot - 1);
            quickSort(arr, pivot + 1, height);
        }
    }

    /**
     * 对数组进行划分
     *
     * @param arr    数组
     * @param low    数组最小下标
     * @param height 数组最大下标
     * @return 划分完后关键字下标
     */
    private static int partition(int[] arr, int low, int height) {

//        用子表的第一个记录作为关键字
        int pivotKey = arr[low];
        while (low < height) {
//        先从后往前找大于关键字的
            while (low<height && arr[height] >= pivotKey ) {
                height--;
            }
//            将找到的大于关键字的数 直接赋值到‘空’位置上
            arr[low] =  arr[height];
//            从前往后找大于关键词
            while(low<height && arr[low] <= pivotKey){
                low++;
            }
//            将找到的小于关键字的数 直接赋值到‘空’位置上
            arr[height] = arr[low];
        }
//        最后将关键字的值赋值给最后空的位置上
        arr[low] = pivotKey;
//        返回关键字最终所在的下标
        return low;
    }
}

归并 排序(nlogn)

递归方法 :空间复杂度n+logn

基本思路:将初始数组的n个记录 看做是n个有序的子序列,每个子序列长度为1,然后两两归并,得到[n/2]个长度的为2或1的有序子序列,再两两归并,如此重复,直至得到一个长度为n的有序序列为止

/**
 * 归并排序
 * 归并意思就是合并,并入
 * @author 张
 */
public class MergeSortDemo {

    public static void main(String[] args) {

        int[] arr = {16,7,13,10,9,15,3,2,5,8,12,1,11,4,6,14};
        int[] temp = new int[arr.length];
        System.out.println(Arrays.toString(arr));

        mergeSort(arr,temp,0,arr.length-1);
        System.out.println(Arrays.toString(temp));
        System.out.println(Arrays.toString(arr));
    }

    private static void mergeSort(int[] arr,int[] temp, int low, int high) {
        int m;
        //        判断子序列长度是否为1
        if (low == high){
            temp[low] = arr[low];
        }else {
            m = (low+high)/2;
            //            先对原数组的左右子序列进行递归切分,直至每个子序列长度为1
            mergeSort(arr,temp,low,m);
            mergeSort(arr,temp,m+1,high);
            //            当每个字序列长度都为1时,开始进行合并
            merge(arr,temp,low,m,high);
        }
    }

    /**
     * 合并方法
     * @param arr 原数组
     * @param temp 临时数组
     * @param low   最左侧数的下标
     * @param m     中间数的下标
     * @param high  最右侧数的下标
     */
    private static void merge(int[] arr,int[] temp, int low, int m, int high) {
        int i =0;
        int j=m+1;
        int l = low;
        while(l<=m&&j<=high){
            //            判断左子序列的值是否小于右子序列值
            if (arr[l]<arr[j]){
                //                是的话,将左子序列的值放在temp数组中
                temp[i++] = arr[l++];
            }else {
                //                否则将右子序列的值存放在temp数组中
                temp[i++] = arr[j++];
            }
        }
        //        将左子序列的其余值依次放进temp数组中
        while(l<=m){
            temp[i++] = arr[l++];
        }

        //        将右子序列的其余值依次放入temp数组中
        while(j<=high){
            temp[i++] = arr[j++];
        }
        //        将排好序的temp数组复制给原数组,以保证向上返回时为有序数组
        for (int k = 0; k < i; k++){
            //            low+k 是为了右子序列进行复制从右边low开始
            arr[low+k] = temp[k];
        }

    }


}

堆排序(nlogn)

基本思路:

  1. 先将大小为n的未排序的序列构造成一个大顶堆

  2. 将大顶堆的根节点和未排序序列的最后一个值交换, 将最大元素"沉"到数组末端;

  3. 然后将剩余n-1个未排序的序列()调整成一个大顶堆(从上往下进行,与子节点最大值交换位置进行调整)

  4. 然后得到元素中第二大的值

  5. 如此反复执行,便能得到一个有序序列

如何构造大/小顶堆?

根据大顶堆的特性: 给个节点的值都大于或等于其左右孩子的值 即 i >= 2i(左孩子) i>=2i+1(有=右孩子)

0开头的数组为 arr[i]>=arr[2i+1] arr[i]>=arr[2i+2]

构造大顶堆:就是从下往上,从右往左,将每个非叶子节点当做根节点,将该子树调整成一个大顶堆 即:比较非叶子节点值和它的子树值,如果该节点小于其左/右子树的值就交换(意思就是将最大的值放到该节点)

调整大顶堆:从上往下进行,与子节点最大值交换位置进行调整,循环往复,直到无子节点或大于左右子节点中最大的

/**
 * 堆排序
 * 1\. 先调整为大顶堆
 * 2\. 交换根节点和未排序序列最后一个
 *
 * @author 张
 */
public class HeapSort {

    public static void main(String[] args) {

        int[] arr = {5, 1, 9, 3, 7, 4, 8, 6, 2};
        System.out.println(Arrays.toString(arr));
        //      将无序数组构建成大顶堆(从下至上,从右至左,选择非叶子结点调整结构)
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            heapAdjust(arr, i, arr.length);
        }
        //      交换堆顶元素与末尾元素后,重新调整为大顶堆结构
        for (int j = arr.length - 1; j > 0; j--) {
            //将堆顶元素与末尾元素进行交换
            swap(arr, 0, j);
            //重新对堆进行调整
            heapAdjust(arr, 0, j);
        }
        System.out.println(Arrays.toString(arr));
    }

    /**
     * 交换元素
     *
     * @param arr 数组
     * @param i 0 构造或调整成大顶堆后的根节点在数组中的下标
     * @param j 未排序序列的末尾元素下标
     */
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     * 调整为大顶堆
     *
     * @param arr    数组
     * @param i      开始的节点位置 在数组中的下标
     * @param length 数组的长度
     */
    private static void heapAdjust(int[] arr, int i, int length) {
        int temp = arr[i];
        for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
            //            判断右字节点是否大于左字节点  是的话 将k值+1(代表k为右字节点下标),k代表左子节点下标
            if (k + 1 < length && arr[k] < arr[k + 1]) {
                k += 1;
            }
            //        判断父节点值是否大于子节点的最大值 是的话,将最大值赋值到要改变的节点,将该节点在数组中的下边改变为最大子节点的下标
            if (temp < arr[k]) {
                arr[i] = arr[k];
                i = k;
            } else {
                break;
            }
        }
        //将temp值放到最终的位置
        arr[i] = temp;
    }
}