常见排序算法(三)(快速排序、归并排序、计数排序)

相关文章:

常见排序算法(零)(各类排序算法总结与比较)

常见排序算法(一)(冒泡排序、插入排序)

常见排序算法(二)(选择排序)

常见排序算法(三)(快速排序、归并排序、计数排序)

常见排序算法(四)(基数排序、桶排序)


快速排序(Quick Sort)

       快速排序是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

基本思路:

1、设待排序数组为data

2、设置key = 0,i = 0,j = data.length – 1,从j开始从后往前遍历数组,当找到data[j] < data[key]的数的时候,交换j和key位置的数,让key = j,j递减;然后开始递增i,当找到data[i]>= data[key]时,交换i和key位置的数,让key = i,i递减。

3、当i < j时,重复第二步,直到i == j,此时将数组以i为界分割为两段,一段是[0, i - 1],一段是[i + 1, data.length - 1],对这两段分别进行快速排序,直到不能再分段为止,排序结束。

快速排序的java实现:

iterationSort(0, data.length - 1);
/**
 * 递归的快速排序
 * @param begin 排序的开始位置
 * @param end 排序的结束位置
 */
private void iterationSort(int begin, int end) {
    if (begin >= end) {  //  如果分段中的数小于等于1个,直接返回
        return;
    }
    int top = begin;
    int bottom = end;
    int key = data[top];
    while (top < bottom) {
        while (bottom > top) {
            if (data[bottom] <= key) {  //  注意在这两处判断的地方要有一处有等于号
                swap(top, bottom);
                break;
            }
            bottom--;  //  不管是否发生交换,这里都需要移动
        }
        while (top < bottom) {
            if (data[top] > key) {
                swap(top, bottom);
                break;
            }
            top++;  //  不管是否发生交换,这里都需要移动
        }
    }
    iterationSort(begin, top - 1);  // 递归时记得减1和加1
    iterationSort(top + 1, end);
}

       快速排序的最优时间复杂度为O(nlogn),最差时间复杂度为O(n2),平均时间复杂度为O(nlogn),空间复杂度为O(n)。具有速度快,适用范围广(实数都可以用),使用方便等优点。缺点在于在最差情况下(逆序或者全部相等),时间复杂度很高,达到O(n2)。并且迭代深度过深,容易引发栈溢出错误。


归并排序(Merge Sort)

       归并排序是将待排序数组不停地分段,直到每段中的数只有一个,就开始两两合并,合并的过程中需要开辟一个新数组,用来存放合并后的序列。合并的时候每段从头开始遍历,将两段中最小的数放在前面,然后依次扫描,扫描结束后,如果某一段中还有数据没有扫描到,则直接接到新数组后面,最终得到排好序的序列。

基本思路:

1、递归分段,每次将一段分为两段,直到不能再分为止(段中数的数量为1)

2、合并分段,将返回的段两两合并为一段再返回,直到返回到最上层为止,此时返回的段长度和待排序数组的长度一致。

归并排序的java实现:

data = iterationSort(0, data.length - 1);  //  data为待排序数组
/**
 * 迭代的归并排序
 * @param begin 开始排序的起始位置
 * @param end 结束排序的终止位置
 * @return
 */
private int[] iterationSort(int begin, int end) {
    if (begin >= end) {  //  如果段中数的数量为1,构造一个新数组返回
        return new int[] {data[begin]};
    }
    int middle = (begin + end) / 2;  //  得到中间位置
    //  分别向下递归分段,然后合并
    return merge(iterationSort(begin, middle), iterationSort(middle + 1, end));
}
/**
	 * 合并分段
	 * @param left 第一段(已排好序)
	 * @param right 第二段(已排好序)
	 * @return
	 */
private int[] merge(int[] left, int[] right) {
    int leftLength = left.length;  //  第一段数组长度
    int rightLength = right.length;  //  第二段数组长度
    int[] result = new int[leftLength + rightLength];  //  合并后新数组长度是原来两段数组长度之和
    int counter = 0;  //  用于移动新数组下标
    int leftSign = 0;  //  用于移动第一段数组的下标
    int rightSign = 0;  //  用于移动第二段数组的下标
    while (leftSign < leftLength && rightSign < rightLength) {  //  当两个下标都还在范围内时
        if (left[leftSign] < right[rightSign]) {  //  此时第一段下标位置的数比第二段下标位置的数小
            result[counter] = left[leftSign];  //  将该数复制进新数组
            leftSign++;  //  移动第一段的下标
        } else {  //  此时第一段下标位置的数不比第二段下标位置的数小
            result[counter] = right[rightSign];  //  将该数复制进新数组
            rightSign++;  //  移动第二段的下标
        }
        counter++;  //  移动新数组的下标
    }
    if (leftSign < leftLength) {  //  如果第一段的下标还没有移动到第一段末尾,将剩余的数复制到新数组末端
        for (; leftSign < leftLength; leftSign++, counter++) {
            result[counter] = left[leftSign];
        }
    }
    if (rightSign < rightLength) {  //  如果第二段的下标还没有移动到第二段末尾,将剩余的数复制到新数组末端
        for (; rightSign < rightLength; rightSign++, counter++) {
            result[counter] = right[rightSign];
        }
    }
    return result;  //  返回新数组
}

       归并排序的最优时间复杂度,最差时间复杂度,平均时间复杂度都是O(nlogn),空间复杂度为O(n),是一种稳定的排序算法。优点是时间复杂度稳定,对于各种情况下时间复杂度都达到比较排序算法最优的O(nlogn),在待排序序列分段有序的情况下表现优异。但是需要辅助空间,占用内存,在数据量较大时会占用较多的内存空间。


计数排序(Counting Sort)

       计数排序不是一种基于比较的排序。算法思想是先计算出待排序数组的最大值和最小值的差值,然后创建一个新数组,长度为差值的大小,初始化新数组全部元素为0。计算待排序数组中每个数与最小值的差值,以此差值为下标,新数组此下标位置的元素加1。遍历完之后,从前向后扫描新数组,使每个值为前面所有值之和,此时新数组中的某一下标元素的值代表差值小于等于此下标的待排序数组元素的数量。最后,根据这个数量来放置元素,得到排序后的数组。计数排序是一种不稳定的排序算法。

基本思路:

1、深拷贝待排序数组,得到copyData,查找最小值min,最大值max,得到差值diff= max – min

2、根据diff的大小创建一个计数数组counts,初始化全部元素为0

3、遍历copyData,每个元素与min做差得到一个值,计数数组中对应下标元素加1

4、从前往后遍历计数数组,每个值等于前面所有值之和(处理后的计数数组中每个元素的值代表的含义是待排序数组中差值小于等于此元素下标的数量,例如counts[10] = 100代表待排序数组中与min做差小于等于10的数有100个)

5、向待排序数组中放置数值,放置的方法为:遍历copyData,取出每个数后做差,得到差值min’,假设counts[min’] = 50,那么,这个数应该放在待排序数组中的第49位(因为数组下标从0开始计数,所以要减1),然后counts[min’]递减。(可以预见到,下一个差值为min’的数会被放置在待排序数组中的第48位,两者的相对位置发生了变化,因此计数排序是一种不稳定的排序算法)。以此方法遍历完整个copyData,得到的新的待排序数组就已经排好序了。

计数排序的java实现:
int[] copyData = Arrays.copyOf(data, data.length);
int min = copyData[0];
int max = copyData[0];
//  获得最大最小值
for (int x : copyData) {
    if (x > max) {
        max = x;
        continue;
    }
    if (x < min) {
        min = x;
    }
}

int[] counts = new int[max - min + 1];
Arrays.fill(counts, 0);
//  计算数组中每个值和最小值的差,并计数
for (int x : copyData) {
    counts[x - min]++;
}

//  得到数组中大于某个数的值一共有多少个
for (int i = 1; i < counts.length; i++) {
    counts[i] = counts[i] + counts[i - 1];
}
//  根据数量放置值,每放置一个,对应的值的数量减一
for (int i = 0; i < copyData.length; i++) {
    int index = copyData[i] - min;
    data[counts[index] - 1] = copyData[i];
    counts[index]--;
}
       计数排序的时间复杂度为O(n + k),空间复杂度为O(n + k)(空间复杂度查找不到资料,因为所需空间为一个待排序数组的拷贝和一个长度为差值的数组,就猜想了一下可能是这个),在k值较小时,计数排序的表现要优于基于排序的算法(基于排序算法的最低时间复杂度为O(nlogn))。不过,计数排序的使用具有局限性,从算法的描述可以看出,待排序数组的元素必须是的整数,涉及小数,计数排序就用不上了。如果待排序元素全部为整数并且最大值和最小值的差值不会过大(上亿的话就需要几亿个字节的辅助存储空间了(以一个整数四个字节来计算)),那么计数排序一般会优于快排等基于比较的排序算法。

本文所使用的java代码已上传至github,为java project:https://github.com/sysukehan/SortAlgorithm.git


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值