十大经典排序算法

1. 选择排序

算法描述:
首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。

public static int[] selectSort(int[] arr) { 
    for (int i= 0;i<arr.length-1;i++){
        int min = i;
        for(int j = i+1;j<arr.length;j++){
            if (arr[min]>arr[j]) min = j;
        }
        //交换
        int temp = arr[i];
        arr[i] = arr[min];
        arr[min]=temp;
    }
}

性质:
1、时间复杂度:O(n2)
2、空间复杂度:O(1)
3、非稳定排序
4、原地排序

2. 插入排序

算法描述:
1、从数组第2个元素开始抽取元素。
2、把它与左边第一个元素比较,如果左边第一个元素比它大,则继续与左边第二个元素比较下去,直到遇到不比它大的元素,然后插到这个元素的右边。
3、继续选取第3,4,….n个元素,重复步骤2 ,选择适当的位置插入。

public static int[] insertSort(int[] arr) {
    if(arr == null || arr.length < 2) return arr;
    
    for (int i= 1;i<arr.length;i++){
        int temp = arr[i];
        int k = i-1;
        while(k>=0 && arr[k]>temp){
            k--;
        }
        //插入的位置是k+1
        for(int j =i;j>k+1;j--){
            arr[j] = arr[j-1];
        }
        //插入元素
        arr[k+1] = temp;
}

性质:
1、时间复杂度:O(n2)
2、空间复杂度:O(1)
3、稳定排序
4、原地排序

3. 冒泡排序

1、把第一个元素与第二个元素比较,如果第一个比第二个大,则交换他们的位置。接着继续比较第二个与第三个元素,如果第二个比第三个大,则交换他们的位置
2、我们对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样一趟比较交换下来之后,排在最右的元素就会是最大的数。
3、除去最右的元素,我们对剩余的元素做同样的工作,如此重复下去,直到排序完成。

 public static int[] bubbleSort(int[] arr) {
     if (arr == null || arr.length < 2) {
         return arr;
     } 
    for (int i= 0;i<arr.length;i++){
        for(int j = 0;j<arr.length-i-1;j++){
            if(arr[j+1]<arr[j]){
                //交换
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
   	}
 }

性质:
1、时间复杂度:O(n2)
2、空间复杂度:O(1)
3、稳定排序
4、原地排序

4. 希尔排序

希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。

public static int[] shellSort(int[] ins){
    int n = ins.length;
    int gap = n/2;
    while(gap > 0){
        for(int j = gap; j < n; j++){
            int i=j;
            while(i >= gap && ins[i-gap] > ins[i]){
                int temp = ins[i-gap]+ins[i];
                ins[i-gap] = temp-ins[i-gap];
                ins[i] = temp-ins[i-gap];
                i -= gap;
            }
        }
        gap = gap/2;
    }
    return ins;
}

性质:
1、时间复杂度:O(nlogn)
2、空间复杂度:O(1)
3、非稳定排序
4、原地排序

5. 归并排序

二路归并排序就是将两个有序子表归并成一个有序表。首先我们得有一个算法用于归并:两个有序表放在同一数组的相邻位置上,arr[left]到arr[mid]为第一个有序表,arr[mid+1]到arr[right]是第二个有序表。每次从两段中取出一个进行比较,小的先放入一个temp数组,最后将比较剩下的直接放到temp中去,最后将temp又复制回arr。这是“治”。

public static void main(String[] args) {
        int[] arr = {3, 1, 6, 2, 9, 7};
        int[] ints = mergeSort(arr, 0, arr.length-1);
        for (int anInt : ints) {
            System.out.print(anInt);
        }
    }

public static int[] mergeSort(int[] arr, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        arr = mergeSort(arr, left, mid);
        arr = mergeSort(arr, mid+1, right);
        merge(arr, left, mid, right);
    }
    return arr;
}

private static void merge(int[] arr, int left, int mid, int right) {
    //先用一个临时数组把他们合并汇总起来
    int[] a = new int[right - left + 1];
    int i = left;
    int j = mid + 1;
    int k = 0;
    while (i <= mid && j <= right) {
        if (arr[i] < arr[j]) {
            a[k++] = arr[i++];
        } else {
            a[k++] = arr[j++];
        }
    }
    while (i <= mid) a[k++] = arr[i++];
    while (j <= right) a[k++] = arr[j++];
    // 把临时数组复制到原数组
    for (i = 0; i < k; i++) {
        arr[left++] = a[i];
    }
}

性质:
1、时间复杂度:O(nlogn)
2、空间复杂度:O(n)
3、稳定排序
4、非原地排序

6. 快速排序

我们从数组中选择一个元素,我们把这个元素称之为中轴元素吧,然后把数组中所有小于中轴元素的元素放在其左边,所有大于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置的是有序的。也就是说,我们无需再移动中轴元素的位置。
从中轴元素那里开始把大的数组切割成两个小的数组(两个数组都不包含中轴元素),接着我们通过递归的方式,让中轴元素左边的数组和右边的数组也重复同样的操作,直到数组的大小为1,此时每个元素都处于有序的位置。

public static void main(String[] args) {
    int[] arr = {3, 1, 6, 2, 9, 7};
    int[] ints = quickSort(arr, 0, arr.length - 1);
    for (int anInt : ints) {
        System.out.print(anInt);
    }
}
public static int[] quickSort(int[] arr, int left, int right) {
    if (left < right) {
        //获取中轴元素所处的位置
        int mid = partition(arr, left, right);
        //进行分割
        arr = quickSort(arr, left, mid - 1);
        arr = quickSort(arr, mid + 1, right);
    }
    return arr;
}

private static int partition(int[] arr, int left, int right) {
    //选取中轴元素
    int pivot = arr[left];
    int i = left + 1;
    int j = right;
    while (true) {
        // 向右找到第一个小于等于 pivot 的元素位置
        while (i <= j && arr[i] <= pivot) i++;
        // 向左找到第一个大于等于 pivot 的元素位置
        while (i <= j && arr[j] >= pivot) j--;
        if (i >= j)
            break;
        //交换两个元素的位置,使得左边的元素不大于pivot,右边的不小于pivot
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    arr[left] = arr[j];
    // 使中轴元素处于有序的位置
    arr[j] = pivot;
    return j;
}

性质:
1、时间复杂度:O(nlogn)
2、空间复杂度:O(logn)
3、非稳定排序
4、原地排序

7. 堆排序

算法思想(以大顶堆为例):
1.将长度为n的待排序的数组进行堆有序化构造成一个大顶堆
2.将根节点与尾节点交换并输出此时的尾节点
3.将剩余的n -1个节点重新进行堆有序化
4.重复步骤2,步骤3直至构造成一个有序序列

 public static void main(String[] args) {
        int array[] = { 20, 50, 20, 40, 70, 10, 80, 30, 60 };
        System.out.println("排序之前:");
        for (int element : array) {
            System.out.print(element + " ");
        }
        heapSort(array);
        System.out.println("\n排序之后:");
        for (int element : array) {
            System.out.print(element + " ");
        }
    }

    private static void heapSort(int[] arr) {
        int len = arr.length - 1;
        for (int i = len / 2 - 1; i >= 0; i--) { // 堆构造
            heapAdjust(arr, i, len);
        }
        while (len >= 0) {
            swap(arr, 0, len--); // 将堆顶元素与尾节点交换后,长度减1,尾元素最大
            heapAdjust(arr, 0, len); // 再次对堆进行调整
        }
    }

    public static void heapAdjust(int[] arr, int i, int len) {
        int left, right, j;
        while ((left = 2 * i + 1) <= len) { // 判断当前父节点有无左节点(即有无孩子节点,left为左节点)
            right = left + 1; // 右节点
            j = left; // j"指针指向左节点"
            if (j < len && arr[left] < arr[right]) // 右节点大于左节点
                j++; // 当前把"指针"指向右节点
            if (arr[i] < arr[j]) // 将父节点与孩子节点交换(如果上面if为真,则arr[j]为右节点,如果为假arr[j]则为左节点)
                swap(arr, i, j);
            else // 说明比孩子节点都大,直接跳出循环语句
                break;
            i = j;
        }
    }

    public static void swap(int[] arr, int i, int len) {
        int temp = arr[i];
        arr[i] = arr[len];
        arr[len] = temp;
    }

性质:
1、时间复杂度:O(nlogn)
2、空间复杂度:O(1)
3、非稳定排序
4、原地排序

注意:堆排序主要在于理解堆的构造过程和在输出最大元素后如何对堆进行重新调整

8. 计数排序

计数排序是一种适合于最大值和最小值的差值不是不是很大的排序。
基本思想:就是把数组元素作为数组的下标,然后用一个临时数组统计该元素出现的次数,例如 temp[i] = m, 表示元素 i 一共出现了 m 次。最后再把临时数组统计的数据从小到大汇总起来,此时汇总起来是数据是有序的。

 public static int[] countSort(int[] arr) {
     if (arr == null || arr.length < 2) return arr;
     int n = arr.length;
     int max = arr[0];
     // 寻找数组的最大值
     for (int i = 1; i < n; i++) {
         if (max < arr[i]) max = arr[i];
     }
     //创建大小为max的临时数组
     int[] temp = new int[max + 1];
     //统计元素i出现的次数
     for (int i = 0; i < n; i++) {
         temp[arr[i]]++;
     }
     int k = 0;
     //把临时数组统计好的数据汇总到原数组
     for (int i = 0; i <= max; i++) {
         for (int j = temp[i]; j > 0; j--) {
             arr[k++] = i;
         }
     }
     return arr;
 }

性质:
1、时间复杂度:O(n+k)
2、空间复杂度:O(k)
3、稳定排序
4、非原地排序
注:K表示临时数组的大小

9. 桶排序

桶排序就是把最大值和最小值之间的数进行瓜分,例如分成 10 个区间,10个区间对应10个桶,我们把各元素放到对应区间的桶中去,再对每个桶中的数进行排序,可以采用归并排序,也可以采用快速排序之类的。之后每个桶里面的数据就是有序的了,我们在进行合并汇总。

 public static void main(String[] args) {
        int array[] = {20, 50, 20, 40, 70, 10, 80, 30, 60};
        System.out.println("排序之前:");
        for (int element : array) {
            System.out.print(element + " ");
        }
        BucketSort(array);
        System.out.println("\n排序之后:");
        for (int element : array) {
            System.out.print(element + " ");
        }
    }

    public static int[] BucketSort(int[] arr) {
        if (arr == null || arr.length < 2) return arr;

        int n = arr.length;
        int max = arr[0];
        int min = arr[0];
        // 寻找数组的最大值与最小值
        for (int i = 1; i < n; i++) {
            if (min > arr[i])
                min = arr[i];
            if (max < arr[i])
                max = arr[i];
        }
        //和优化版本的计数排序一样,弄一个大小为 min 的偏移值
        int d = max - min;
        //创建 d / 5 + 1 个桶,第 i 桶存放  5*i ~ 5*i+5-1范围的数
        int bucketNum = d / 5 + 1;
        ArrayList<LinkedList<Integer>> bucketList = new ArrayList<>(bucketNum);
        //初始化桶
        for (int i = 0; i < bucketNum; i++) {
            bucketList.add(new LinkedList<Integer>());
        }
        //遍历原数组,将每个元素放入桶中
        for (int i = 0; i < n; i++) {
            bucketList.get((arr[i] - min) / d).add(arr[i] - min);
        }
        //对桶内的元素进行排序,我这里采用系统自带的排序工具
        for (int i = 0; i < bucketNum; i++) {
            Collections.sort(bucketList.get(i));
        }
        //把每个桶排序好的数据进行合并汇总放回原数组
        int k = 0;
        for (int i = 0; i < bucketNum; i++) {
            for (Integer t : bucketList.get(i)) {
                arr[k++] = t + min;
            }
        }
        return arr;
    }

性质:
1、时间复杂度:O(n+k)
2、空间复杂度:O(n+k)
3、稳定排序
4、非原地排序

注:k 表示桶的个数

10. 基数排序

基数排序的排序思路是这样的:先以个位数的大小来对数据进行排序,接着以十位数的大小来多数进行排序,接着以百位数的大小…… 排到最后,就是一组有序的元素了。

 public static int[] radioSort(int[] arr) {
     if (arr == null || arr.length < 2) return arr;

     int n = arr.length;
     int max = arr[0];
     // 找出最大值 for (int i = 1; i < n; i++) {
     for (int i = 1; i < n; i++) {
         if (max < arr[i]) max = arr[i];
     }
     // 计算最大值是几位数
     int num = 1;
     while (max / 10 > 0) {
         num++;
         max = max / 10;
     }
     // 创建10个桶
     ArrayList<LinkedList<Integer>> bucketList = new ArrayList<>(10);
     //初始化桶
     for (
         int i = 0;
         i < 10; i++) {
         bucketList.add(new LinkedList<Integer>());
     }
     // 进行每一趟的排序,从个位数开始排
     for (
         int i = 1;
         i <= num; i++) {
         for (int j = 0; j < n; j++) {
             // 获取每个数最后第 i 位是数组
             int radio = (arr[j] / (int) Math.pow(10, i - 1)) % 10;
             //放进对应的桶里
             bucketList.get(radio).add(arr[j]);
         }
         //合并放回原数组
         int k = 0;
         for (int j = 0; j < 10; j++) {
             for (Integer t : bucketList.get(j)) {
                 arr[k++] = t;
             }
             //取出来合并了之后把桶清光数据
             bucketList.get(j).clear();
         }
     }
     return arr;
 }

性质:
1、时间复杂度:O(kn)
2、空间复杂度:O(n+k)
3、稳定排序
4、非原地排序

你可能感兴趣的:(随笔,排序算法)