概述
十大经典排序算法的C语言实现
插入排序类:
直接插入排序(Insertion Sort)、
折半插入排序(Binary Insertion Sort)、
希尔排序(缩小增量排序)(Shell’s Sort / Diminishing Increment Sort)
交换排序类:
冒泡排序(Bubble Sort)、
快速排序(Quicksort)
选择排序类:
简单选择排序(Selection sort)、
堆排序(Heapsort)
归并排序类:
归并排序(Merge Sort)、
非基于比较的排序类(分配式排序):
桶排序(Bucket Sort)、
基数排序(Radix Sort)
具体的算法思想和原理请大家参照海量网络教程和纸质参考书,下面是十种经典排序算法的C语言实现代码,在此仅对int类型数组进行排序。代码采用了随机生成数组的方式测试各算法的正确性,具体的解释见注释
#include<stdio.h>
#include<stdlib.h>
#define printProcess 1 // 宏置为 1 时打印排序中间过程,0 则不打印
// 辅助链式存储的链结点结构体
typedef struct nnode {
int num;
struct nnode *next;
} node;
// 辅助函数
int *randomArray(int n, int min, int max); // 生成随机的数组
void printArray(int *A, int n); // 依次打印数组所有元素
int biInsert(int *A, int lo, int hi, int x); // 二分查找插入点(折半插入排序使用)
void swap(int *A, int i, int j); // 交换数组两个位置的元素(冒泡排序、选择排序使用)
int getPivot(int *A, int lo, int hi); // 快速排序划分时选取轴点(快速排序使用)
int partition(int *A, int lo, int hi); // 快速排序划分函数(快速排序使用)
void adjustHeap(int *A, int n, int k); // 调整堆结构(k结点下滤)(堆排序使用)
void merge(int *A1, int len1, int *A2, int len2); // 归并两个有序数列(归并排序使用)
int maxItem(int *A, int n); // 数列中元素的最大值(桶排序与基数排序使用)
int minItem(int *A, int n); // 数列中元素的最小值(桶排序使用)
node *collect(node *BF, node *BT); // 收集桶中元素连成一条链(基数排序使用)
// 插入排序族
void insertSort (int *A, int n); // 直接插入排序
void biInsertSort(int *A, int n); // 折半插入排序
void shellSort(int *A, int n); // 希尔排序
// 交换排序族
void bubbleSort(int *A, int n); // 冒泡排序
void quickSort(int *A, int lo, int hi); // 快速排序
// 选择排序族
void selectSort(int *A, int n); // 选择排序
void heapSort(int *A, int n); // 堆排序
// 归并排序
void mergeSort(int *A, int lo, int hi); // 归并排序
// 非基于比较的排序
void bucketSort(int *A, int n); // 桶排序
void radixSort(int *A, int n); // 基数排序
int main() {
int n = 15, min = 0, max = 300;
int *A = randomArray(n, min, max);
printf("原数组:n");
printArray(A, n);
printf("n");
//insertSort(A, n);
//biInsertSort(A, n);
//shellSort(A, n);
//bubbleSort(A, n);
//quickSort(A, 0, n - 1);
//selectSort(A, n);
//heapSort(A, n);
//mergeSort(A, 0, n - 1);
//bucketSort(A, n);
radixSort(A, n);
printf("排序结果:n");
printArray(A, n);
printf("n");
free(A);
}
// 生成容量为 n,范围为 [min, max] 整数的随机序列
int * randomArray(int n, int min, int max) {
int *A = (int *)malloc(sizeof(int) * (max - min + 1));
for (int i = 0; i < n; i++)
A[i] = rand() % (max - min + 1);
return A;
}
// 从前至后顺序打印一个数组所有元素
void printArray(int *A, int n) {
for (int i = 0; i < n; i++)
printf("%4d ", A[i]);
printf("n");
}
// 直接插入排序:依次将当前元素插入到其前部已经有序的子序列中
// 时间复杂度:O(n^2),空间复杂度:O(1)
// 特点:稳定排序,不保证每趟有一个元素归位,排序过程中前部局部有序
void insertSort (int *A, int n) {
printf("直接插入排序:n");
int tmp, i, j;
for (i = 1; i < n; i++) {
tmp = A[i];
for (j = i - 1; ~j && tmp < A[j]; j--)
A[j + 1] = A[j];
A[j + 1] = tmp;
if (printProcess) // 打印每一趟排序过程的语句
printArray(A, n);
}
}
// 二分查找插入点:从数组 A 的 [lo, hi] 非降序区间查找元素 x 应当插入的位置
// 返回从后向前最后一个大于 x 的元素下标
int biInsert(int *A, int lo, int hi, int x) {
if (A[hi] <= x) return hi + 1; // 若闭区间内不存在比 x 大的元素,则应当插入到区间之后
int mi;
while (lo < hi) {
mi = (lo + hi) >> 1;
if (A[mi] > x) hi = mi;
else lo = mi + 1;
}
return hi;
}
// 折半插入排序:在直接插入排序的基础上,将寻找插入点的过程优化为二分查找
// 时间复杂度:O(n^2),空间复杂度O(1)
// 特点:稳定排序,具有直接插入排序的其他特点,只优化了比较次数
void biInsertSort(int *A, int n) {
printf("折半插入排序:n");
int tmp, i, j, position;
for (i = 1; i < n; i++) {
tmp = A[i];
position = biInsert(A, 0, i - 1, tmp);
for (j = i - 1; j >= position; j--)
A[j + 1] = A[j];
A[position] = tmp;
if (printProcess) // 打印每一趟排序过程的语句
printArray(A, n);
}
}
// 希尔排序:又称缩小增量排序,每次先选取一定间隔的子序列进行直接插入排序,
// 再多趟缩小间隔,直至间隔为 1
// 时间复杂度:最坏情况O(n^2),但 n 在一定范围时可以降至约 O(n^1.3),空间复杂度 O(1)
// 特点:不稳定排序,是直接插入排序的改进,减少了数据移动的次数
void shellSort(int *A, int n) {
printf("希尔排序:n");
int step = n >> 1, tmp, i, j, k;
while (step) {
for (k = 0; k < step; k++) {
for (i = step + k; i < n; i += step) {
tmp = A[i];
for (j = i - step; j >= 0 && tmp < A[j]; j -= step)
A[j + step] = A[j];
A[j + step] = tmp;
}
if (printProcess) // 打印每一趟排序过程的语句
printArray(A, n);
}
step >>= 1; // 间隔的选取:初始为 n / 2,其后每次减半
}
}
// 交换函数:交换数组中 i 位置和 j 位置两元素的值
void swap(int *A, int i, int j) {
if (i == j) return;
int tmp = A[i];
A[i] = A[j];
A[j] = tmp;
}
// 冒泡排序:每趟扫描相邻两数,若逆序则交换之,至多 (n-1) 趟完成排序
// 时间复杂度:O(n^2),空间复杂度:O(1)
// 特点:简单易于实现,排序过程中的有序子序列全局有序
void bubbleSort(int *A, int n) {
printf("冒泡排序:n");
int i, j, flag;
for (i = 0; i < n; i++) {
flag = 1;
for (j = n - 1; j > i; j--)
if (A[j] < A[j - 1]) {
flag = 0;
swap(A, j, j - 1);
}
if (printProcess) // 打印每一趟排序过程的语句
printArray(A, n);
if (flag) break;
}
}
// 快速排序划分时选取轴点
int getPivot(int *A, int lo, int hi) {
int s = (hi - lo + 1 > 2048) ? 1 : 2;
switch (s) {
case 0: // 简单策略:以数组区间最左侧的元素为轴点
return lo;
case 1: // 随机策略:在数组区间中随机选择一个元素作为轴点
return lo + (rand() % (hi - lo + 1));
case 2: // 三数取中策略:在数组两端和中间取三个数,选择其中位数作为轴点
if (hi - lo == 1) return lo;
int mi = (lo + hi) >> 1;
if ((lo <= mi && mi <= hi) || (hi <= mi && mi <= lo)) return mi;
if ((mi <= lo && lo <= hi) || (hi <= lo && lo <= mi)) return lo;
return hi;
}
}
// 快速排序划分函数:选取一个轴点后,调整数组使得轴点左侧元素都小于等于轴点元素,
// 轴点右侧元素都大于等于轴点元素,返回轴点的位置
int partition(int *A, int lo, int hi) {
int pivotPosition = getPivot(A, lo, hi);
int pivot = A[pivotPosition];
while (lo < hi) {
while (pivotPosition < hi && A[hi] >= pivot) hi--;
A[pivotPosition] = A[hi];
pivotPosition = hi;
while (lo < pivotPosition && A[lo] <= pivot) lo++;
A[pivotPosition] = A[lo];
pivotPosition = lo;
}
A[pivotPosition] = pivot;
return pivotPosition;
}
// 快速排序:分治策略,每次确定一个元素的正确位置,再递归地排两侧子序列
// 时间复杂度:O(nlogn),最坏情况下 O(n^2),但其概率极低;空间复杂度 O(logn),最坏 O(n^2)
// 特点:不稳定排序,每趟必有一个元素归位,平均时间性能最佳的排序算法
void quickSort(int *A, int lo, int hi) {
if (lo >= hi) return;
static int n = hi - lo + 1;
if (hi - lo + 1 == n) printf("快速排序:n");
int pivotPosition = partition(A, lo, hi);
if (printProcess) printArray(A, n);
quickSort(A, lo, pivotPosition - 1);
quickSort(A, pivotPosition + 1, hi);
}
// 选择排序:数组分为无序前部和有序后部,每次选择前部最大元素与前部最后一个元素交换
// 时间复杂度 O(n^2),空间复杂度 O(1)
// 特点:不稳定排序,排序过程中已排好元素全局有序,最好情况下也需要 O(n^2) 时间
void selectSort(int *A, int n) {
printf("选择排序:n");
int max, maxIndex, i, len = n;
while (len) {
max = A[0];
maxIndex = 0;
for (i = 1; i < len; i++)
if (A[i] > max) max = A[maxIndex = i];
swap(A, maxIndex, --len);
if (printProcess) printArray(A, n);
}
}
// 调整大顶堆中以 A[k] 为根结点的子树,使之符合大顶堆性质
void adjustHeap(int *A, int n, int k) {
int l = (k << 1) + 1, r = (k + 1) << 1; // 计算 k 下标结点左右孩子下标
if (l >= n) return; // k 下标结点没有左子树,则返回
int maxIndex = k;
if (A[k] < A[l]) maxIndex = l;
if (r < n && A[maxIndex] < A[r]) maxIndex = r; // 寻找根结点和左右孩子三者最大的
if (maxIndex != k) { // 若最大元素不是根结点
swap(A, k, maxIndex); // 则将根结点与之交换
adjustHeap(A, n, maxIndex); // 然后递归地调整其子树
}
}
// 堆排序:在选择排序基础上,将前部未排序部分维护成一个大顶堆
// 时间复杂度:O(nlogn),空间复杂度:O(1)
// 特点:不稳定排序,不论何种情况时间性能均为 O(nlogn),适合具有偏序关系的问题
void heapSort(int *A, int n) {
int i, len = n;
printf("堆排序:n");
for (i = n >> 1 - 1; ~i; i--) adjustHeap(A, n, i);
while (len) {
swap(A, 0, --len);
adjustHeap(A, len, 0);
if (printProcess) printArray(A, n);
}
}
// 归并两个有序数列:A1 起始地址处长为 len1 的数列和 A2 起始地址处长为 len2 的数列,合并为
// 新数列,仍存放在 A1 起始地址处
void merge(int *A1, int len1, int *A2, int len2) {
int i, j, k;
int *B = (int *)malloc(sizeof(int) * (len1 + len2));
for (i = 0; i < len1; i++) B[i] = A1[i];
for (j = 0; j < len2; j++) B[len1 + j] = A2[j];
for (i = 0, j = len1, k = 0; i < len1 && j < len1 + len2; k++)
A1[k] = (B[i] <= B[j]) ? B[i++] : B[j++];
while (i < len1) A1[k++] = B[i++];
while (j < len1 + len2) A1[k++] = B[j++];
free(B);
}
// 归并排序:先递归地将左右两子序列分别排序,再将两个有序子序列归并为全局有序
// 时间复杂度:O(nlogn),空间复杂度:O(n)
// 特点:稳定排序,每一趟不一定有元素归位
void mergeSort(int *A, int lo, int hi) {
if (lo >= hi) return;
int mi = (lo + hi) >> 1;
static int n = hi - lo + 1;
if (hi - lo + 1 == n) printf("归并排序:n");
mergeSort(A, lo, mi);
mergeSort(A, mi + 1, hi);
if (printProcess) printArray(A, n);
merge(A + lo, mi - lo + 1, A + mi + 1, hi - mi);
}
// 数列中元素的最大值(时间复杂度 O(n))
int maxItem(int *A, int n) {
int max = A[0];
for (int i = 1; i < n; i++)
if (A[i] > max) max = A[i];
return max;
}
// 数列中元素的最小值(时间复杂度 O(n))
int minItem(int *A, int n) {
int min = A[0];
for (int i = 1; i < n; i++)
if (A[i] < min) min = A[i];
return min;
}
// 桶排序:当待排序内容的范围合适时,可以利用空间直接安插元素,最终收集
// 时间复杂度:O(n),空间复杂度:O(max - min)
// 特点:稳定排序,不基于比较,因此时间突破了 O(nlogn)下界,
// 但在待排序内容范围较大或未知范围时耗费空间过大
void bucketSort(int *A, int n) {
printf("桶排序:n");
int i, j, k, l;
int max = maxItem(A, n), min = minItem(A, n);
node **BF = (node **)malloc(sizeof(node *) * (max - min + 1)); // 桶数组,记录头指针
node **BT = (node **)malloc(sizeof(node *) * (max - min + 1)); // 桶数组,记录尾指针
for (i = 0; i < max - min + 1; i++) BF[i] = BT[i] = NULL;
for (i = 0; i < n; i++) { // 将元素依次置于桶中
node *nd = (node *)malloc(sizeof(node));
nd->num = A[i];
nd->next = NULL;
l = A[i] - min;
if (BF[l] == NULL) BF[l] = BT[l] = nd;
else BT[l] = BT[l]->next = nd;
}
for (i = 0, k = 0; i < max - min + 1; i++) // 收集桶中元素
for (node *p = BF[i]; p != NULL;) {
A[k++] = p->num;
node *t = p->next;
free(p);
p = t;
}
}
// 回收元素,将每个槽位的链依次连成一条链
node * collect(node **BF, node **BT, int n) {
node *head, *tail; int i;
for (i = 0; BF[i] == NULL; i++);
head = BF[i]; tail = BT[i];
for ( ; i < n; i++)
if (BF[i] != NULL) {
tail->next = BF[i];
tail = BT[i];
}
for (i = 0; i < n; i++) BF[i] = BT[i] = NULL;
return head;
}
// 基数排序:基于数位的多趟桶排序,其基数可以扩展至更多数据类型实现字典序
// 时间复杂度:O(n),空间复杂度:O(n)
// 特点:稳定排序,改进了桶排序空间需求过大的弊端,但仍需要额外的结点空间解决桶地址冲突,
// 同时多趟分发和收集增加了线性时间复杂度的常系数。
void radixSort(int *A, int n) {
printf("基数排序:n");
int r, i, j, l, base, round = 0;
for (int max = maxItem(A, n); max; max /= 10) round++; // 基于十进制数位,计算需要多少趟桶排序
node *BF[10], *BT[10], *head, *p;
for (i = 0; i < 10; i++) BF[i] = BT[i] = NULL;
for (i = 0; i < n; i++) { // 第一趟桶排序,为待排序元素建立结点并按最低位归入桶
l = A[i] % 10;
node *nd = (node *)malloc(sizeof(node)); nd->num = A[i]; nd->next = NULL;
if (BF[l] == NULL) BF[l] = BT[l] = nd;
else BT[l] = BT[l]->next = nd;
}
head = collect(BF, BT, 10); // 收集桶中元素称为一个链
if (printProcess) {
for (p = head; p != NULL; p = p->next)
printf("%4d ", p->num);
printf("n");
}
for (r = 1; r < round; r++) {
for (i = 0, base = 1; i < r; i++) base *= 10; // 计算本趟选取的数位
for (p = head; p != NULL; ) { // 按照当前趟数对应到数位归入桶
l = (p->num / base) % 10;
node *t = p->next;
p->next = NULL;
if (BF[l] == NULL) BF[l] = BT[l] = p;
else BT[l] = BT[l]->next = p;
p = t;
}
head = collect(BF, BT, 10); // 收集桶中元素
if (printProcess) {
for (p = head; p != NULL; p = p->next)
printf("%4d ", p->num);
printf("n");
}
}
for (i = 0, p = head; p != NULL && i < n; i++) { // 将链中元素还原到数组中,同时释放结点
A[i] = p->num;
node *t = p->next; free(p); p = t;
}
}
最后
以上就是热情钻石为你收集整理的【数据结构】十大经典排序算法的C语言实现的全部内容,希望文章能够帮你解决【数据结构】十大经典排序算法的C语言实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复