我是靠谱客的博主 热情钻石,最近开发中收集的这篇文章主要介绍【数据结构】十大经典排序算法的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;
	}
} 

最后

以上就是热情钻石为你收集整理的【数据结构】十大经典排序算法的C语言实现的全部内容,希望文章能够帮你解决【数据结构】十大经典排序算法的C语言实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部