归并排序
基本思想:分而治之,然后将有序子表合并。
算法思路:
1. 将两段相邻有序表放到临时数组中;
2. 两个值i,j分别记录当前读取位子;
3. 读取当前位子值并比较,小的放到数组中;
4. 如果i或者j超过表长,则将剩下的直接复制过去。
int n = 8;
int *b = (int *)malloc((n + 1)*sizeof(int));
void merge(int arr[], int low, int mid, int high)
{
int i, j, k;
for (k = low; k <= high; ++k)
b[k] = arr[k];
for (i = low, j = mid + 1, k = i; i <= mid&&j <= high; ++k){
if (b[i] <= b[j])
arr[k] = b[i++];
else
arr[k] = b[j++];
}
while (i <= mid)
arr[k++] = b[i++];//第一个表剩下的复制过去
while (j <= high)
arr[k++] = b[j++];//第二个表剩下的复制过去
}
void mergeSort(int arr[], int low, int high)
{
if (low < high){
int mid = (low + high) / 2;
mergeSort(arr, low, mid);
mergeSort(arr, mid+1, high);
merge(arr, low, mid, high);
print(arr, 8, 1);
}
}
输出:
6 5 3 1 8 7 2 4
5 6 3 1 8 7 2 4
5 6 1 3 8 7 2 4
1 3 5 6 8 7 2 4
1 3 5 6 7 8 2 4
1 3 5 6 7 8 2 4
1 3 5 6 2 4 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
空间复杂度:辅助空间占n个单元,还要一个递归栈,深度最大为 log2n ,所以复杂度为 O(n)
时间复杂度:一趟归并必定要移动n次(从辅助空间b到arr),需要 log2n 次归并,故为 nlog2n
稳定性:一个个比的,也没有交换的情况,很稳定。
基数排序
基本思想:通过序列中各个元素的值,对排序的N个元素进行若干趟的“分配”与“收集”来实现排序。
分配:我们将L[i]中的元素取出,首先确定其个位上的数字,根据该数字分配到与之序号相同的桶中
收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集形成新的一个待排序列L[ ]
对新形成的序列L[]重复执行分配和收集元素中的十位、百位…直到分配完该序列中的最高位,则排序结束。
以下是最低位优先时的基数排序的实现:
void radixSortLSD(int arr[], int n)
{
int i, maxVal INT_MIN, digitPos = 1;
int *bucket = new int[n];
for (i = 0; i < n; ++i)
if (arr[i]>maxVal)
maxVal = arr[i];
while (maxVal/digitPos > 0){
int digitCount[RADIX] = { 0 };
//统计各个桶有几个数
for (i = 0; i < n; ++i)
++digitCount[arr[i] / digitPos % 10];
//将个数转换为桶中最后一个数的下标
for (i = 1; i < RADIX; ++i)
digitCount[i] += digitCount[i - 1];
//依次从后往前计算每个数属于哪个桶,再根据那个桶中的数决定放在辅助数组哪个位子,从后往前能保证算法稳定
for (i = n - 1; i >= 0; --i)
bucket[--digitCount[arr[i]/digitPos%10]] = arr[i];
//替换到原数组中
for (i = 0; i < n; ++i)
arr[i] = bucket[i];
//下次循环位数增加
digitPos *= 10;
}
}
时间复杂度:
设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为
O(d(n+r))
,其中,一趟分配时间复杂度为
O(n)
,一趟收集时间复杂度为
O(r)
,共进行d趟分配和收集。
空间复杂度:
需要一个指向队列长度的辅助空间和一个radix长度的辅助空间,共
O(n+r)
各算法复杂度表
加上前几篇的内容,表扩充为:
算法 | 平均时间复杂度 | 最好时间复杂度 | 最差时间复杂度 | 空间复杂度 | 稳定性 | 备注 |
---|---|---|---|---|---|---|
直接插入 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | |
折半插入 | O(n2) | O(nlog2n) | O(n2) | O(1) | 稳定 | |
shell | O(n1.3) | O(n) | O(n2) | O(1) | 不稳定 | 和增量序列有关 |
冒泡 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | 子序列全局有序,不同于插入排序 |
快排 | O(nlog2n) | O(nlog2n) | O(n2) | O(log2n) | 不稳定 | |
简单选择 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 | |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 | |
2路归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 稳定 | |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(n+r) | 稳定 | 其中,d 为位数,r 为基数,n 为原数组个数。 |