九大排序算法(选择、冒泡、插入、快速、归并、堆、桶、计数、基数)的递归和非递归实现及并行思路(C语言)

九大排序算法(选择、冒泡、插入、快速、归并、堆、桶、计数、基数)的递归和非递归实现及并行思路(C语言)

void swap(float *a, float *b) { float temp = *a; *a = *b; *b = temp; } // 交换两个浮点数

// 选择排序
// parallel: 并行查找最小值
void selection_sort(float *x, int size){ int min_idx; float tmp; for (int j = 0; j < size - 1; j++){ min_idx = j; for (int i = j + 1; i < size; i++){ if (x[i] < x[min_idx]) { min_idx = i; } } swap(&x[j], &x[min_idx]); } }

// 冒泡排序
// parallel: 奇数阶段: 并行地比较和交换所有奇数索引与其右侧相邻元素的值;偶数阶段: 并行地比较和交换所有偶数索引与其右侧相邻元素的值。 重复执行奇数阶段和偶数阶段,直到数组有序。
void bb_sort(float *x, int size){
    float tmp; bool flag = false;
    for (int j = size -1; j >= 1; j-- ){ flag = false; for (int i = 0; i < j; i++){ if (x[i] > x[i + 1]){ swap(&x[i], &x[i+1]);flag = true;} } if (!flag) break; }
}

// 插入排序
// parallel: 分块插入排序,最后合并
void insertion_sort(float *x, int size){ float tmp; int i, j; for (i = 1; i < size; i++ ){ tmp = x[i]; j = i - 1; while (j >= 0 && x[j] > tmp){ x[j + 1] = x[j]; j--; } x[j + 1] = tmp; } }

// 快速排序
// parallel: 分区、并行、合并
int mid3(float *x, int low, int high) { // 三数取中法,选取low, mid, high三个位置的元素,取其中值作为pivot
    int mid = (low + high) / 2;
    if (x[low] > x[mid]) swap(&x[low], &x[mid]); if (x[low] > x[high]) swap(&x[low], &x[high]); if (x[mid] > x[high]) swap(&x[mid], &x[high]);
    return mid;
}
void partition(float *x, int size, int *pivot) { // 根据pivot将数组分为两部分,左边的元素小于等于pivot,右边的元素大于pivot
    int low = 0, high = size - 1, i = low - 1, j = low, pivotIndex = mid3(x, low, high);
    float pivotValue = x[pivotIndex]; swap(&x[pivotIndex], &x[high]);
    for (j = low; j < high; j++) { if (x[j] <= pivotValue) { i++; swap(&x[i], &x[j]); } }
    swap(&x[i + 1], &x[high]); *pivot = i + 1;
}
void q_sort_rec(float *x, int size) { if (size > 1) { int pivot; partition(x, size, &pivot); q_sort_rec(x, pivot); q_sort_rec(x + pivot + 1, size - pivot - 1); } }
void q_sort_nrec(float *x, int size) {
    int *stack, top_idx = -1, left, right, pivot;
    // 最坏情况通常发生在每次分区时,枢轴总是选择到最小或最大的元素,递归深度会达到最大,即等于数组的大小 n,每次处理一个子数组时,会向栈中压入左右边界,即栈空间的需求为两倍。
    stack = (int *)malloc(2 * size * sizeof(int)); stack[++top_idx] = 0; stack[++top_idx] = size - 1;
    while (top_idx >= 0){
        right = stack[top_idx--]; left = stack[top_idx--]; partition(x + left, right - left + 1, &pivot); pivot += left;
        if (pivot - 1 > left) {stack[++top_idx] = left; stack[++top_idx] = pivot - 1;} if (pivot + 1 < right) {stack[++top_idx] = pivot + 1; stack[++top_idx] = right;}
    }
    free(stack);
}

// 归并排序
// parallel: 并行排序、并行合并规约
void merge(float *x, int sl, int sr){
    float *l_start = x, *r_start = x + sl, *tmp = (float *)malloc((sl + sr) * sizeof(float)); int i = 0, j = 0, k = 0;
    while (i < sl && j < sr){ if (l_start[i] <= r_start[j]) { tmp[k++] = l_start[i++]; } else { tmp[k++] = r_start[j++]; } }
    while (i < sl) { tmp[k++] = l_start[i++]; } while (j < sr) { tmp[k++] = r_start[j++]; }
    for (i = 0; i < sl + sr; i++) { x[i] = tmp[i]; } free(tmp);
}
void merge_sort_rec(float *x, int size){ if (size > 1){ int sl = size / 2, sr = size - sl; merge_sort_rec(x, sl); merge_sort_rec(x + sl, sr); merge(x, sl, sr); } }
void merge_sort_nrec(float *x, int size) {
    for (int width = 1; width < size; width *= 2) { // width of each part : 1, 2, 4, 8, ...; e.g. 1 + 1 -> 2; 2 + 2 -> 4; 4 + 4 -> 8; ...
        int left = 0;
        while (left < size) { // 合并每一对子数组
            int mid = left + width - 1, right = (mid + width < size) ? mid + width : size - 1;  // [left, mid], [mid+1, right] ; 此处限制了right的范围,避免超出数组的大小
            if (mid < size - 1) {  merge(x + left, mid - left + 1, right - mid); }// 当前子数组确实有需要合并的右侧部分,才进行合并操作。
            left += 2 * width;
        }
    }
}

// 堆排序
// parallel: 堆的构建:从最后一个非叶子节点开始,依次执行 heaptifyDown 操作。这些操作之间是相对独立的,可以并行执行。
void getLeftChild(int i, int *lc_idx) { *lc_idx = 2 * i + 1; } // 获取左子节点的索引
void getRightChild(int i, int *rc_idx) { *rc_idx = 2 * i + 2; } // 获取右子节点的索引
void heaptifyDown(float *arr, int size, int idx) { // 比较当前节点与其左右子节点的大小。选择最小的子节点,如果当前节点大于这个子节点,则交换它们,并继续向下调整。
    int cur = idx;
    while (true) {
        int lc, rc, minidx = cur; getLeftChild(cur, &lc); getRightChild(cur, &rc);
        if (lc < size && arr[lc] < arr[minidx]) { minidx = lc; } // 找到当前节点和左子节点中的最小值
        if (rc < size && arr[rc] < arr[minidx]) { minidx = rc; } // 找到当前节点和右子节点中的最小值
        if (minidx == cur) break; // 如果当前节点是最小值,退出循环
        float tmp = arr[cur]; arr[cur] = arr[minidx]; arr[minidx] = tmp; cur = minidx;  // 交换当前节点和最小值节点, 更新 cur 为新的索引
    }
}
void buildHeap(float *arr, int size){
    // 使用heaptifyDown的原因是,如果使用 heaptifyUp从树的底部向上调整,每个节点在最坏情况下可能需要一直移到树的根部(并且底部的节点数量多)。
    // 这意味着可能需要执行更多的比较和交换操作。而如果我们从上往下调整,每个节点最多只需要向下移动几层(通常是树的高度),这使得整体效率非常高。
    for (int i = size / 2 - 1; i >= 0; i--) heaptifyDown(arr, size, i); // 从最后一个非叶子节点(size/2 - 1)开始,向上调整堆;
}
void heap_sort(float *heap, int size){ // 堆排序,从大道小排序
    buildHeap( heap, size); // 构建小顶堆
    while (size > 1) {
        float tmp = heap[0]; heap[0] = heap[size - 1]; heap[size - 1] = tmp; size--; // 将堆顶元素与堆的最后一个元素交换,每次循环最小的元素被调整到末尾,堆的大小减1
        heaptifyDown(heap, size, 0); // 从堆顶开始重新调整堆
    }
}

// 桶排序
// parallel: 将元素分配到桶中的过程可以并行化;对每个桶中的元素进行排序也可以并行化;将桶中的元素取出也可以并行化。
int compare_floats(const void *a, const void *b) { float fa = *(const float*)a, fb = *(const float*)b; return (fa > fb) - (fa < fb);  }
void bucket_sort(float *arr, int size, int k){
    if (k == 0) k = 4; // 默认桶的数量为4
    float max_val, min_val, interval, **buckets = (float **)malloc(k * sizeof(float *)); int *bucket_size = (int *)malloc(k * sizeof(int)), idx;
    if (size > 0) { max_val = arr[0]; min_val = arr[0];} // 最大最小值用于计算桶间隔
    for (int i = 0; i < k; i++) { buckets[i] = (float *)malloc(sizeof(float) * size); bucket_size[i] = 0; }
    for (int i = 1; i < size; i++) { if (arr[i] > max_val) max_val = arr[i]; if (arr[i] < min_val) min_val = arr[i]; } interval = (max_val - min_val) / k;
    if (max_val == min_val) { for (int i = 0; i < k; i++) { free(buckets[i]);} free(buckets); free(bucket_size); return;} // 计算桶的间隔;如果最大值和最小值相等,直接返回; 避免出现后续的除以0的情况
    for (int i = 0; i < size; i++) { idx = (int)((arr[i] - min_val) / interval); if (idx >= k) idx = k - 1; buckets[idx][bucket_size[idx]++] = arr[i]; } // 计算当前元素所属的桶;当 arr[i] 恰好等于 max_val 时,idx 会计算为 k,而 k 是桶的数量,这会导致数组越界。
    for (int i = 0; i < k; i++) { qsort(buckets[i], bucket_size[i], sizeof(float), compare_floats); } // 对每个桶进行排序
    idx = 0; for (int i = 0; i < k; i++) { for (int j = 0; j < bucket_size[i]; j++) { arr[idx++] = buckets[i][j]; } free(buckets[i]); } free(buckets); free(bucket_size); // 将桶中的元素取出
}

// 计数排序
// parallel: 并行归约(reduction)技术来在多个线程中协同查找最大值; 并行计数; 并行前缀和计算; 并行拷贝
void counting_sort(int *arr, int size){ // 仅适用于非负整数
    if (size <= 0) return; int max_val = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] > max_val) max_val = arr[i]; } // 找到最大值,用于确定计数数组大小[0, max_val]
    int *counter = (int *) calloc(max_val + 1, sizeof(int)), *res = (int *)malloc(sizeof(int) * size); // 计数数组(最终存储前缀和);结果数组
    for (int i = 0; i < size; i++) { counter[arr[i]]++; } for (int i = 1; i <= max_val; ++i) { counter[i] += counter[i - 1]; } // 计数和计算前缀和
    for (int i = size - 1; i >= 0; i--) { int cur_val = arr[i]; res[counter[cur_val] - 1] = cur_val; counter[cur_val]--; } // 从后往前遍历原数组,通过前缀和找到元素在结果数组中的位置,存入后,前缀和计数减1
    memcpy(arr, res, sizeof(int) * size); free(counter); free(res);
}

// 基数排序
// parallel: 并行归约(reduction)技术来在多个线程中协同查找最大值; 并行计数; 并行前缀和计算; 并行拷贝; 多位并行处理
void get_kth_digit(int x, int k, int *digit){ while (k--) { x /= 10; } *digit = x % 10; } // 获取数字x的第k位数字
void counting_sort_digit(int *arr, int size, int k){
    int *counter = (int *) calloc(10, sizeof(int)), *res = (int *)malloc(sizeof(int) * size); // 计数数组(最终存储前缀和);结果数组
    for (int i = 0; i < size; ++i) { int digit; get_kth_digit(arr[i], k, &digit); counter[digit]++; } // 累计计数
    for (int i = 1; i < 10; ++i) { counter[i] += counter[i - 1]; } // 计算前缀和
    for (int i = size - 1; i >= 0 ; --i) { int digit; get_kth_digit(arr[i], k, &digit); res[counter[digit] - 1] = arr[i]; counter[digit]--; } // 从后往前遍历,根据前缀和确定元素在结果数组中的位置
    memcpy(arr, res, sizeof(int) * size); free(counter); free(res);
}
void radix_sort(int *arr, int size){ // 仅适用于非负整数
    if (size <= 0)  return; int max_val = arr[0], max_digit = 0;
    for (int i = 1; i < size; i++) { if (arr[i] > max_val) max_val = arr[i]; } // 找到最大值,用于确定计数数组大小[0, max_val]
    while (max_val > 0) { max_val /= 10; max_digit++; } // 计算最大值的位数
    for (int i = 0; i < max_digit; i++) { counting_sort_digit(arr, size, i); } // 从低位到高位,依次对每一位进行计数排序
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值