程序员,你应该知道的基础排序算法 - Go语言中文社区

程序员,你应该知道的基础排序算法



冒泡排序(Bubble Sort)

原理

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。

性能

  • 时间复杂度:O(n²)

  • 最好时间复杂度:O(n)

  • 最差时间复杂度:O(n²)

  • 平均时间复杂度:O(n²)

  • 空间复杂度:O(1)

代码

    public static void bubbleSort(int[] a, int n) {	
        if (n <= 1) return;	
        for (int i = 0; i < n; ++i) {	
            // 如果没有数据交换,则提前退出,提前退出冒泡循环的标志位	
            boolean flag = false;	
            for (int j = 0; j < n - i - 1; ++j) {	
                if (a[j] > a[j+1]) { // 交换	
                    int tmp = a[j];	
                    a[j] = a[j+1];	
                    a[j+1] = tmp;	
                    flag = true;  // 表示有数据交换	
                }	
            }	
            if (!flag) break;  // 没有数据交换,提前退出	
        }	
    }

插入排序(Insertion Sort)

原理

插入排序就像我们玩棋牌一样,每次摸的牌我们都会插入到合适的位置,保证我们的牌是有序的,在这个过程中我们有一个比大小的操作,插入排序没我们手动方便,插入排序除了比大小之外还需要元素移动。例如:需要将一个数据 a 插入到已排序区间时,需要拿 a 与已排序区间的元素依次比较大小,找到合适的插入位置。找到插入点之后,我们还需要将插入点之后的元素顺序往后移动一位,这样才能腾出位置给元素 a 插入。

性能

  • 时间复杂度:O(n²)

  • 最好时间复杂度:O(n)

  • 最差时间复杂度:O(n²)

  • 平均时间复杂度:O(n²)

  • 空间复杂度:O(1)

代码

    // 插入排序,a 表示数组,n 表示数组大小	
    public static void insertionSort(int[] a, int n) {	
        if (n <= 1) return;	
        for (int i = 1; i < n; ++i) {	
            int value = a[i];	
            int j = i - 1;	
            // 查找插入的位置	
            for (; j >= 0; --j) {	
                if (a[j] > value) {	
                    a[j + 1] = a[j];  // 数据移动	
                } else {	
                    break;	
                }	
            }	
            a[j + 1] = value; // 插入数据	
        }	
    }

选择排序(Selection Sort)

原理

选择排序的思想很简单,从数组的下标 i=0开始,在 i+1后面找出最小的一个元素与 i对应的值比大小,如果 i+1的值小于 i的值,则将这两个数进行交换,然后 i++后面重这种操作,直到整个数组排好序。

性能

  • 时间复杂度:O(n²)

  • 最好时间复杂度:O(n²)

  • 最差时间复杂度:O(n²)

  • 平均时间复杂度:O(n²)

  • 空间复杂度:O(1)

代码

public static void sort(int arr[],int n){	
    for( int i = 0;i < n ; i++ ){	
        int min = i;//最小元素的下标	
        for(int j = i + 1;j < n; j++ ){	
            if(arr[j] < arr[min]){	
                min = j;//找最小值	
            }	
        }	
        //交换位置	
        int temp = arr[i];	
        arr[i] = arr[min];	
        arr[min] = temp;	
    }	
}

归并排序(Merge Sort)

原理

归并排序采用的是分而治之的思想,我们将要排序的数组从中间分成两半,分别对前后两个数组进行排序,然后将接口合并,这样整个数据就有序了。

归并排序一般我们采用递归的方式,将数组进行拆分到不可分为止,然后对不可拆分的数组进行排序,左右两半数组分别从第一个开始逐一比较大小,放入到新的临时数组中,当左右两半的值一样时,将左边的值先放入到数组中,最后判断左右两边是否有剩余,将剩余的数组加入到临时数组中,经过上面的操作后,整个数组都有序了,最后需要将临时数组的值拷贝回原数组中,整个归并排序就完成了。

图解

我们以数组 40,2,11,5,15,6,90,10来演示归并排序的过程640?wx_fmt=png

性能

  • 时间复杂度:O(nlogn)

  • 最好时间复杂度:O(nlogn)

  • 最差时间复杂度:O(nlogn)

  • 平均时间复杂度:O(nlogn)

  • 空间复杂度:O(n)

代码

    /**	
     * 递归 将数组分成两半,分别对前后两部分排序	
     *	
     * @param nums     数组	
     * @param leftPtr  左半边开始下标	
     * @param rightPtr 右半边结束下标	
     */	
    public static void merge_sort(int[] nums, int leftPtr, int rightPtr) {	
        if (leftPtr >= rightPtr) return;	
        // 将数组分成两半	
        int mid = leftPtr + (rightPtr - leftPtr) / 2;	
        // 左半边排序	
        merge_sort(nums, leftPtr, mid);	
        // 右半边排序	
        merge_sort(nums, mid + 1, rightPtr);	
        merge(nums, leftPtr, mid + 1, rightPtr);	
    }	
    /**	
     * 将前后两半排好序的数组进行合并	
     *	
     * @param nums	
     * @param leftPtr    左半边开始下标值	
     * @param rightPtr   右半边开始下标值	
     * @param rightBound 左半边结束值	
     */	
    public static void merge(int[] nums, int leftPtr, int rightPtr, int rightBound) {	
        // 新开辟临时排序数组	
        int[] sortNums = new int[rightBound - leftPtr + 1];	
        // 求出中间值	
        int mid = rightPtr - 1;	
        // 前半部分数组起始下标	
        int i = leftPtr;	
        // 后半部分起始下标	
        int j = rightPtr;	
        // 临时排序数组的起始下标	
        int k = 0;	
        // 左右两边分别逐一比较,将小的存入到临时数组	
        while (i <= mid && j <= rightBound) {	
            sortNums[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];	
        }	
        // 判断左半边时候有剩下	
        while (i <= mid) sortNums[k++] = nums[i++];	
        // 判断右半边时候有剩下	
        while (j <= rightBound) sortNums[k++] = nums[j++];	
        // 将数组拷贝回nums	
        for (int m = 0; m < sortNums.length; m++) nums[leftPtr + m] = sortNums[m];	
    }

快速排序(Quick Sort)

原理

快速排序也称做快排,快排跟归并排序一样也是利分治思想。快排分为单轴快排和双轴快排,因为双轴排序效率比单抽排序效率高,所以这篇主要讲的是双轴排序。快速排序每次从数组中随机选择一个元素作为pivot(一般情况下,选择数组的最后一个元素),然后从数组的第一个元素和分区值前一个元素开始进行查找,从第一个元素开始查找比分区值大的第一个数 A,从分区值前面的一个数往前开始查找,找到第一个比分区值小的数 B,将 AB交换位置,直到左边开始查找的下标大于右边开始查找的下标停止查找,然后将分区值与第一个大于分区值的数交换位置,这样分区值左边的数就全部小于分区值,右边的数全部大于分区值。继续遍历左右两边的数组,直到整个数组排好序。

图解

我们以数组 40,2,11,5,15,6,90,10来演示快速排序的过程640?wx_fmt=png选择数组最右边的值作为分区值,即黄颜色的数组10,建立两个下标索引,一个指向数组的第一个元素 left,一个指向分区值的前一位数组的元素 right,即图中用红色箭头标出来的地方。从左边开始找出第一个比分区值大的元素,从右边找出第一个比分区值小的元素,将这两个元素进行交换并且将坐标往后移,即图中的第二步和第三步,按照上面的规则继续查找,直到 left > right 为止。将分区值与 left所指的元素进行交换,这样分区值左边的数都是比分区值小的,右边的数都是被分区值大的。按照上面的规则继续遍历左右两边的数组,最后整个数组就排好序了。

性能

  • 时间复杂度:O(nlogn)

  • 最好时间复杂度:O(nlogn)

  • 最差时间复杂度:O(n²)

  • 平均时间复杂度:O(nlogn)

  • 空间复杂度:O(logn)

代码

// 递归	
    public static void sort(int[] nums, int leftBound, int rightBound) {	
        if (leftBound >= rightBound) return;	
        // 分区值的下标位置	
        int mid = partition(nums, leftBound, rightBound);	
        // 左分区排序	
        sort(nums, leftBound, mid - 1);	
        // 右分区排序	
        sort(nums, mid, rightBound);	
    }	
    // 分区	
    public static int partition(int[] nums, int leftBound, int rightBound) {	
        // 分区点的值	
        int piovt = nums[rightBound];	
        // 左边下标	
        int left = leftBound;	
        //右边起始下标	
        int right = rightBound - 1;	
        while (left <= right) {	
            // 找到第一个大于分区值的	
            while (left <= right && nums[left] <= piovt) left++;	
            // 找到第一个小于分区值的	
            while (left <= right && nums[right] > piovt) right--;	
            // 将左右两边的值进行交换	
            if (left < right) swap(nums, left, right);	
        }	
        // 将left的值与分区值交换位置	
        swap(nums, left, rightBound);	
        return left;	
    }	
    /**	
     * 数据交换	
     *	
     * @param nums	
     * @param i	
     * @param k	
     */	
    public static void swap(int[] nums, int i, int k) {	
        int temp = nums[i];	
        nums[i] = nums[k];	
        nums[k] = temp;	
    }

桶排序(Bucket sort)

原理

桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据按照范围分到几个桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。

图解

我们以数组 90,85,63,34,42,12,10来演示桶排序的过程640?wx_fmt=png

性能

  • 时间复杂度:O(n)

  • 最好时间复杂度:O(n)

  • 最差时间复杂度:O(nlogn)

  • 平均时间复杂度:O(n)

  • 空间复杂度:O(n)

代码

    public static void sort(int[] nums) {	
        //最大值	
        int maxValue = nums[0];	
        // 最小值	
        int minValue = nums[0];	
        int length = nums.length;	
        /**	
         * 求出最大最小值	
         */	
        for (int i = 1; i < length; i++) {	
            if (nums[i] > maxValue) {	
                maxValue = nums[i];	
            } else if (nums[i] < minValue) {	
                minValue = nums[i];	
            }	
        }	
        //最大值和最小值的差,	
        int diff = maxValue - minValue;	
        //初始化桶列表	
        ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();	
        for (int i = 0; i < length; i++) {	
            bucketList.add(new ArrayList<Integer>());	
        }	
        //桶之间的数值间距	
        float section = (float) diff / (float) (length - 1);	
        //数据入桶	
        for (int i = 0; i < length; i++) {	
            //当前数除以区间得出存放桶的位置 减1后得出桶的下标	
            int num = (int) (nums[i] / section) - 1;	
            if (num < 0) {	
                num = 0;	
            }	
            bucketList.get(num).add(nums[i]);	
        }	
        //桶内排序	
        for (int i = 0; i < bucketList.size(); i++) {	
            //jdk内置的集合排序框架	
            Collections.sort(bucketList.get(i));	
        }	
        //写入原数组	
        int index = 0;	
        for (ArrayList<Integer> arrayList : bucketList) {	
            for (int value : arrayList) {	
                nums[index] = value;	
                index++;	
            }	
        }	
    }

计数排序(Counting sort)

原理

计数排序其实是桶排序的一种特殊情况,当要排序的 n 个数据,所处的范围并不大的时候,比如最大值是 k,我们就可以把数据划分成 k 个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。比如腾讯面试题,不用数据交换,查询出你的成绩排名?考生的满分是 750 分,最小是 0 分,这个数据的范围很小,所以我们可以分成 750 个桶,对应分数从 0 分到 750 分。根据考生的成绩,我们将这 50 万考生划分到这 750 个桶里。桶内的数据都是分数相同的考生,所以并不需要再进行排序。我们只需要依次扫描每个桶,将桶内的考生依次输出到一个数组中,就实现了 50 万考生的排序。

图解

我们以数组 5,8,9,7,8,4,5,6,9,3,0,2,1来演示计数排序的过程

640?wx_fmt=png

性能

  • 时间复杂度:O(n)

  • 最好时间复杂度:O(n)

  • 最差时间复杂度:O(n)

  • 平均时间复杂度:O(n)

  • 空间复杂度:O(n)

代码

    /**	
     * 计数排序	
     *	
     * @param nums       待排序数组	
     * @param rangeCount 数组范围个数	
     * @param min        最小的个数	
     * @return	
     */	
    public static int[] countingSort(int[] nums, int rangeCount, int min) {	
        int[] result = new int[nums.length];	
        // 定义计数桶	
        int[] count = new int[rangeCount + min];	
        // 将数据添加到桶里	
        for (int i = 0; i < nums.length; i++) {	
            count[nums[i]]++;	
        }	
        // 遍历桶 将数据写入到返回数组中	
        for (int i = min, j = 0; i < count.length; i++) {	
            while (count[i]-- > 0) result[j++] = i;	
        }	
        return result;	
    }

基数排序(Radix sort)

原理

基数排序也是桶排序的一种,基数排序对要排序的数据是有要求的,需要可以分割出独立的“位”来比较,而且位之间有递进的关系,如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了,除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序。例如假设我们有 10 万个手机号码,需要按照从小到大的顺序对电话号码排序,前面的快排、桶排序、计数排序都可以进行快速的排序,但是内存有限,不允许你做这样的操作,这是就可以使用基数排序来解决这个问题。

图解

我们以数组 2154,5896,356,201,698,412来演示基数排序的过程640?wx_fmt=png

性能

  • 时间复杂度:O(n*k)

  • 最好时间复杂度:O(n*k)

  • 最差时间复杂度:O(n*k)

  • 平均时间复杂度:O(n*k)

  • 空间复杂度:O(n+k)

代码

public static void radixSort(int[] nums){	
        // 记录数组的大小	
        int length = nums.length;	
        //最大值	
        int numMax = nums[0];	
        for(int i = 0;i < length;i++){	
            if(nums[i] > numMax){	
                numMax = nums[i];	
            }	
        }	
        //当前排序位置	
        int location = 1;	
        //桶列表 一个桶中会有多个不同的元素	
        ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();	
        //初始化一个空桶	
        for(int i = 0; i < 10; i++){	
            bucketList.add(new ArrayList<Integer>());	
        }	
        while(true)	
        {	
            //求出每位数的最小值	
            int min = (int)Math.pow(10,(location - 1));	
            // 判断最大值是否小于每位数的最小值,小于就结束	
            if(numMax < min){	
                break;	
            }	
            //遍历数据,将数据写入桶	
            for(int i = 0; i < length; i++)	
            {	
                //计算余数 放入相应的桶	
                int number = ((nums[i] / min) % 10);	
                bucketList.get(number).add(nums[i]);	
            }	
            //将数从桶中取回,重新组成数组	
            int k = 0;	
            for (int i=0;i<10;i++){	
                int size = bucketList.get(i).size();	
                for(int j = 0;j < size;j ++){	
                    nums[k++] = bucketList.get(i).get(j);	
                }	
                // 将桶清空,用于下一次排序	
                bucketList.get(i).clear();	
            }	
            // 位数加一	
            location++;	
        }	
    }

以上就是编程中的基本排序算法,基本排序算法,在很多组件内部使用,如果对基础排序了解的话,对你阅读源码还是有些帮助的,非常感觉你看到这里,希望这篇文章对你有所帮助。

如果您发现文中错误,还请多多指教。欢迎关注公众号,一起交流学习。

640?wx_fmt=jpeg

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/z694644032/article/details/98823658
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 22:36:49
  • 阅读 ( 649 )
  • 分类:算法

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢