【数据结构】十大经典排序算法的C语言实现

十大经典排序算法的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;
	}
} 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
16进制10进制.txt 32.txt asm.txt Crctable.txt C标志符命名源程序.txt erre.txt erre2.txt ff.txt for循环的.txt list.log N皇后问题回溯算法.txt ping.txt re.txt source.txt winsock2.txt ww.txt 万年历.txt 万年历的算法 .txt 乘方函数桃子猴.txt 乘法矩阵.txt 二分查找1.txt 二分查找2.txt 二叉排序树.txt 二叉树.txt 二叉树实例.txt 二进制数.txt 二进制数2.txt 余弦曲线.txt 余弦直线.txt 傻瓜递归.txt 冒泡排序.txt 冒泡法改进.txt 动态计算网络最长最短路线.txt 十五人排序.txt 单循环链表.txt 单词倒转.txt 单链表.txt 单链表1.txt 单链表2.txt 单链表倒序.txt 单链表的处理全集.txt 双链表正排序.txt 反出字符.txt 叠代整除.txt 各种排序法.txt 哈夫曼算法.txt 哈慢树.txt 四分砝码.txt 四塔1.txt 四塔2.txt 回文.txt 图.txt 圆周率.txt 多位阶乘.txt 多位阶乘2.txt 大加数.txt 大小倍约.txt 大整数.txt 字符串查找.txt 字符编辑.txt 字符编辑技术(插入和删除) .txt 完数.txt 定长串.txt 实例1.txt 实例2.txt 实例3.txt 小写数字转换成大写数字1.txt 小写数字转换成大写数字2.txt 小写数字转换成大写数字3.txt 小字库DIY-.txt 小字库DIY.txt 小孩分糖果.txt 小明买书.txt 小白鼠钻迷宫.txt 带头结点双链循环线性表.txt 平方根.txt 建树和遍历.txt 建立链表1.txt 扫描码.txt 挽救软盘.txt 换位递归.txt 排序法.txt 推箱子.txt 数字移动.txt 数据结构.txt 数据结构2.txt 数据结构3.txt 数组完全单元.txt 数组操作.txt 数组递归退出.txt 数组递归退出2.txt 文件加密.txt 文件复制.txt 文件连接.txt 无向图.txt 时间陷阱.txt 杨辉三角形.txt 栈单元加.txt 栈操作.txt 桃子猴.txt 桶排序.txt 检出错误.txt 检测鼠标.txt 汉字字模.txt 汉诺塔.txt 汉诺塔2.txt 灯塔问题.txt 猴子和桃.txt 百鸡百钱.txt 矩阵乘法动态规划.txt 矩阵转换.txt 硬币分法.txt 神经元模型.txt 穷举搜索法.txt 符号图形.txt 简单数据库.txt 简单计算器.txt 简单逆阵.txt 线性顺序存储结构.txt 线索化二叉树.txt 绘制圆.txt 编随机数.txt 网络最短路径Dijkstra算法.txt 自我复制.txt 节点.txt 苹果分法.txt 螺旋数组1.txt 螺旋数组2.txt 试题.txt 诺汉塔画图版.txt 读写文本文件.txt 货郎担分枝限界图形演示.txt 货郎担限界算法.txt 质因子.txt 输出自已.txt 迷宫.txt 迷宫问题.txt 逆波兰计算器.txt 逆矩阵.txt 逆阵.txt 递堆法.txt 递归桃猴.txt 递归车厢.txt 递推.txt 逻辑移动.txt 链串.txt 链栈.txt 链表十五人排序.txt 链表(递归).txt 链队列.txt 队列.txt 阶乘递归.txt 阿姆斯特朗数.txt 非递归.txt 顺序栈.txt 顺序表.txt 顺序队列.txt 骑士遍历1.txt 骑士遍历2.txt 骑士遍历回逆.txt 黑白.txt
C语言内部排序算法有以下几种: 1. 冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法,其基本思想是重复地遍历要排序的数列,每次比较相邻的两个元素,如果顺序错误就交换它们的位置,直到整个数列都有序为止。 2. 选择排序(Selection Sort) 选择排序是一种简单直观的排序算法,其基本思想是每次从待排序的数列中选择最小的元素,将其放在已排序数列的末尾,直到所有元素都排序完毕。 3. 插入排序(Insertion Sort) 插入排序是一种简单直观的排序算法,其基本思想是将待排序的数列分成已排序和未排序两个部分,每次从未排序部分中取出一个元素,插入到已排序部分的适当位置,直到所有元素都排序完毕。 4. 快速排序(Quick Sort) 快速排序是一种高效的排序算法,它的基本思想是通过一趟排序将待排序的数列分成两部分,其中一部分的所有元素都小于另一部分的所有元素,然后分别对这两部分再进行快速排序,直到整个数列都有序为止。 5. 归并排序(Merge Sort) 归并排序是一种稳定的排序算法,其基本思想是将待排序的数列分成若干个子序列,每个子序列都是有序的,然后将这些子序列两两合并,直到整个数列都有序为止。 6. 堆排序(Heap Sort) 堆排序是一种高效的排序算法,它的基本思想是将待排序的数列看作一棵完全二叉树,每次将最大的元素放到堆的末尾,并重新调整堆结构,直到整个数列都有序为止。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值