算法与数据结构(二):排序

趁着需要面试百度的机会,复习了常用的排序方法。

1、快速排序

平均时间复杂度:O(nlogn),最差情况时间复杂度:O(n^2),即序列以有序的情况

空间复杂度:O(1),不需要开辟额外的空间

实现细节:递归的分而治之,

在每一个递归中,将最后一个数作为比较数mid,pa指示小于的部分,pb指示大于等于mid的部分。

当pa=pb时,比较结束。将序列分割为小于mid、等于mid和大于mid三段,分别递归

2、计数排序

时间复杂度:O(n)

空间复杂度:O(码本)

实现细节:在知道元素取值范围的情况下使用

3、选择排序

时间复杂度:O(n^2)

空间复杂度:O(1)

不稳定:序列(7) 2 5 9 3 4 [7] 1第一轮遍历就将(7)放到了最后

实现细节:每一轮循环遍历后面n-i个元素,找出最小值与第i个元素交换

4、冒泡排序

时间复杂度:O(n^2)

空间复杂度:O(1)

实现细节:每一轮循环从i开始,两两比较x和x+1

优化:若某一轮循环没有发生交换,则表明自元素i后的序列有序,对于局部有序序列可以有O(n)的性能

5、归并排序

时间复杂度:O(nlogn)

空间复杂度:O(n)

实现细节:首先递归的将序列等距离划分,向上返回时归并段两两比较,写入辅助数组,然后复制到对应的归并段位置,保证向上返回的归并段段内有序。

6、堆排序

一种树形选择排序,是对直接选择排序的改进。

时间复杂度:O(nlogn),其中O(n)为建堆的时间复杂度,O(nlogn)为每次调整堆顶元素的时间复杂度

空间复杂度:O(1)

应用场景:适合大数据排序场合。TOPK

实现细节:

堆排序包括两个基本步骤:建立最小(大)堆和排序。其中的核心操作HeapAdjust(Heap H, int s, int m)。

HeapAdjust输入参数为序列中的元素s和序列长度m。

初始条件:s元素左右子树皆为堆

递归的:比较s与左右子树根节点的大小,若s是最小值,则不发生交换,s保持了堆的特性,退出;若发生交换,则左/右子树的堆特性无法保持,递归的HeapAdjust发生交换的子树。

退出:原来以s为根节点的子树保持了堆的特性

建堆:从最后一个根节点length/2开始调整[length/2….0]这些根节点的堆特性。

有序输出:循环的将头结点与最后一个结点交换,然后调用HeapAdjust保持自根节点的堆特性。

     

堆排序源码:

HeapAdjust操作

  1. public static void sift(int[] input, int s, int length) {
  2.    int left = 2 * s + 1;
  3.    int min = left;
  4.      
  5.    if (s > length / 2 - 1) return; //判断向下递归的退出条件
  6.      
  7.    //寻找根、左子树根、右子树根三者间的最小值
  8.    if (left + 1 < length && input[left] > input[left + 1])
  9.       min = left + 1;
  10.      
  11.    if (input[s] <= input[min]) {
  12.       return;
  13.    } else {
  14.       //交换,破坏了子树堆的特性,递归的调整
  15.       int tmp = input[s];
  16.       input[s] = input[min];
  17.       input[min] = tmp;
  18.      
  19.       sift(input, min, length);
  20.    }
  21. }

主过程:

  1. public static void heapsort(int[] input) {
  2.    //建堆
  3.    for (int i = input.length / 2 - 1; i >= 0; i--) {
  4.       sift(input, i, input.length);
  5.    }
  6.    //输出堆头,调整堆头
  7.    for (int i = input.length - 1; i >= 0; i--) {
  8.       int tmp = input[0];
  9.       input[0] = input[i];
  10.       input[i] = tmp;
  11.       sift(input, 0, i);
  12.    }
  13. }

7、寻找最大的K个数

方法一:利用堆排序的特性

时间复杂度:O(nlogK)

首先建立一个K元素的最小堆,即根节点小于左右子树节点;

接着遍历数组元素,若x>root,则root<-x,调整堆性质O(logK)

整个过程为O(n*logK)

方法二:线性期望时间O(n)

    采用randomized-select方法,从数组中随机选取一个数x,将数组划分为小于x的Sa和大于x的Sb的两个部分,i为x的坐标

    若i=K,则x就是第K个数,同时可以输出前k个数

    若i > K,则randomized-select(Sa,K)

    若i < K,则randomized-select(Sb, K-i)

但是在最坏情况下,具有O(n^2)的时间复杂度,也就是每次划分极不均匀,选到的随机数不是最大,就是最小,每次划分为O(n),共要划分n次

     

     

     

     

   

原文地址:https://www.cnblogs.com/zjgtan/p/3377680.html