排序算法总结

Posted xdcat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法总结相关的知识,希望对你有一定的参考价值。

排序算法比较

https://www.cnblogs.com/bjwu/articles/10006419.html

冒泡排序

 1     /**冒泡排序
 2      * 第一次比较0~N-1位 将最大值沉底
 3      * 第二次比较0~N-2位 将最大值沉底
 4      * ......
 5      * 第N-1次比较0~1位 将最大值沉底(在0~1中沉底)
 6      * 时间复杂度:O(n^2)
 7      */
 8     public static void sort(int[] nums){
 9         if(nums == null || nums.length < 2){
10             return;
11         }
12         for(int end = nums.length-1; end > 0; end--){
13             for(int j = 0; j < end; j++){
14                 if(nums[j] > nums[j+1]){
15                     swap(nums,j,j+1);
16                 }
17             }
18         }
19 
20     }
21     public static void swap(int[] nums, int i, int j){
22         int tmp = nums[i];
23         nums[i] = nums[j];
24         nums[j] = tmp;
25     }

插入排序

 1     /** 插入排序
 2      *  0~end-1 位置的数都有序
 3      *  每次考察end位置的数应该插在哪里
 4      *  如果前面的数比end位置数大则交换
 5      *
 6      *  插入选择时间复杂度和数据样本无关
 7      *  插入排序 有序 O(n) 逆序O(n^2)
 8      *  时间复杂度按最差的算
 9      *  所以插入排序的时间复杂度是O(n^2)
10      */
11     public static void sort(int[] nums){
12         for(int end = 1; end < nums.length; end++){
13             for(int i = end-1; i >= 0; i--){//与前面有序部分比较是否能够交换
14                 if(nums[i] > nums[i+1]){
15                     swap(nums,i,i+1);
16                 }
17             }
18         }
19     }
20 
21     public static void swap(int[] nums, int i, int j){
22         int tmp = nums[i];
23         nums[i] = nums[j];
24         nums[j] = tmp;
25     }

选择排序

    /**选择排序
     * 第一次比较 0~N-1 找到最小值的位置和0交换
     * 第二次比较 1~N-2 找到最小值的位置和1交换
     * ......
     * 第N-1次比较 N-2~N 找到最小值的位置和N-2交换
     * 时间复杂度:O(N^2)
     */
    public static void sort(int[] nums){
        if(nums == null || nums.length < 2){
            return;
        }
        for(int start = 0; start < nums.length; start++){
            int minIndex = start;
            for(int i = start; i < nums.length; i++){
                minIndex = nums[i] < nums[minIndex]? i:minIndex;
            }
            swap(nums, minIndex, start);
        }

    }

    public static void swap(int[] nums, int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

快排

 1     public static void quickSort(int [] nums, int left, int right){
 2         if (nums == null || nums.length < 2) {
 3             return;
 4         }
 5         if(left < right){
 6             //随机选一个位置和最后一个数字交换
 7             swap(nums, left+(int)Math.random()*(right-left+1), right);
 8             int[] p = partition(nums, left, right);//等于区域的左边界和右边界
 9             quickSort(nums, left, p[0]-1); // 小于区域递归
10             quickSort(nums, p[1]+1, right); // 大于区域递归
11         }
12     }
13 
14     public static int[] partition(int[] nums, int left, int right){
15         int smaller = left - 1; // 小于区域的右边界
16         int bigger = right; // 大于区域的左边界
17         while(left < bigger){
18             int target = nums[right];
19             if(nums[left] > target){
20                 swap(nums,left,bigger-1);
21                 bigger--;
22             }else if(nums[left] < target){
23                 swap(nums,left,smaller+1);
24                 smaller++;
25                 left++;
26             }else {
27                 left++;
28             }
29         }
30         swap(nums, bigger, right);
31         return new int[]{smaller+1,bigger};
32     }
33     public static void swap(int[] nums, int i, int j){
34         int tmp = nums[i];
35         nums[i] = nums[j];
36         nums[j] = tmp;
37     }

归并排序

 1     /**归并排序
 2      * 左边排序 右边排序
 3      * 外排整体排序 辅助数组
 4      * 左边有序数组和右边有序数组谁小谁加入辅助数组
 5      * T(N) = 2T(N/2) + O(N)
 6      * log2(2) = 1 = d -->
 7      * 时间复杂度为 O(N*logN)
 8      */
 9     public static void mergeSort(int[] nums){
10         if(nums ==null || nums.length < 2){
11             return;
12         }
13         sortProcess(nums, 0, nums.length-1);
14     }
15     public static void sortProcess(int[] nums, int left, int right){
16         if(left == right){//结束递归条件 这个范围只有一个数
17             return;
18         }
19         int mid = left + (right - left)/2;
20         sortProcess(nums,left,mid);//左边有序 T(N/2)
21         sortProcess(nums,mid+1,right);//右边有序 T(N/2)
22         merge(nums, left, mid, right);//外排序 O(N)
23     }
24 
25     public static void merge(int[] nums, int left, int mid, int right){
26         int[] help = new int[right-left+1];//left到right有多少个数
27         int i = 0;//指向help
28         int p1 = left;//指向左侧数组第一个数
29         int p2 = mid+1;//指向右侧数组第一个数
30 
31         while(p1 <= mid && p2 <= right){//有一个数组读完了就退出循环
32             //哪边小哪边的p指向的元素加入help 加入之后移动p指针
33             help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++];
34         }
35         //只有一个越界 把剩余的数组一次性添加进help
36         while(p2 <= right){
37             help[i++] = nums[p2++];
38         }
39         while(p1 <= mid){
40             help[i++] = nums[p1++];
41         }
42         //将help中的依次覆盖nums
43         for(int j = 0; j < help.length; j++){
44             nums[left+j] = help[j];
45         }
46     }

逆序和  & 最小和都可以用归并排序的思路来解

逆序对

 1 /**
 2  * 逆序对和最小和求解思路的区别
 3  *
 4  * 【最小和】是优先找小值
 5  * [1,1,2] [2,3,4]
 6  * 优先在左侧数组找到小值后  右边剩下的就都比该值要大了 从左向右遍历
 7  * 例如:对于左边第一个元素1 右边第一个元素2及2之后的所有元素都对1有一个小和
 8  * smallsum += 1 * (right-p2+1)
 9  * 即 如果左小于右 累加
10  *
11  * 【逆序对】要求是左侧元素大于右侧
12  * 优先在左侧找大值 对于升序排序 大值在最右边 应该从右向左遍历
13  * [1,1,5] [1,3,4]
14  * 例如:对于左边最后一个元素5 大于右边最后一个元素4 则5可以和4及4之前的所有元素形成一个逆序对
15  * [5,1][5,3][5,4]
16  * count += right-(mid+1)+1  5 - 3 + 1 = 3 个
17  * 即 如果左大于右 累加
18  */
19 public class ReversePair {
20     private static int count;
21     private static List<int[]> list = new ArrayList<>();
22     public static void mergeSort(int[] nums){
23         if(nums ==null || nums.length < 2){
24             return;
25         }
26         sortProcess(nums, 0, nums.length-1);
27     }
28     public static void sortProcess(int[] nums, int left, int right){
29         if(left == right){//结束递归条件 这个范围只有一个数
30             return;
31         }
32         int mid = left + (right - left)/2;
33         sortProcess(nums,left,mid);//左边有序 T(N/2)
34         sortProcess(nums,mid+1,right);//右边有序 T(N/2)
35         merge(nums, left, mid, right);//外排序 O(N)
36     }
37 
38     public static void merge(int[] nums, int left, int mid, int right){
39         int[] helper = new int[right -left + 1];
40         int p = right - left;
41         int rightBegin = mid + 1;
42         while(left <= mid && rightBegin <= right){
43             if(nums[mid] > nums[right]){
44                 count += (right - rightBegin)+1;
45                 for(int k = right; k >= rightBegin; k--){
46                     list.add(new int[] {nums[mid],nums[k]});
47                 }
48                 helper[p--] = nums[mid--];
49             }else{
50                 helper[p--] = nums[right--];
51             }
52         }
53         while(left <= mid){
54             helper[p--] = nums[mid--];
55         }
56         while(right >= rightBegin){
57             helper[p--] = nums[right--];
58         }
59         for(int help:helper){
60             nums[left++] = help;
61         }
62     }
63 
64     public static void main(String[] args) {
65         int[] nums = {3,5,4,2,1};
66         mergeSort(nums);
67         System.out.println(Arrays.toString(nums));
68         System.out.println(count);
69         for(int[] pair: list){
70             System.out.println(Arrays.toString(pair));
71         }
72     }
73 }

最小和

 1 public class SmallSum {
 2     public static int smallSum(int[] arr){
 3         if(arr == null|| arr.length < 2){
 4             return 0;
 5         }
 6         return mergeSort(arr,0,arr.length-1);
 7     }
 8 
 9     /**
10      * 计算left ~ right范围内有多少小和
11      */
12     public static int mergeSort(int[] arr, int left, int right){
13         if(left == right){
14             return 0;
15         }
16         int mid = left + ((right - left) >> 1); //右移1位 等于 除以2
17         //mid = left + (right - left)/2; 防止溢出
18         return mergeSort(arr, left, mid)+//左边排序产生的小和
19                 mergeSort(arr, mid+1, right)+//右边排序产生的小和
20                 merge(arr, left, mid, right);//最后合并产生的小和
21     }
22 
23     public static int merge(int[] arr, int left, int mid, int right){
24         int[] help = new int[right-left+1];//准备一个临时数组
25         // 长度和传进来的arr一样 是原arr的一部分 用left和right划分出来的一部分
26         int i = 0;
27         int p1= left;
28         int p2 = mid+1;
29         int res = 0;//计算小和
30         while(p1 <= mid && p2 <= right){
31             //关键在这里:
32             // 每一次比较可以知道比当前值更大的值有几个,这个较小的当前值就需要累加多少次。
33             //如果左边小于右边,那就有(r - p2 + 1)个arr[p1]元素的和是最小和,进行累加。
34             //p2后面的个数 * p1指向的数
35             res += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
36             help[i++] = arr[p1] > arr[p2] ? arr[p2++] : arr[p1++];
37         }
38         while(p1 <= mid){
39             help[i++] = arr[p1++];
40         }
41         while (p2 <= right){
42             help[i++] = arr[p2++];
43         }
44         for(i = 0; i < help.length; i++){
45             arr[left+i] = help[i];
46         }
47         return res;
48     }
49 
50     public static void main(String[] args) {
51         int[] nums = {1,3,4,2,5};
52         int i = smallSum(nums);
53         System.out.println(i);
54     }
55 }

堆排序

/**
 * HeapSort展示了heapinsert和heapify的两个过程
 * 时间复杂度是O(NlogN)
 * 实际上可以只用heapify
 * 用heapify代替heapinsert从后向前heapify
 * 原来的heapinsert是O(NlogN) heapify时间复杂度是O(N) 可以稍微快一点
 * 但最后的时间复杂度还是O(NlogN) 因为后面的操作还是O(NlogN)
 */
public class HeapSort {
    //堆化【调整堆】 某个数在index值 能否往下移动
    public static void heapify(int[] nums, int index, int heapSize){
        //index是初始的父节点
        int left = index * 2 + 1; //左孩子
        while(left < heapSize){//下方还有孩子时 没有越界
            int largest = left;
            if(left + 1 < heapSize) {//右孩子存在 没有越界
                //左右两孩子PK
                largest = nums[left]>nums[left+1]?left:left+1;
            }
            //较大孩子和父亲PK
            largest = nums[index]>nums[largest]?index:largest;
            //父亲最大不交换
            if(largest == index){
                break;
            }
            //父亲和较大孩子交换
            swap(nums, largest, index);
            //下一个比较的数 新父亲
            index = largest;
            //交换后的新的左孩子
            left = index * 2 + 1;
        }
    }

    /**
     * 无序数组 相当于依次插入大根堆 heapinsert
     * 得到大根堆后
     * 每次让数组第一个元素(最大值)和最后一个元素交换
     * 交换后heapsize--,调整堆heapify为大根堆
     * 最后heapsize == 0 数组有序
     */
    public static void heapSort(int[] nums){
        if(nums == null || nums.length < 2){
            return;
        }
        for (int i = nums.length-1; i >= 0; i--) {
            heapify(nums,i,nums.length);
        }
        int heapSize = nums.length;
        swap(nums,0,--heapSize);//先交换
        while(heapSize > 0){
            heapify(nums,0,heapSize);//再堆化
            swap(nums,0,--heapSize);//继续交换
        }
    }

    public static void swap(int[] nums, int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

    public static void main(String[] args) {
        int[] nums = {2,3,1,4,4,3,3,4,5,1};
        heapSort(nums);
        System.out.println(Arrays.toString(nums));
    }
}

适用于求基本有序的数列

 * 已知一个几乎有序的数组,几乎有序是指,
 * 如果把数组排好顺序的话,每个元素移动的距离可以不超过K
 * 可以用堆排序来解决这个问题
 *
 * 思路:
 * 1.建立有K个元素的小顶堆,然后取出顶上元素
 * 2.堆顶用没有建堆的下一元素替代,重新建堆
 * 3.反复调用,完成排序,此算法因为每个元素移动都在k以内,所以时间复杂度为O(NlogK)
 */
public class KHeapSort {

    public static void kHeapSort(int[] nums, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        int index= 0;
        //k个值加入堆
        for(; index <= Math.min(k, nums.length); index++){
            heap.add(nums[index]);
        }
        int i = 0;
        //从index的下一个位置开始遍历 也就是k+1
        for(; index < nums.length; i++,index++){
            heap.add(nums[index]);//加一个 heapsize+1
            nums[i] = heap.poll();//弹一个 heapsize-1
        }
        while (!heap.isEmpty()){
            nums[i++] = heap.poll();
        }

    }

    public static void main(String[] args) {
        int [] nums ={3,2,7,1,8,6,4,5,12,10,15,9,13,14,11,20,16,17,18,19};
        kHeapSort(nums,7);
        System.out.println(Arrays.toString(nums));
    }
}

基数排序

public class RadixSort {
    /**
     * 统计最大值有多少位
     */
    public static int maxbits(int[] nums){
        int max = nums[0];
        for(int i = 0; i < nums.length; i++){
            max = Math.max(nums[i], max);
        }
        int count = 0;
        while(max > 0){
            max /= 10;
            count++;
        }
        return count;
    }
    /**
     * 获取倒数第i位的数字 i=1 取出个位
     */
    public static int getDigit(int num,int i){
        while(i > 1){
            num /= 10;
            i--;
        }
        return num%10;
    }

    /**
     * 基数排序
     */
    public static void radixSort(int[] arr){
        int digit = maxbits(arr);
        //基数 10
        final int radix = 10;
        //给每个数准备一个桶
        int[] buckets = new int[arr.length];
        //有多少位 就要出桶进桶几次
        for(int d = 1; d <= digit; d++){
            //计数器 等于count的有几个
            int[] count = new int[radix];
            for(int i = 0; i < arr.length; i++){
                int j = getDigit(arr[i],d);
                count[j]++;
            }
            //将count变为前缀和 小于等于count的有几个
            for(int i = 1; i < radix; i++){
                count[i] += count[i-1];
            }
            System.out.println(Arrays.toString(count));

            //数组从右向左遍历 入桶
            for(int i = arr.length-1; i >=0; i--){
                int j = getDigit(arr[i],d);
                buckets[count[j]-1] = arr[i];
                count[j]--;
            }

            //当前位数出桶
            for(int i = 0; i < arr.length; i++) {
                arr[i] = buckets[i];
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = {123,423,19,32,321,1200};
        radixSort(nums);
        System.out.println(Arrays.toString(nums));
    }
}

 

以上是关于排序算法总结的主要内容,如果未能解决你的问题,请参考以下文章

10 大排序算法总结

十大经典排序算法总结(归并排序)

十大经典排序算法总结(桶排序)

十大经典排序算法总结(基数排序)

十大经典排序算法总结(希尔排序)

十大经典排序算法总结(快速排序)