排序(上)

算法排序执行效率

  • 最好情况、最坏情况、平均情况的时间复杂度
  • 时间复杂度的系数、常数、低阶
  • 比较次数和交换次数

排序算法的内存消耗

算法的内存消耗可以通过空间复杂度来衡量,针对排序算法的空间复杂度,我们有个新的概念,原地排序。原地排序算法,就是特指空间复杂度为O(1)的排序算法。

排序算法的稳定性

如果待排序的序列中存在值相等的元素,经过排序后,相等元素之间原有的先后顺序不变,我们称这种算法称为稳定的排序算法。

冒泡排序

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

    public static void bubbleSort(int[] a, int n) {
        if (n <= 1) return;
        for (int i = 0; i < n - 1; i++) {
            boolean flag = false;
            for (int j = 0; j < n - i - 1; j++) {
                if (a[j] > a[j + 1]) {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    flag = true;
                }
            }
            if (!flag) break;
        }
    }

插入排序

我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序的区间数据一直有序。

    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;//插入数据
        }
    }

二分插入排序是优化后的直接插入排序,在新元素插入到已序数组时,采用二分查找方式找到插入位置。

    public static void binarySort(int[] a) {
        for (int i = 1; i < a.length; i++) {
            int temp = a[i];
            int left = 0;
            int right = i - 1;
            //确定要插入的位置
            while (left <= right) {
                //先获取中间位置
                int mid = (left + right) / 2;
                if (temp < a[mid]) {
                    //如果值比中间值小,让right左移到中间下标-1
                    right = mid - 1;
                } else {
                    //如果值比中间值大,让left右移到中间下标+1
                    left = mid + 1;
                }
            }
            for (int j = i - 1; j >= left; j--) {
                //以左下标为标准,在左位置前插入该数据,左及左后边全部后移
                a[j + 1] = a[j];
            }
            if (left != i) {
                //左位置插入该数据
                a[left] = temp;
            }
        }
    }

归并排序

利用归并的思想实现的排序算法,该算法用哪个经典的分治策略(分治法中分是将问题问题分成一些小的问题,然后在递归求解,而治的阶段则是将分的阶段得到的各个答案在‘修补’在一起)
分治思想

合并修补

    private static void sort(int[] a) {
        int temp[] = new int[a.length];//排序前,先建好一个等长的临时数组,避免在递归中频繁的开辟空间
        sort(a, 0, a.length - 1, temp);
    }

    private static void sort(int[] a, int left, int right, int temp[]) {
        if (left < right) {
            int middle = (left + right) / 2;
            sort(a, left, middle, temp);//左边归并排序,使得左子序列有序
            sort(a, middle + 1, right, temp);//右边归并排序,使得右子序列有序
            merge(a, left, right, middle, temp);//将两个有序的子数组合并操作
        }
    }

    private static void merge(int[] a, int left, int right, int middle, int[] temp) {
        int i = left;//左序列指针
        int j = middle + 1;//右序列指针
        int t = 0;//临时数组指针
        while (i <= middle && j <= right) {
            if (a[i] < a[j]) {
                temp[t++] = a[i++];
            } else {
                temp[t++] = a[j++];
            }
        }
        while (i <= middle) {//将左边的剩余元素填入临时表
            temp[t++] = a[i++];
        }

        while (j <= right) {//将右边的剩余元素填入临时表
            temp[t++] = a[j++];
        }
        t = 0;
        //将临时数组中的元素拷贝到原数组
        while (left <= right) {
            a[left++] = temp[t++];
        }
    }

归并参考文档

快速排序

    /**
     * 一趟快速排序算法是
     * 1.设置初始变量 i=0;j=N-1
     * 2.关键值 key = a[0]
     * 3.从j开始向前搜索(j--),找到第一个比key小的a[j],交换a[i]和a[j]
     * 4.从开始向后搜索(i++),找到第一个比key大的a[i],交换a[j]和a[i]
     * 5.重复3、4步骤,知道找到i=j
     *
     * @param a
     * @param start
     * @param end
     * @return
     */

    public static int[] qsort(int[] a, int start, int end) {
        int key = a[start];
        int i = start;
        int j = end;
        while (i < j) {
            while ((i < j) && a[j] > key) {//想象一下,数组就是一堆树桩,key是特殊的树桩,a[j](右边的)比特殊树桩大的j--,
                // a[i](左边的)比特殊树桩小的i++,则是整体数组升序排序渐渐变大,反之降序
                j--;
            }
            while ((i < j) && a[i] < key) {
                i++;
            }
            if (a[i] == a[j] && (i < j)) {
                i++;
            } else {
                int temp = a[i];
                a[i] = a[j];
                a[j] = temp;
            }
        }
        if (i - 1 > start) a = qsort(a, start, i - 1);
        if (j + 1 < end) a = qsort(a, j + 1, end);
        return a;
    }

桶排序

桶排序是将待排序集合处于同一个值域的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域,则拆分后形成多个桶,从值域上看是处于有序状态的。对每个桶中元素进行排序,则所有桶中所有构成的集合是已排序的。
桶排序过程存在两个关键的环节:1元素值域的划分,也就是说元素到桶的映射规则。映射规则需要根据待排序集合的元素分布特性进行选择,若规则设计的过于模糊、宽泛,则可能导致待排序集合中所有元素全部映射到一个桶上,则桶排序相比较排序算法演变。若映射规则设计过于具体、苛刻,则可能导致待排序集合中每个元素值映射到一个桶上,则桶排序向计数排序方式演化。2排序算法的选择,从待排序集合中元素映射到各个桶上的过程,并不存在元素的比较和交换操作,在堆各个桶中元素进行排序时,可以自主选择合适的排序算法,桶排序算法的复杂度和稳定性都根据选择的排序算法不同而不同。

待排序集合为:[-7, 51, 3, 121, -3, 32, 21, 43, 4, 25, 56, 77, 16, 22, 87, 56, -10, 68, 99, 70]
定义映射规则 Γ ( x ) = x 10 − c , 其 中 c = m i n 10 , m i n 为 元 素 最 小 值 , 即 以 间 隔 大 小 10 来 区 分 不 同 值 域 。 每 个 桶 排 序 算 法 为 : 堆 排 序 , 根 据 堆 排 序 特 性 可 知 , K 个 元 素 的 集 合 , 时 间 复 杂 度 为 : K log ⁡ 2^K , 算 法 不 保 持 稳 定 性 \Gamma(x) =\frac x {10} - c,其中c = \frac {min} {10},min为元素最小值,即以间隔大小10来区分不同值域。每个桶排序算法为:堆排序,根据堆排序特性可知,K个元素的集合,时间复杂度为:K \log \verb!2^K!,算法不保持稳定性 Γ(x)=10xc,c=10min,min,10KKlog2^K

伪代码实现

def bucketSort(arr):
	maxium,minum = max(arr),min(arr)
	bucketArr = [] for i in range(maxium /10 - minum / 10 +1)
	for i in arr: # map every element in array to the corresponding bucket 
		index = i / 10 - minum / 10
		bucketArr[index].append(i)
	arr.clear()
	for i in bucketArr:
		heapSort(i)# sort the elements in every bucket 
		arr.extends(i) # move the sorted elements in bucket to array
	

桶排序参考

计数排序

计数排序是一个非基于比较的排序算法,它的优势在于在一定的范围内的整数排序时,他的复杂度为O(k+n)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(K)>O(nlogn)的时候其效率反而不如基于比较排序。
计数排序对输入的数据有附加的限制条件:

  1. 输入的线性表的元素属于有限偏序集S;
  2. 设输入的线性表的长度为n,|S|=k (表示集合S中的元素的总数目为K),则k=O(n),这两个条件下计数排序的复杂性为O(n)。
    public static int[] countSort(int[] a) {
       int[] b = new int[a.length];
       int max = a[0], min = a[0];
       for (int i : a) {
           if (i > max) {
               max = i;
           }
           if (i < min) {
               min = i;
           }
       }
       int k = max - min + 1; //这里k的大小是要排序的数组中元素大小的极差+1
       int c[] = new int[k];
       for (int i = 0; i < a.length; i++) {
           c[a[i] - min] += 1;//记录相同元素在数组中的个数
       }
       for (int i = 1; i < c.length; ++i) {
           c[i] = c[i] + c[i - 1]; // 数组顺序累加
       }

       for (int i = a.length - 1; i >= 0; i--) {
           b[--c[a[i] - min]] = a[i];
       }
       return b;
   }

基数排序

基数排序属于‘分配式排序’,又称‘桶子法’,它是透过键值得部分资讯,将要排序的元素分配至某些‘桶’中,籍以达到排序的作用,基数排序算法属于稳定性的排序,其时间复杂度为O(nlog®m),其中r为所采取的基数,而m为堆数,在某些情况下,基数排序法的效率高于其他的稳定性排序法。

    /**
    * 基数排序
    *
    * @param arr
    * @param d
    */
   public static void RadixSort(int[] arr, int d) {//d表示最大有多少位
       int k = 0;//重排时数组下标
       int n = 1;
       int m = 1;//控制排序在哪一位
       int[][] temp = new int[10][arr.length];//数组的第一维表示可能的余数0-9
       int order[] = new int[10];//表示在该位是i的个数
       while (m <= d) {
           for (int i = 0; i < arr.length; i++) {
               int lsd = (arr[i] / n) % 10;
               temp[lsd][order[lsd]] = arr[i];
               order[lsd]++;
           }

           for (int i = 0; i < 10; i++) {
               if (order[i] != 0) {
                   for (int j = 0; j < order[i]; j++) {
                       arr[k] = temp[i][j];
                       k++;
                   }
               }
               order[i] = 0;
           }
           n *= 10;
           m++;
           k = 0;
       }
   }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值