目次
0. アルゴリズムの概要
0.1 アルゴリズムの分類
10 個の一般的な並べ替えアルゴリズムは、次の 2 つの大きなカテゴリに分類できます。
- 比較ソート: 要素の相対的な順序を比較によって決定し、時間計算量が O(nlogn) を超えることができないため、非線形時間比較ソートとも呼ばれます。
- 非比較ソート: 要素の相対的な順序は比較によって決定されません。比較ベースのソートの下限を突破し、線形時間で実行できるため、線形時間非比較ソートとも呼ばれます。
0.2 アルゴリズムの複雑さ
0.3 関連概念
- 安定: a が元々 b の前にあり、a=b の場合、ソート後も a は b の前にあります。
- 不安定: a が元々 b の前にあり、a=b の場合、ソート後に a が b の後ろに現れる可能性があります。
- 時間計算量: 並べ替えられたデータに対する操作の合計数。これは、n が変化するときの操作数の規則性を反映しています。
- 空間の複雑さ:コンピューター内のアルゴリズムを指します。
これは内部実行に必要な記憶領域の尺度であり、データ サイズ n の関数でもあります。
1. バブルソート
バブル ソートは単純な並べ替えアルゴリズムです。ソート対象の配列を繰り返し調べて、一度に 2 つの要素を比較し、順序が間違っている場合はそれらを交換します。シーケンスを訪問する作業は、交換する必要がなくなるまで、つまりシーケンスがソートされるまで繰り返されます。このアルゴリズムの名前は、小さな要素が交換によってシーケンスの先頭にゆっくりと「浮く」という事実に由来しています。
1.1 アルゴリズムの説明
- 隣接する要素を比較します。最初の値が 2 番目の値より大きい場合は、両方を交換します。
- 隣接する要素の各ペアに対して、最初の最初のペアから最後の最後のペアまで同じことを行い、最後の要素が最大の数値になるようにします。
- 最後の要素を除くすべての要素に対して上記の手順を繰り返します。
- 並べ替えが完了するまで手順 1 ~ 3 を繰り返します。
1.2 アニメーションのプレゼンテーション
1.3 コードの実装
int *bubbleSort(int arr[], int n)
{
for(int i =0 ; i< n-1; ++i)
{
for(int j = 0; j < n-i-1; ++j)
{
if(arr[j] > arr[j+1])
{
int tmp = a[j] ; //交换
a[j] = a[j+1] ;
a[j+1] = tmp;
}
}
}
return arr;
}
2. 選択範囲の並べ替え
選択ソート (Selection-sort) は、シンプルで直感的なソート アルゴリズムです。仕組み: まず、ソートされていないシーケンス内で最小 (最大) の要素を見つけ、それをソートされたシーケンスの先頭に格納します。次に、引き続きソートされていない残りの要素から最小 (最大) の要素を見つけて、それをソート済みのシーケンスに置きます。シーケンスの終わり。すべての要素がソートされるまで続きます。
2.1 アルゴリズムの説明
n レコードの直接選択と並べ替えでは、n-1 個の直接選択と並べ替えを通じて順序付けされた結果を取得できます。具体的なアルゴリズムは次のように説明されます。
- 初期状態: 順序付けされていない領域は R[1..n] で、順序付けされた領域は空です。
- i 番目のソート (i=1,2,3...n-1) が開始されると、現在の順序付き領域と順序なし領域はそれぞれ R[1..i-1] と R(i..n) になります。このソート プロセスでは、現在の順序なし領域から最小のキーを持つレコード R[k] を選択し、それを順序なし領域の最初のレコード R と交換します。これにより、R[1..i] と R[i+1 .. n) それぞれ、レコード数が 1 増加した新しい順序付けられた領域と、レコード数が 1 減少した新しい無秩序領域になります。
- n-1 回の移動が終了し、配列がソートされました。
2.2 アニメーションのプレゼンテーション
2.3 コードの実装
int *bubbleSort(int arr[], int n) {
int len = n;
int minIndex, 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];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
2.4 アルゴリズム解析
最も安定した並べ替えアルゴリズムの 1 つ。どのようなデータが入力されても、時間計算量は O(n2) であるため、使用する場合はデータ サイズが小さいほど優れています。唯一の利点は、追加のメモリ領域を占有しないことです。理論的に言えば、選択ソートは、ほとんどの人がソートするときに通常考えるソート方法でもあります。
3. 挿入ソート
Insertion-Sort のアルゴリズムの説明は、シンプルで直感的な並べ替えアルゴリズムです。これは、順序付けられたシーケンスを構築することによって機能し、ソートされていないデータの場合は、ソートされたシーケンスの後ろから前にスキャンして、対応する位置を見つけて挿入します。
3.1 アルゴリズムの説明
一般に、挿入ソートはインプレースを使用して配列に実装されます。具体的なアルゴリズムは次のように説明されます。
- 最初の要素から始めて、要素はソートされていると見なされます。
- 次の要素を取り出し、ソートされた要素シーケンスを後ろから前にスキャンします。
- (ソートされた) 要素が新しい要素より大きい場合は、要素を次の位置に移動します。
- 並べ替えられた要素が新しい要素以下になる位置が見つかるまで、手順 3 を繰り返します。
- その位置に新しい要素を挿入した後。
- 手順2~5を繰り返します。
3.2 アニメーションのプレゼンテーション
3.2 コードの実装
int *bubbleSort(int arr[], int n) {
int len = n;
int preIndex, current;
for (int i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
3.4 アルゴリズム解析
挿入ソートの実装では、通常、インプレースソート (つまり、O(1) 個の追加スペースのみを使用するソート) が使用されるため、前から前へスキャンする過程で、要素を段階的に後方にソートし、最新の要素の挿入スペースを提供します。
4. シェルソート
1959 年に Shell によって発明された、O(n2) を突破した最初のソート アルゴリズムは、単純な挿入ソートの改良版です。挿入ソートとは異なり、遠くにある要素を最初に比較します。ヒル ソートは、縮小増分ソートとも呼ばれます。
4.1 アルゴリズムの説明
まず、ソート対象のレコード シーケンス全体を直接挿入ソート用のいくつかのサブシーケンスに分割します。具体的なアルゴリズムの説明は次のとおりです。
- インクリメンタルシーケンス t1、t2、...、tk を選択します。ここで、ti>tj、tk=1。
- 増分シーケンス番号 k に従って、シーケンスを k 回ソートします。
- 各ソートでは、対応する増分 ti に従って、ソート対象の列が長さ m のいくつかのサブシーケンスに分割され、各サブリストに対して直接挿入ソートがそれぞれ実行されます。インクリメント係数が 1 の場合のみ、シーケンス全体がテーブルとして扱われ、テーブルの長さがシーケンス全体の長さになります。
4.2 アニメーションのプレゼンテーション
4.3 コードの実装
int *bubbleSort(int arr[], int n) {
int len = n;
for (int gap = len / 2; gap > 0; gap = gap / 2) {
// 注意:这里和动图演示的不一样,动图是分组执行,实际操作是多个分组交替执行
for (int i = gap; i < len; i++) {
int j = i;
int current = arr[i];
while (j - gap >= 0 && current < arr[j - gap]) {
arr[j] = arr[j - gap];
j = j - gap;
}
arr[j] = current;
}
}
return arr;
}
4.4 アルゴリズム解析
ヒルソートの核心は、間隔シーケンスの設定にあります。間隔シーケンスは事前に設定することも、動的に定義することもできます。間隔シーケンスを動的に定義するアルゴリズムは、Algorithms (第 4 版) の共著者である Robert Sedgewick によって提案されました。
5. マージソート
マージ ソートは、マージ操作に基づく効率的な並べ替えアルゴリズムです。このアルゴリズムは、分割統治の非常に典型的なアプリケーションです。順序付けられたサブシーケンスを結合して、完全に順序付けられたシーケンスを取得します。つまり、最初に各サブシーケンスを順番に作成し、次にサブシーケンス セグメントを順番に作成します。2 つのソート済みリストを 1 つのソート済みリストにマージすることを、双方向マージと呼びます。
5.1 アルゴリズムの説明
- 長さ n の入力シーケンスを長さ n/2 の 2 つのサブシーケンスに分割します。
- 2 つのサブシーケンスにそれぞれマージ ソートを使用します。
- 2 つのソートされたサブシーケンスを最終的なソートされたシーケンスにマージします。
5.2 アニメーションのプレゼンテーション
5.3 コードの実装
//递归实现
int *bubbleSort(int arr[], int left,int right) {
// [left, right)
if(left < right) {
int mid = (left + right) / 2;//从中间截开
merge_sort(arr,left, mid);//把左边沿中间截开
merge_sort(arr, mid + 1, right);//把右边沿中间截开
merge(arr, left, right, mid);//合并
}
return arr;
}
//接下来这个函数是合并的过程。
void merge(int a[],int left,int right,int mid) {
int s[right - left];//一个新数组用来存储排序好的数组
int i = left, j = mid + 1;//两个变量分别指向左边和右边两组数的第一个数
int sor = 0;
while (i <= mid && j <= right) {
if (a[i] < a[j]) {//归并的过程
s[sor++] = a[i++];
}
else {
s[sor++] = a[j++];
}
}
//当一组数已经全部排进去之后,再将另外一组数的全部数字都排进去
while (i <= mid) s[sor++] = a[i++];
while (j <= right) s[sor++] = a[j++];
//合并之后的数组替换原数组
for (int m = 0; m < right - left; ++m) {
a[left++] = s[m];
}
}
//迭代实现
int *bubbleSort(int arr[], int left,int right) {
//对数组arr归并排序迭代实现,len为数组长度,从小到大排序,O(nlog2^n),稳定
/*核心思想,i表示步长,也就是左右两组各几个元素比较
,从第一轮左右每组1个开始,每轮步长增大一倍
,比较后从小到大存入temp,再对剩余元素进行处理
,最后将排好序的temp返回arr数组
*/
//分别为步长、temp下标、左边起始下标、左边终点下标、右边起始下标、右边终止下标
int i,next,left_min,left_max,right_min,right_max;
int len = right - left + 1;
//新建一个temp数组,长度等于初始数组长度
int temp[len];
//每轮比较左右两个步长i长度的区间,每轮后i*=2
for(i=1; i < len; i *= 2) {
//从数组0号开始,下一组的起始位置等于上一组的终止位置,如果下一组左边步长都不够就不比了
for(left_min=0; left_min < len-i; left_min = right_max){
//右边起始位置=左边终止位置=左边起始加步长i
right_min = left_max = left_min + i;
//右边终止位置=右边起始位置加步长i
right_max = right_min + i;
next = 0;//temp的下标
if(right_max > len){//如果右边越界
right_max = len;//右边终止位置最大值只能为len
}
while(left_min < left_max && right_min < right_max){//左右都没到尽头
if(arr[left_min] < arr[right_min]){//左小右大,左边存入temp
temp[next++] = arr[left_min++];
}else{//右小左大,右边存入temp
temp[next++] = arr[right_min++];
}
}
/*左边还有一组剩余元素,右边已到终止位置
,说明左边剩余元素最大,将剩余元素移到右边最后
,如果是右边有剩余,则不需要移了已经在最后*/
while(left_min < left_max){
arr[--right_min] = arr[--left_max];
}
while(next > 0){//把排好序的temp部分返回arr
arr[--right_min] = temp[--next];
}
}
}
return arr;
}
5.4 アルゴリズム解析
マージソートは安定したソート方法です。選択ソートと同様、マージ ソートのパフォーマンスは入力データの影響を受けませんが、時間計算量が常に O(nlogn) であるため、パフォーマンスは選択ソートよりもはるかに優れています。その代償として、追加のメモリ領域が必要になります。
6. クイックソート
クイックソートの基本的な考え方: ソート対象のレコードはワンパスソートによって 2 つの独立した部分に分離され、レコードの一方の部分のキーワードが他方の部分のキーワードより小さく、その後、レコードの 2 つの部分が小さくなります。レコードを個別に並べ替えることで、全体の順序が整います。
6.1 アルゴリズムの説明
クイックソートは分割統治法を使用して、文字列 (リスト) を 2 つの部分文字列 (サブリスト) に分割します。具体的なアルゴリズムは次のように説明されます。
- シーケンスから「ピボット」(ピボット) と呼ばれる要素を選択します。
- シーケンスを並べ替えます。基準値より小さいすべての要素は基準値の前に配置され、基準値より大きいすべての要素は基準値の後ろに配置されます (同じ数値をどちらの側にも配置できます)。このパーティションが終了すると、ベンチマークはシーケンスの途中になります。これはパーティション操作と呼ばれます。
- 基本値より小さい要素を含む部分配列と、基本値より大きい要素を含む部分配列を再帰的に並べ替えます。
6.2 アニメーションのプレゼンテーション
6.3 コードの実装
//递归实现
int *bubbleSort(int arr[], int left, int right) {
int i = left, j = right;
int pivot = arr[(left + right) / 2];
int temp;
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
if (left < j)
quickSort(arr, left, j);
if (i < right)
quickSort(arr, i, right);
return arr;
}
//迭代实现
int *bubbleSort(int arr[], int left, int right) {
int s[right-left+1];
int top = -1;
s[++top] = left;
s[++top] = right;
while (top != -1) {
int r = s[top--];
int l = s[top--];
int pivot = arr[(l + r) / 2];
int i = l, j = r;
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
if (l < j) {
s[++top] = l;
s[++top] = j;
}
if (i < r) {
s[++top] = i;
s[++top] = r;
}
}
return arr;
}
7. ヒープソート
ヒープソート (Heapsort) は、ヒープのデータ構造を使用して設計されたソート アルゴリズムを指します。スタッキングは完全なバイナリ ツリーに近似する構造であり、同時にスタッキングの性質も満たします。つまり、子ノードのキー値またはインデックスは常にその親ノードよりも小さい (または大きい) ものです。
7.1 アルゴリズムの説明
- 並べ替えるキーワードの最初のシーケンス (R1、R2....Rn) を、最初の順序付けされていない領域である大きな上部ヒープに構築します。
- 最上位要素 R[1] を最後の要素 R[n] と交換し、新しい非順序領域 (R1, R2,...Rn-1) と新しい順序領域 (Rn) を取得し、R [1, 2...n-1]<=R[n];
- 交換後の新しいヒープ先頭 R[1] はヒープの性質に反する可能性があるため、現在の未順序領域 (R1, R2,...Rn-1) を新しいヒープに調整して R を結合する必要があります。 [1] 再び無秩序領域を使用します。領域の最後の要素が交換されて、新しい無秩序領域 (R1、R2....Rn-2) と新しい順序領域 (Rn-1、Rn) が得られます。この処理を順序付け領域の要素数が n-1 個になるまで繰り返し、ソート処理全体が完了します。
7.2 アニメーションのプレゼンテーション
7.3 コードの実装
// 堆排序:(最大堆,有序区)。从堆顶把根卸出来放在有序区之前,再恢复堆。
void max_heapify(int arr[], int start, int end) {
//建立父节点指标和子节点指标
int dad = start;
int son = dad * 2 + 1;
while (son <= end) { //若子节点在范围内才做比较
if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点指标,选择最大的
son++;
if (arr[dad] > arr[son]) //如果父节点大于子节点代表调整完成,直接跳出函数
return;
else { //否则交换父子內容再继续子节点与孙节点比較
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
int *heap_sort(int arr[], int len) {
//初始化,i从最后一个父节点开始调整
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
//先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完成
for (int i = len - 1; i > 0; i--) {
swap(arr[0], arr[i]);
max_heapify(arr, 0, i - 1);
}
return arr;
}
8. カウントソート
カウントソートは、比較に基づくソートアルゴリズムではなく、入力データ値をキーに変換し、追加の配列スペースに格納することが中心です。一種の線形時間計算量として、カウンティング ソートでは、入力データが特定の範囲の整数である必要があります。
8.1 アルゴリズムの説明
- 並べ替える配列内の最大要素と最小要素を見つけます。
- 配列内で値が i である各要素の出現数をカウントし、それを配列 C の i 番目の項目に格納します。
- すべてのカウントを累積します (C の最初の要素から開始して、各項目が前の項目に追加されます)。
- ターゲット配列を逆に埋めます。各要素 i を新しい配列の C(i) 番目の項目に置き、要素が配置されるたびに C(i) から 1 を減算します。
8.2 アニメーションのプレゼンテーション
8.3 コードの実装
int *bubbleSort(int arr[], int len) {
int maxValue = arr[0];
for (int i = 0; i < len; ++i) {
maxValue = fmax(maxValue, arr[i]);
}
int bucket[maxValue + 1];
memset(bucket, 0, sizeof(bucket));
int sortedIndex = 0;
int arrLen = len;
int bucketLen = maxValue + 1;
//计数数组
for (int i = 0; i < arrLen; ++i) {
bucket[arr[i]]++;
}
//累计数组
for (int i = 1; i < bucketLen; ++i) {
bucket[i] += bucket[i-1];
}
int temp[len];
for (int i = len; i > 0; i--) {
temp[--bucket[arr[i - 1]]] = arr[i - 1];
}
memcpy(arr, temp, sizeof(temp));
return arr;
}
8.4 アルゴリズム解析
カウンティングソートは安定したソートアルゴリズムです。入力要素が 0 から k までの n 個の整数の場合、時間計算量は O(n+k)、空間計算量も O(n+k) となり、ソート速度はどの比較ソート アルゴリズムよりも高速になります。カウンティング ソートは、k がそれほど大きくなく、シーケンスが比較的集中している場合に効率的なソート アルゴリズムです。
9. バケットソート
バケット ソートは、カウンティング ソートのアップグレード バージョンです。これは関数のマッピング関係を利用するもので、高効率の鍵はこのマッピング関数の決定にあります。バケット ソートの動作原理 (バケット ソート): 入力データが均一に分散されていると仮定すると、データは限られた数のバケットに分割され、各バケットは個別にソートされます (他のソート アルゴリズムを使用することも、引き続き使用することも可能です)再帰的に) バケットソート)。
9.1 アルゴリズムの説明
- 定量的配列を空のバケットとして設定します。
- 入力データを走査し、データを 1 つずつ対応するバケットに入れます。
- 空ではない各バケットを並べ替えます。
- 空ではないバケットからソートされたデータを連結します。
9.2 画像のプレゼンテーション
9.3 コードの実装
#define NBUCKET 6 // 桶的数量
//建立桶
struct Node {
float data;
struct Node *next;
};
// 桶排序,arr 为待排序序列
float *bubbleSort(float arr[], int len) {
int i, j;
struct Node **buckets;
// 创建所有桶
buckets = (struct Node **)malloc(sizeof(struct Node *) * NBUCKET);
// 设置每个桶为空桶
for (i = 0; i < NBUCKET; ++i) {
buckets[i] = NULL;
}
// 根据规定,将 arr 中的每个元素分散存储到各个桶中
for (i = 0; i < len; ++i) {
struct Node *current;
int pos = arr[i] * 10; //根据规则,确定元素所在的桶
//创建存储该元素的存储块,并连接到指定的桶中
current = (struct Node *)malloc(sizeof(struct Node));
current->data = arr[i];
current->next = buckets[pos];
buckets[pos] = current;
}
// 调用自定义的排序算法,对各个桶进行排序
for (i = 0; i < NBUCKET; ++i) {
buckets[i] = InsertionSort(buckets[i]);
}
// 合并所有桶内的元素
for (j = 0, i = 0; i < NBUCKET; ++i) {
struct Node *node;
node = buckets[i];
while (node) {
arr[j++] = node->data;
node = node->next;
}
}
return arr;
}
// 自定义的排序算法,用于对各个桶内元素进行排序
struct Node *InsertionSort(struct Node *list) {
struct Node *k, *nodeList;
if (list == NULL || list->next == NULL) {
return list;
}
nodeList = list;
k = list->next;
nodeList->next = NULL;
while (k != NULL) {
struct Node *ptr;
if (nodeList->data > k->data) {
struct Node *tmp;
tmp = k;
k = k->next;
tmp->next = nodeList;
nodeList = tmp;
continue;
}
for (ptr = nodeList; ptr->next != 0; ptr = ptr->next) {
if (ptr->next->data > k->data)
break;
}
if (ptr->next != 0) {
struct Node *tmp;
tmp = k;
k = k->next;
tmp->next = ptr->next;
ptr->next = tmp;
continue;
}
else {
ptr->next = k;
k = k->next;
ptr->next->next = 0;
continue;
}
}
return nodeList;
}
9.4 アルゴリズム解析
バケット ソートでは、最良の場合、線形時間 O(n) が使用されます。他の部分の時間計算量は O(n) であるため、バケット ソートの時間計算量は、バケット間でのデータの並べ替えの時間計算量に依存します。明らかに、バケットの分割が小さくなるほど、各バケット間のデータが少なくなり、並べ替えにかかる時間が短くなります。ただし、それに応じてスペース消費量も増加します。
10. 基数ソート
カーディナリティの並べ替えでは、最初に低位に従って並べ替えてから収集し、次に上位に従って並べ替えてから収集するということを、最高位まで繰り返します。場合によっては、一部の属性に優先順位が付けられ、最初に優先順位の低い順に並べ替えられ、次に優先順位の高い順に並べ替えられることがあります。最終的な順序は、優先度が高いものが先、同じ優先度が高くても優先度が低いものが先となります。
10.1 アルゴリズムの説明
- 配列内の最大数を取得し、桁数を取得します。
- arr は元の配列で、各ビットが最下位ビットから取得されて基数配列を形成します。
- 基数に対してカウントソートを実行します(カウントソートが狭い範囲の数値に適しているという機能を使用します)。
10.2 アニメーションのプレゼンテーション
10.3 コードの実装
int *bubbleSort(int arr[], int len) {
//max为数组中最大值
int max = arr[0];
int base = 1;
//找出数组中的最大值
for (int i = 0; i < n; i++)
{
if (arr[i] > max)
{
max = arr[i];
}
}
//循环结束max就是数组最大值
//临时存放数组元素的空间
int tmp[n];
//循环次数为最大数的位数
while (max / base > 0)
{
//定义十个桶,桶里面装的不是数据本身,而是每一轮排序对应(十、白、千...)位的个数
//统计每个桶里面装几个数
int bucket[10] = { 0 };
for (int i = 0; i < n; i++)
{
//arr[i] / base % 10可以取到个位、十位、百位对应的数字
bucket[arr[i] / base % 10]++;
}
//循环结束就已经统计好了本轮每个桶里面应该装几个数
//将桶里面的元素依次累加起来,就可以知道元素存放在临时数组中的位置
for (int i = 1; i < 10; i++)
{
bucket[i] += bucket[i - 1];
}
//循环结束现在桶中就存放的是每个元素应该存放到临时数组的位置
//开始放数到临时数组tmp
for (int i = n - 1; i >= 0; i--)
{
tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
bucket[arr[i] / base % 10]--;
}
//把临时数组里面的数拷贝回去
for (int i = 0; i < n; i++)
{
arr[i] = tmp[i];
}
base *= 10;
}
return arr;
}
10.4 アルゴリズム解析
基数ソートは個別にソートして個別に収集することに基づいているため、安定しています。ただし、基数ソートのパフォーマンスはバケット ソートよりわずかに悪く、各キーワード バケットの割り当てには O(n) 時間の計算量が必要で、割り当て後の新しいキーワード シーケンスの取得には O(n) 時間の計算量が必要です。ソート対象のデータが d 個のキーワードに分割できる場合、基数ソートの時間計算量は O(d*2n) になります。もちろん、d は n よりもはるかに小さいため、基本的には線形です。
基数ソートの空間計算量は O(n+k) です。ここで、k はバケットの数です。一般的に言えば、n>>k であるため、追加のスペースは約 n 個必要になります。