算法导论之排序:快速排序、归并排序、计数排序、基数排序、桶排序

怼烎@ 2022-07-28 05:06 324阅读 0赞

问题描述:

输入:一个n个数的序列

输出:输入序列的一个排列<a1’,a2’,a3’,a4’,……,an’>。

相关知识:

关键字:排序问题中要排序的值。

卫星数据:与关键字一同存储的值,如map中的key和value。

快速排序:

快速排序在最坏情况下的时间复杂度为θ(n^2),但是它的期望时间复杂度是θ(nlgn),而且θ(nlgn)中隐含的常数因子非常小。并且快速排序是原址排序的。

分解:将数组A[p..r]划分为两个子数组A[p..q-1]和A[q+1..r],使A[p..q-1]中元素都小于等于A[q],而A[q]小于等于A[q+1..r]中的每一个元素。

解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]进行排序。

合并:因为子数组都是原址排序的,所以不需要合并操作。

代码的实现如下:

  1. package algorithms;
  2. import java.util.Random;
  3. public class QuickSort {
  4. public QuickSort() {
  5. }
  6. public static void doQuickSort(int A[], int left, int right) {
  7. if (left >= right) {
  8. return;
  9. }
  10. int partition = doPartition(A, left, right);
  11. doQuickSort(A, left, partition - 1);
  12. doQuickSort(A, partition + 1, right);
  13. return;
  14. }
  15. private static int doPartition(int[] A, int left, int right) {
  16. int i = left - 1;
  17. int x = A[right];
  18. int temp;
  19. for (int j = left; j < right; j++) {
  20. if (A[j] <= x) {
  21. i++;
  22. if (i != j) {// 线程测试这里if对执行性能的影响
  23. temp = A[i];
  24. A[i] = A[j];
  25. A[j] = temp;
  26. }
  27. }
  28. }
  29. temp = A[i + 1];
  30. A[i + 1] = A[right];
  31. A[right] = temp;
  32. return i + 1;
  33. }
  34. public static void main(String[] args) throws InterruptedException {
  35. long start = System.currentTimeMillis();
  36. int[] A = new int[1000];
  37. Random random = new Random();
  38. for (int i = 0; i < 1000; i++) {
  39. A[i] = random.nextInt(1000);
  40. }
  41. doQuickSort(A, 0, A.length - 1);
  42. Thread t = new Thread(new Runnable() {
  43. @Override
  44. public void run() {
  45. System.out.println("this is test thread");
  46. }
  47. });
  48. t.start();
  49. Thread.sleep(100);
  50. t.join();
  51. long end = System.currentTimeMillis();
  52. System.out.println(end - start);
  53. }
  54. }

快速排序的随机化:

与始终采用A[r]作为主元素的方法不同,随机抽样从子数组中随机选择一个元素作为主元。

对PARTITION和QUICKSORT的更改比较少,只需要随机生成一个主元就可以,代码如下:

  1. private static int doRandomPartition(int[] A, int left, int right) {
  2. Random random = new Random();
  3. int i = left + random.nextInt(right-left);
  4. int temp = A[right];
  5. A[right]=A[i];
  6. A[i]=temp;
  7. return doPartition(A, left, right);
  8. }

然后QUICKSORT调用这个doRandomPartition函数。

快速排序的随机化版本的期望运行时间是O(nlgn)。

归并排序:

分解:分解步骤仅仅计算子数组的中间位置,需要常量的时间,因此为θ(1)。

解决:我们递归的求解两个规模均为n/2的子问题,将贡献2T(n/2)的时间。

合并:一个具有n个元素的子数组的MERGE过程需要θ(n)的时间。

可以推出时间复杂度的函数关系为:

  1. θ(1) N=1
  2. T(N)=
  3. 2T(N/2)+θ(N) N>1

根据主方法可以求得归并排序的时间复杂度是θ(nlgn)。实现的代码如下:

  1. package algorithms;
  2. public class MergeSort {
  3. public MergeSort() {
  4. // TODO Auto-generated constructor stub
  5. }
  6. public static void doMergeSortII(int[] A, int m, int n){
  7. if (m == n) {
  8. return ;
  9. }
  10. int mid = (m + n)/2;
  11. doMergeSortII(A, m, mid);
  12. doMergeSortII(A, mid+1, n);
  13. int index = mid + 1,mark = 0,i = m;
  14. int[] B = new int[n-m+1];
  15. while(i <= mid && index <= n) {
  16. if (A[i] > A[index])
  17. B[mark++] = A[index++];
  18. else
  19. B[mark++] = A[i++];
  20. }
  21. while (i <= mid) {
  22. B[mark++]= A[i++];
  23. }
  24. while (index <= n) {
  25. B[mark++]= A[index++];
  26. }
  27. for (int j = 0; j < B.length; j++) {
  28. A[j+m]=B[j];
  29. }
  30. return ;
  31. }
  32. public static void main(String[] args) {
  33. int[] A = new int[]{0,9,8,7,6,5,4,3,2,1};
  34. doMergeSortII(A, 0, A.length-1);
  35. for (int i : A) {
  36. System.out.print(i + ",");
  37. }
  38. }
  39. }

接下来是三种线性时间复杂度的排序算法:计数排序、基数排序、桶排序。这些算法不是通过比较来排序的。

计数排序:

基本思想:对每一个输入元素x,确定小于x的元素的个数。(假设数据都是属于一个小区间的整数)

利用这一信息,可以直接把x放到它在输出数组中的位置上。代码如下:

  1. package algorithms;
  2. import java.util.Random;
  3. public class CountSort {
  4. public static void main(String[] args) {
  5. int A[]=new int[100];
  6. Random random = new Random();
  7. for (int i = 0; i < A.length; i++) {
  8. A[i]=random.nextInt(100);
  9. }
  10. int B[] = new int[A.length];
  11. doCountSort(A, B, A.length);
  12. for (int i : B) {
  13. System.out.print(i + "->");
  14. }
  15. }
  16. private static void doCountSort(int[] a, int[] b, int k) {
  17. int c[] = new int[k]; // c的长度受a中最大元素值的影响
  18. for (int i = 0; i < c.length; i++) {
  19. c[i] = 0;
  20. }
  21. for (int i = 0; i < a.length; i++) {
  22. c[a[i]] += 1;
  23. }
  24. for (int i = 1; i < c.length; i++) {
  25. c[i] += c[i - 1];
  26. }
  27. for (int i = a.length - 1; i >= 0; i--) {
  28. b[c[a[i]] - 1] = a[i];
  29. c[a[i]]--;
  30. }
  31. return;
  32. //计数排序用空间换时间
  33. }
  34. }

计数排序的时间复杂度是θ(n+k)。并且是稳定排序。

基数排序:

从最低位开始进行排序,依次往前进一位。

基数排序的时间复杂度是θ(d*(n+k))。并且是稳定排序。

桶排序:

桶排序假设输入的数据服从均匀分布,平均情况下它的时间代价为O(n)。(假设输入是一个随机的过程产生的,该过程将元素均匀、独立地分布到[0,1]区间上)

桶排序需要一个临时的数组来存放链表(即桶),在实现上我用arraylist来替代了链表的一些操作。代码如下:

  1. package algorithms;
  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.Comparator;
  5. import java.util.Random;
  6. public class BuckerSort {
  7. public static void main(String[] args) {
  8. Random random = new Random();
  9. double A[] = new double[100];
  10. for (int i = 0; i < A.length; i++) {
  11. A[i] = random.nextInt(100) / 100.0;//自动转型为double
  12. }
  13. doBucketSort(A);
  14. for (double d : A) {
  15. System.out.println(d);
  16. }
  17. }
  18. private static void doBucketSort(double[] a) {
  19. node b[] = new node[50];
  20. for (int i = 0; i < b.length; i++) {
  21. b[i] = new node();
  22. }
  23. for (int i = 0; i < a.length; i++) {
  24. node temp = new node();
  25. temp.num = a[i];
  26. temp.next = b[(int) (50 * a[i])].next;
  27. b[(int) (50 * a[i])].next = temp;
  28. }
  29. int j = 0;
  30. for (int i = 0; i < b.length; i++) {
  31. node n = b[i].next;
  32. ArrayList<node> al = new ArrayList<>();
  33. while (n != null) {
  34. al.add(n);
  35. n = n.next;
  36. }
  37. Collections.sort(al, new comparenode());
  38. for (node node : al) {
  39. a[j] = node.num;
  40. j++;
  41. }
  42. }
  43. }
  44. static class node {
  45. node next = null;
  46. double num = 0;
  47. }
  48. static class comparenode implements Comparator<node> {
  49. @Override
  50. public int compare(node o1, node o2) {
  51. if (o1.num > o2.num) {
  52. return 1;
  53. }
  54. return -1;
  55. }
  56. }
  57. }

桶排序的时间复杂度是 θ(n)。

总结:

如下图:

20160330220732920

下图来自百度百科

20160330220753202

发表评论

表情:
评论列表 (有 0 条评论,324人围观)

还没有评论,来说两句吧...

相关阅读