问题:谈谈你所知道的排序算法

算法概念

1.算法分类

十种常见排序算法可以分为两大类:

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
    算法分类

2.相关概念

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数

3.算法复杂度

排序方法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
插入排序 O(n^2) O(n^2) O(n) O(1) 稳定
希尔排序 O(n^{1.3}) O(n^2) O(n) O(1) 稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 稳定
冒泡排序 O(n^2) O(n^2) O(n) O(1) 稳定
快速排序 O(n{log_2{n}}) O(n^2) O(n{log_2{n}}) O(n{log_2{n}}) 稳定
堆排序 O(n{log_2{n}}) O(n{log_2{n}}) O(n{log_2{n}}) O(1) 稳定
归并排序 O(n{log_2{n}}) O(n{log_2{n}}) O(n{log_2{n}}) O(n) 稳定
计数排序 O(n+k) O(n+k) O(n+k) O(n+k) 稳定
桶排序 O(n+k) O(n^2) O(n) O(n+k) 稳定
基数排序 O(n*k) O(n*k) O(n*k) O(n+k) 稳定

十种算法

一、插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  • 算法描述
  1. 一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
  2. 从第一个元素开始,该元素可以认为已经被排序;
  3. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  4. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  5. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  6. 将新元素插入到该位置后;
  7. 重复步骤2~5。
  • 算法演示
插入排序
  • 代码实现
- (NSArray *)insertionSort:(NSArray *)arr {
   NSMutableArray *array = [[NSMutableArray alloc] initWithArray:arr];
   int preIndex = 0;
   id current;
   for (int i = 1; i < array.count; i++) {
       preIndex = i - 1;
       current = array[I];
       while (preIndex >= 0 && array[preIndex] > current) {
           array[preIndex + 1] = array[preIndex];
           preIndex--;
       }
       array[preIndex + 1] = current;
   }
   return array;
}
  • 算法分析

插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

二、希尔排序(Shell Sort)

希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n^2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

  • 算法描述

增量gap的范围: 1<= gap < 待排序数组的长度 (gap 需为 int 值)
增量的取值: 一般的初次取序列(数组)的一半为增量,以后每次减半,直到增量为1。
第一个增量=数组的长度/2,
第二个增量= 第一个增量/2,
第三个增量=第二个增量/2,
以此类推,最后一个增量=1。

  1. 根据增量gap将数组分成若干组;
  2. 分别对每个增量序列进行插入排序;
  3. 重复步骤1~2,直至gap=1。
  • 算法演示
希尔排序
  • 代码实现
- (NSArray *)ShellSort:(NSArray *)arr {
    NSMutableArray *array = [[NSMutableArray alloc] initWithArray:arr];
    int len = (int)array.count;
    id temp;
    int gap = len / 2;
    while (gap > 0) {
        for (int i = gap; i < array.count; i++) {
            temp = array[I];
            int preIndex = i - gap;
            while (preIndex >= 0 && array[preIndex] > temp) {
                array[preIndex + gap] = array[preIndex];
                preIndex -= gap;
            }
            array[preIndex + gap] = temp;
        }
        gap /= 2;
    }
    return array;
}
  • 算法分析

希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。

三、选择排序(Selection Sort)

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

  • 算法描述

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。

  1. 初始状态:无序区为R[1..n],有序区为空;
  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  3. n-1趟结束,数组有序化了
  • 算法演示
选择排序
  • 代码实现
- (NSArray *)selectionSort:(NSArray *)arr {
   NSMutableArray *array = [[NSMutableArray alloc] initWithArray:arr];
   int len = (int)array.count;
   int minIndex;
   id temp;
   for (int i = 0; i < len - 1; i++) {
       minIndex = I;
       for (int j = i + 1; j < len; j++) {
           if (arr[j] < arr[minIndex]) {     // 寻找最小的数
               minIndex = j;                 // 将最小数的索引保存
           }
       }
       temp = arr[i];
       array[i] = arr[minIndex];
       array[minIndex] = temp;
   }
   return arr;
}
  • 算法分析

表现最稳定的排序算法之一,因为无论什么数据进去都是O(n^2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

四、冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

  • 算法描述
  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 重复步骤1~3,直到排序完成。
  • 算法演示
冒泡排序
  • 代码实现
- (NSArray *)bubbleSort:(NSArray *)arr {
   NSMutableArray *array = [[NSMutableArray alloc] initWithArray:arr];
   int len = (int)array.count;
   for (int i = 0; i < len - 1; i++) {
       for (int j = 0; j < len - 1 - i; j++) {
           if (array[j] > array[j+1]) {        // 相邻元素两两对比
               id temp = array[j+1];        // 元素交换
               array[j+1] = array[j];
               array[j] = temp;
           }
       }
   }
   return arr;
}
  • 算法分析

程序员第一排序,你懂得~

五、快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

  • 算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
  • 算法演示
快速排序
  • 代码实现
@property (nonatomic, strong) NSMutableArray *array;

- (NSArray *)quickSort:(NSArray *)arr left:(int)left right:(int)right {
   self.array = [[NSMutableArray alloc] initWithArray:arr];
   if (left < right) {
       int partitionIndex = [self partitionLeft:left right:right];
       [self quickSort:self.array left:left right:partitionIndex-1];
       [self quickSort:self.array left:partitionIndex+1 right:right];
   }
   return self.array;
}

- (int)partitionLeft:(int)left right:(int)right {
   int pivot = left;                   // 设定基准值(pivot)
   int index = pivot + 1;
   for (int i = index; i <= right; i++) {
       if (self.array[i] < self.array[pivot]) {
           [self swap:i j:index];
           index++;
       }
   }
   [self swap:pivot j:index-1];
   return index-1;
}

- (void)swap:(int)i j:(int)j {
   id temp = self.array[I];
   self.array[i] = self.array[j];
   self.array[j] = temp;
}

六、堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

  • 算法描述
  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区。

    1.1.假设给定无序序列结构如下:

    1.2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

    1.3.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

    1.4.这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];

    将堆顶元素9和末尾元素4进行交换
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    3.1.重新调整结构,使其继续满足堆定义。

    3.2.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8。

    3.3.后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序。
  • 算法演示
堆排序
  • 代码实现
- (NSArray *)HeapSort:(NSArray *)arr {
   NSMutableArray *array = [NSMutableArray arrayWithArray:arr];
   // 初始化一个终止计算结点索引位置(一次排序后把最大值移动到数组计算结点范围的末尾)
   int endIndex = (int)(array.count - 1);
   while (endIndex > 0) {
       // 遍历一次数组按照堆的规则排序一次
       [self binaryTreeSortWithArray:array maxIndex:endIndex];
       // 将建立堆得到的最大根与数组中最后一个数组互换
       [array exchangeObjectAtIndex:0 withObjectAtIndex:endIndex--];
   }
   return array;
}
/// 二叉树规则(大根堆)排序 -- (排序范围为索引为0的位置到待排序结点最大索引位置)
- (void)binaryTreeSortWithArray:(NSMutableArray *)array maxIndex:(int)maxIndex {
   // 从可排序结点的最大索引处开始进行排序
   for (int i = maxIndex; i >= 0; i--) {
       // 获取移动后的子结点的索引
       int newIndex = [self compareNodesSizeWithArray:array maxIndex:maxIndex fatherNodeIndex:i];
       while (newIndex > 0) {
           newIndex = [self compareNodesSizeWithArray:array maxIndex:maxIndex fatherNodeIndex:newIndex];
       }
   }
}
/// 递归比较指定索引位置的结点与其左右子节点的大小
- (int)compareNodesSizeWithArray:(NSMutableArray *)array maxIndex:(int)maxIndex fatherNodeIndex:(int)fatherIndex {
   // 获取当前索引位置的左右子节点索引
   int leftIndex = fatherIndex * 2 + 1;
   int rightIndex = fatherIndex * 2 + 2;

   // 该索引和左右子节点值
   NSNumber *currentValue = [array objectAtIndex:fatherIndex];
   NSNumber *leftValue = nil;
   NSNumber *rightValue = nil;
   if (leftIndex <= maxIndex) {
       leftValue = [array objectAtIndex:leftIndex];
   }
   if (rightIndex <= maxIndex) {
       rightValue = [array objectAtIndex:rightIndex];
   }

   if (rightValue != nil) { // 右子节点存在, 则左子节点必定存在, 判断三者的值并把最大值换到父节点上
       if (currentValue.doubleValue > leftValue.doubleValue && currentValue.doubleValue > rightValue.doubleValue) {
           return -1;
       } else {
           // 与父节点进行交换的结点的索引
           int index = (leftValue.doubleValue > rightValue.doubleValue ? leftIndex : rightIndex);
           [array exchangeObjectAtIndex:fatherIndex withObjectAtIndex: index];
           // 移动完毕之后同时还要进行(被移动到子节点的值)与(该被移动节点的左右子节点值)进行比较 -- 递归
           return index;
       }
   }
   else if (leftValue != nil) {// 右结点不存在, 左结点存在, 判断左结点是否大于父结点的值
       if (currentValue.doubleValue < leftValue.doubleValue) {
           [array exchangeObjectAtIndex:fatherIndex withObjectAtIndex:leftIndex];
           return leftIndex;
       } else {
           return -1;
       }
   }
   return -1;
}
  • 算法分析

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。

七、归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

  • 算法描述
  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。
  • 算法演示
归并排序
  • 代码实现
#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   NSMutableArray * array = [NSMutableArray arrayWithObjects:@5,@6,@2,@4,@3,@8,@7,@1, nil];
   //调用排序
   [self mergeSortArray:array];
   
   NSLog(@"%@", array);
   // Do any additional setup after loading the view.
}
- (void)mergeSortArray:(NSMutableArray *)array {
   //创建一个副本数组
   NSMutableArray * auxiliaryArray = [[NSMutableArray alloc]initWithCapacity:array.count];
   //对数组进行第一次二分,初始范围为0到array.count-1
   [self mergeSort:array auxiliary:auxiliaryArray low:0 high:array.count-1];
}

- (void)mergeSort:(NSMutableArray *)array auxiliary:(NSMutableArray *)auxiliaryArray low:(int)low high:(int)high {
   //递归跳出判断
   if (low>=high) {
       return;
   }
   //对分组进行二分
   int middle = (high - low)/2 + low;

   //对左侧的分组进行递归二分 low为第一个元素索引,middle为最后一个元素索引
   [self mergeSort:array auxiliary:auxiliaryArray low:low high:middle];

   //对右侧的分组进行递归二分 middle+1为第一个元素的索引,high为最后一个元素的索引
   [self mergeSort:array auxiliary:auxiliaryArray low:middle + 1 high:high];

   //对每个有序数组进行回归合并
   [self merge:array auxiliary:auxiliaryArray low:low middel:middle high:high];
}

- (void)merge:(NSMutableArray *)array auxiliary:(NSMutableArray *)auxiliaryArray low:(int)low middel:(int)middle high:(int)high {
   //将数组元素复制到副本
   for (int i=low; i<=high; i++) {
       auxiliaryArray[i] = array[I];
   }
   //左侧数组标记
   int leftIndex = low;
   //右侧数组标记
   int rightIndex = middle + 1;

   //比较完成后比较小的元素要放的位置标记
   int currentIndex = low;

   while (leftIndex <= middle && rightIndex <= high) {
       //此处是使用NSNumber进行的比较,你也可以转成NSInteger再比较
       if ([auxiliaryArray[leftIndex] compare:auxiliaryArray[rightIndex]]!=NSOrderedDescending) {
           //左侧标记的元素小于等于右侧标记的元素
           array[currentIndex] = auxiliaryArray[leftIndex];
           currentIndex++;
           leftIndex++;
       } else {
           //右侧标记的元素小于左侧标记的元素
           array[currentIndex] = auxiliaryArray[rightIndex];
           currentIndex++;
           rightIndex++;
       }
   }
   //如果完成后左侧数组有剩余
   if (leftIndex <= middle) {
       for (int i = 0; i<=middle - leftIndex; i++) {
           array[currentIndex +i] = auxiliaryArray[leftIndex +I ];
       }
   }
}
@end
  • 算法分析

归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。

八、计数排序(Counting Sort)

计数排序 的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort) 是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

  • 算法描述

1.找出待排序的数组中最大和最小的元素;

  1. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  2. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  3. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
  • 算法演示
计数排序
  • 代码实现
- (NSArray *)countSort:(NSArray *)arr {
   int maxValue = 0;
   for (NSNumber *num in arr) {
       if (num.intValue > maxValue) {
           maxValue = num.intValue;
       }
   }
   NSMutableArray *array = [NSMutableArray array];
   int arrLen = (int)arr.count;
   NSMutableDictionary<NSString *,NSNumber *> *bucket = [NSMutableDictionary dictionary];
   int sortedIndex = 0;
   int bucketLen = maxValue + 1;

   for (int i = 0; i < arrLen; i++) {
       NSString *key = [NSString stringWithFormat:@"%@", arr[I]];
       if (!bucket[key]) {
           bucket[key] = @0;
       }
       NSNumber *num = bucket[key];
       bucket[key] = @(num.intValue + 1);
   }

   for (int j = 0; j < bucketLen; j++) {
       NSString *key = [NSString stringWithFormat:@"%d", j];
       while(bucket[key].intValue > 0) {
           array[sortedIndex ++] = @(j);
           bucket[key] = @(bucket[key].intValue - 1);
       }
   }

   return array;
}
  • 算法分析

计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。

九、桶排序(Bucket Sort)

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

  • 算法描述
  1. 设置一个定量的数组当作空桶;
  2. 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  3. 对每个不是空的桶进行排序;
  4. 从不是空的桶里把排好序的数据拼接起来。
  • 算法演示
桶排序
  • 代码实现
- (NSArray *)bucketSort:(NSArray *)arr{
   NSMutableArray *array = [NSMutableArray arrayWithArray:arr];
   //预计每个桶内能装3个
   NSInteger size = 3;
   
   //桶的数量
   NSInteger bucketsCount = array.count / size;
   if ((array.count % size) > 0) {
       bucketsCount ++;
   }
   
   //找出最小值和最大值
   NSInteger min = [array[0] integerValue];
   NSInteger max = [array[0] integerValue];
   
   for (NSNumber *number in array) {
       if (number.integerValue < min) {
           min = number.integerValue;
       }
       if (number.integerValue > max) {
           max = number.integerValue;
       }
   }
   
   //平均值
   NSInteger average = ceil((double)(max - min)/(double)bucketsCount);
   
   NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
   
   for (NSInteger i = 0; i < bucketsCount; i++) {
       NSMutableArray *bucketArray = [NSMutableArray array];
       NSString *key = [NSString stringWithFormat:@"%@-%@",@(min + i * average),@(min + (i + 1) * average)];
       [dictionary setValue:bucketArray forKey:key];
   }
   
   for (NSNumber *number in array) {
       NSInteger i = floor((double)(number.integerValue - min) / >(double)average);
       NSString *key = [NSString stringWithFormat:@"%@-%@",@(min + i * average),@(min + (i + 1) * average)];
       NSMutableArray *bucketArray = [dictionary valueForKey:key];
       [bucketArray addObject:number];
   }
   
   NSInteger length = 0;
   for (NSInteger i = 0; i < dictionary.allKeys.count; i++) {
       NSString *key = [NSString stringWithFormat:@"%@-%@",@(min + i * average),@(min + (i + 1) * average)];
       NSMutableArray *bucketArray = [dictionary objectForKey:key];
       [self mergeSortArray:bucketArray];
       [array replaceObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(length, bucketArray.count)] withObjects:bucketArray];
       length += bucketArray.count;
   }
   return array;
}
  • 算法分析

桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。

十、基数排序(Radix Sort)

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

  • 算法描述
  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);
  • 算法演示
基数排序
  • 代码实现
- (NSArray *)radixSort:(NSMutableArray *)arr {
   NSMutableArray *array = [NSMutableArray arrayWithArray:arr];
   int len = (int)array.count;
   NSMutableArray *radixArrays = [NSMutableArray array];
   for (int i=0; i<10; i++) {
       NSMutableArray *arr = [NSMutableArray array];
       for (int i=0; i<len; i++) {
           [arr addObject:@0];
       }
       [radixArrays addObject:arr];
   }
   
   int KEYNUM_31 = 10; //关键字个数,这里为32位整形位数
   for (int pos = 1; pos <= KEYNUM_31; pos++) {
       for (int i = 0; i < len; i++) {
           int num = [self GetNumInPos:[array[i] intValue] pos:pos];
           int num2 = [radixArrays[num][0] intValue]+1;
           radixArrays[num][0] = @(num2);
           radixArrays[num][num2] = array[I];
       }
       
       for (int i = 0,j = 0; i < 10; i++) {
           for (int k = 1; k <= [radixArrays[i][0] intValue]; k++) {
               array[j] = radixArrays[i][k];
               j=j+1;
           }
           radixArrays[i][0] = @0; //复位
       }
   }
   return array;
}

- (int)GetNumInPos:(int)num pos:(int)pos{
   int temp = 1;
   for (int i=0; i<pos-1; i++) {
       temp *= 10;
   }
   return (num/temp)%10;
}
  • 算法分析

基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。
基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容