数据结构010 - 排序(直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序)

1. 排序

1.1 排序的概念

排序: 所谓排序,就是将一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录。若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j]之前,则称这种排序算法是稳定的,否则称为不稳定的。

稳定性的意义: 稳定性在根据多种属性进行排序时会有巨大的意义。比如先按照学号对学生进行排序,再按照成绩对学生进行排序,此时学号和成绩成为了两种决定因素。如果在按照成绩进行排序时,所使用的算法是不具有稳定性的,那么在对成绩排序后,之前根据学号进行的排序就没有意义了,此时就会出现相同成绩,但是学号靠后的排在前面的情况。反之,如果选择的排序具有稳定性,那么成绩相同,学号靠前的应该排在前面。

内部排序: 数据元素全部放在内存中的排序。

外部排序: 数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。一般数据是存储在磁盘中的。

排序类型数据存储访问速度支持的访问形式
内部排序数据在内存中(数组)下标随机访问
外部排序数据在磁盘中,数据量大串行访问

1.2 常见的排序算法

排序算法

2. 常见排序算法的实现

代码的实现将放在以下文件中,分别是 Sort.h(用于声明)、Sort.c(用于定义)、Test.c(用于测试)

Sort.h 文件

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <time.h>

// 打印数组
void PrintArrey(int* a, int n);

// 交换
void Swap(int* p1, int* p2);


// 直接插入排序
void InsertSort(int* a, int n);

// 希尔排序
void ShellSort(int* a, int n);

// 选择排序
void SelectSort(int* a, int n);

// 堆排序
void AdjustDown(int* a, int n, int root);
void HeapSort(int* a, int n);

// 冒泡排序
void BubbleSort(int* a, int n);

// 快速排序
int PartSort1(int* a, int begin, int end);
void QuickSort(int* a, int left, int right);

// 归并排序(递归实现)
void _MergeSort(int* a, int left, int right, int* tmp);
void MergeSort(int* a, int n);

// 计数排序
void CountSort(int* a, int n);

2.1 辅助函数

2.1.1 打印数组

Sort.c 文件

#include "Sort.h"

// 打印数组
void PrintArrey(int* a, int n){
	for (int i = 0; i < n; ++i){
		printf("%d ", a[i]);
	}
	printf("\n");
}

2.1.2 交换

Sort.c 文件

#include "Sort.h"

// 交换
void Swap(int* p1, int* p2){
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

2.2 插入排序

插入排序是一种简单的插入排序法。
基本思想是: 将待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

2.2.1 直接插入排序

思路: 当插入第 i(i >= 1)个元素时,前面的 array[0],array[1],…,array[i-1] 已经排序完成。此时用 array[i] 的排序码与 array[i-1],array[i-2],… 的排序码顺序地进行比较,找到插入位置,将 array[i] 插入,原来位置上的元素顺序后移。

用 end 记录下标,x 记录 end+1 下标位置的数据,只要 a[end] > x,就将前一个值向后挪,然后 end 向下标为 0 的方向移动,移动一次比较一次,直到 end < 0。总共有 n 个数,所以需要排序 n-1 次。

思路

Sort.c 文件

#include "Sort.h"

// 插入排序
void InsertSort(int* a, int n){
	assert(a);

	for (int i = 0; i < n - 1; i++){
		// 将 x 插入 [0,end] 有序区间
		int end = i;
		int x = a[end + 1];
		while (end >= 0){
			if (a[end] > x){
				// 将前一个数向后挪动
				a[end + 1] = a[end];
				--end;
			}
			else{
				break;
			}
		}
		a[end + 1] = x;
	}
}

Test.c 文件

#include "Sort.h"

// 测试直接插入排序
void TestInsertSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	InsertSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}

int main(){
	TestInsertSort();
	
	system("pause");
	return 0;
}

运行结果:

运行结果

直接插入排序的特性总结:
(1)元素集合越接近有序,直接插入排序算法的时间效率越高。
(2)时间复杂度:O(N2)。
(3)空间复杂度:O(1)。
(4)稳定性:稳定。

2.2.2 希尔排序

希尔排序又称为 缩小增量排序,本质还是插入排序,是在直接插入排序算法的基础上进行了改进。

思路:

(1)先选定一个整数,将待排序文件分组,间隔为这个整数的数据分为一组,对每一组的数据进行排序。

(2)减小这个整数,再分组进行排序。

(3)当这个整数为 1 时,再分组进行排序,结束排序。

最后一次整数为 1 时,排序相当于是直接插入排序,前面进行多次分组排序是为了让数据愈加有序(预排序过程)。数据越有序,直接插入排序算法的时间效率越高。

Sort.c 文件

#include "Sort.h"

// 希尔排序
void ShellSort(int* a, int n){
	// 1、gap > 1 就相当于预排序,让数组接近有序
	// 2、gap == 1,最后一次相当于直接插入排序,保证有序
	int gap = n;
	while (gap > 1){
		// 保证了最后一次 gap 一定是 1,即最后是一次直接插入排序
		gap = gap / 3 + 1;
		// 为什么是 n - gap?
		// 因为最后一个 end 是 n-1-gap,end + gap 一定小于 n
		for (int i = 0; i < n - gap; i++){
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0){
				if (a[end] > tmp){
					a[end + gap] = a[end];
					end -= gap;
				}
				else{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

Test.c 文件

#include "Sort.h"

// 测试希尔排序
void TestShellSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	ShellSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}

int main(){
	TestShellSort();
	
	system("pause");
	return 0;
}

运行结果:

运行结果

希尔排序的特性总结:
(1)希尔排序是对直接插入排序的优化。
(2)当 gap > 1 时是预排序,目的是让数组更快接近于有序。
(3)当 gap == 1 时,相当于直接插入排序,数组已经接近有序。整体而言,可以达到优化的效果。
(4)稳定性:不稳定(相同的数据可能被分到不同的 gap 组)。

2.3 选择排序

基本思想: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

2.3.1 选择排序

思路:

(1)在 n 个元素的集合中选择最大的和最小的元素。
(2)若最小的元素不是这组元素中的第一个元素,若最大的元素不是这组元素中的最后一个元素,则将它们与这组元素中的第一个元素或最后一个元素交换。
(3)在剩余的元素中,重复上述步骤,直到集合剩余一个元素为止。

Sort.c 文件

#include "Sort.h"

// 选择排序
void SelectSort(int* a, int n){
	assert(a);
	
	int begin = 0;
	int end = n - 1;

	while (begin < end){
		// 在 [begin, end] 之间找出最小数和最大数的下标
		int mini = begin;
		int maxi = begin;
		
		for (int i = begin; i <= end; i++){
			if (a[i] < a[mini])
				mini = i;

			if (a[i] > a[maxi])
				maxi = i;
		}
		
		Swap(&a[begin], &a[mini]);
		// begin == maxi时,最大被换走了,修正一下 maxi 的位置
		if (begin == maxi)
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}

Test.c 文件

#include "Sort.h"

// 测试选择排序
void TestSelectSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	SelectSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}

int main(){
	TestSelectSort();

	system("pause");
	return 0;
}

运行结果:

运行结果

选择排序的特性总结:

(1)选择排序很好理解,但是效率不是很好,实际中很少使用。
(2)时间复杂度:O(N2)。
(3)空间复杂度:O(1)。
(4)稳定性:不稳定。

2.3.2 堆排序

思路: 堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,是选择排序的一种。通过堆来进行选择数据。需要注意的是 排升序要建大堆 ,排降序建小堆

(1)在原数组中通过向下调整建堆。

(2)交换堆顶元素和最后一个叶子节点的数据。

(3)对除了最后一个数据外的其他数据向下调整。

(4)重复以上步骤。

Sort.c 文件

#include "Sort.h"

// 堆排序
// 向下调整建堆
void AdjustDown(int* a, int n, int root){
	int child = root * 2 + 1;
	while (child < n){
		if (child + 1 < n && a[child + 1] > a[child]){
			child++;
		}
		if (a[child] > a[root]){
			Swap(&a[child], &a[root]);
			root = child;
			child = root * 2 + 1;
		}
		else{
			break;
		}
	}
}

// 时间复杂度 O(N*logN),空间复杂度 O(1)
void HeapSort(int* a, int n){
	// 升序建大堆,降序建小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--){
		AdjustDown(a, n, i);
	}
	// 最后一个数据的下标
	// 排序
	int end = n - 1;
	while (end > 0){
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

Test.c 文件

#include "Sort.h"

// 测试堆排序
void TestHeapSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	HeapSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


int main(){
	TestHeapSort();

	system("pause");
	return 0;
}

运行结果:

运行结果

堆排序的特性总结:
(1)堆排序使用堆来选数,效率高了许多。
(2)时间复杂度:O(N ∗ logN)。
(3)空间复杂度:O(1)。
(4)稳定性:不稳定。

2.4 交换排序

基本思想: 所谓交换,即根据序列中两个记录键值的比较结果,来对换这两个记录在序列中的位置。交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

2.4.1 冒泡排序

思路:

(1)相邻两个数排序,小的排在左边,大的排在右边。

(2)从左向右依次两两比较,一趟过后就将最大值放在了最后一位。

(3)对最大值之前的数据重复上述过程,实现整体排序。

Sort.c 文件

#include "Sort.h"

// 冒泡排序
void BubbleSort(int* a, int n){
	int end = n;
	while (end > 0){
		int exchange = 0;
		for (int i = 1; i < end; i++){
			if (a[i - 1] > a[i]){
				exchange = 1;
				Swap(&a[i - 1], &a[i]);
			}
		}
		--end;
        // 如果一趟冒泡的过程中没有发生交换,则说明已经有序,不需要再进行排序
		if (exchange == 0){
			break;
		}
	}
}

Test.c 文件

#include "Sort.h"

// 测试冒泡排序
void TestBubbleSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	BubbleSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}

int main(){
	TestBubbleSort();

	system("pause");
	return 0;
}

运行结果:

运行结果

冒泡排序的特性总结:
(1)冒泡排序是一种非常容易理解的排序。
(2)时间复杂度:O(N2)。
(3)空间复杂度:O(1)。
(4)稳定性:稳定。

2.4.2 快速排序

思路(左右指针法): 任取待排序元素序列中的某元素作为基准值,按照该元素将序列分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值。然后在左右子序列中重复该过程,直到所有元素都排列在相应位置上为止。

Sort.c 文件

#include "Sort.h"

int PartSort1(int* a, int begin, int end){
	int keyi = begin;
	while (begin < end){
		// 右边先走,找小
		while (begin < end && a[keyi] <= a[end]){
			end--;
		}
		// 左边再走,找大
		while (begin < end && a[keyi] >= a[begin]){
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}
	// 交换(右边先走,能停在比 key 小的地方)
	Swap(&a[keyi], &a[begin]);

	return begin;
}

// 快速排序
void QuickSort(int* a, int left, int right){
	// 当出现错位或者二者相等时就停止
	if (left >= right)
		return;
	
	int keyi = PartSort1(a, left, right);

	// 区间划分[begin , keyi-1] keyi [keyi+1 , end]
	QuickSort(a, left, keyi - 1);  // 左子区间
	QuickSort(a, keyi + 1, right); // 右子区间
}

Test.c 文件

#include "Sort.h"

// 测试快速排序
void TestQuickSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	QuickSort(arr, 0, sizeof(arr) / sizeof(int) - 1);
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}

int main(){
	TestQuickSort();

	system("pause");
	return 0;
}

运行结果:

运行结果

快速排序的特性总结:
(1)快速排序整体的综合性能和使用场景比较好,所以才称之为快速排序。
(2)时间复杂度:O(N*logN)。
(3)空间复杂度:O(logN)。
(4)稳定性:不稳定。

2.5 归并排序

2.5.1 归并排序

思路:

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

(1)将已有序的子序列合并,得到完全有序的序列。

(2)即先使每个子序列有序,再使子序列段之间有序。

(3)若将两个有序表合并成一个有序表,称为二路归并。

归并排序核心步骤:

运行结果

Sort.c 文件

#include "Sort.h"

void _MergeSort(int* a, int left, int right, int* tmp){
	if (left >= right){
		return;
	}
	int mid = (left + right) / 2;
	// [left, mid] 和 [mid+1,right] 有序,则可以合并;无序,用子问题来解决
	// 区间分割
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	
	// 归并 [left, mid] 和[mid + 1, right] 有序
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;

	// 确定归并时存放数据的起始位置
	int index = begin1;

	// 进行合并
	while (begin1 <= end1 && begin2 <= end2){
		if (a[begin1] < a[begin2]){
			tmp[index++] = a[begin1++];
		}
		else{
			tmp[index++] = a[begin2++];
		}
	}
	
	// 没结束的区间直接归并到临时数组中
	while (begin1 <= end1){
		tmp[index++] = a[begin1++];
	}

	while (begin2 <= end2){
		tmp[index++] = a[begin2++];
	}

	// 将 tmp 中归并好的数据,再拷贝回原数组中
	for (int j = left; j <= right; ++j){
		a[j] = tmp[j];
	}
}

// 归并排序(递归实现)
void MergeSort(int* a, int n){
	assert(a);

	// 要用到临时数组
	int* tmp = (int*)malloc(sizeof(int)*n);
	assert(tmp);
	
	// 调用子函数进行区间的分割,合并
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

Test.c 文件

#include "Sort.h"

// 测试归并排序
void TestMergeSort(){
	int arr[] = { 10, 6, 7, 1, 9, 4, 2, 5 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	MergeSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


int main(){
	TestMergeSort();

	system("pause");
	return 0;
}

运行结果:

运行结果

归并排序的特性总结:
(1)归并排序的缺点是需要 O(N) 的空间复杂度,归并排序擅长解决在磁盘中的外排序问题。
(2)时间复杂度:O(N*logN)。
(3)空间复杂度:O(N)。
(4)稳定性:稳定。

2.6 计数排序

2.6.1 计数排序

思路: 计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
(1)统计相同元素出现次数。
(2)根据统计的结果将序列回收到原来的序列中。

Sort.c 文件

#include "Sort.h"

// 计数排序
void CountSort(int* a, int n){
	int max = a[0];
	int min = a[0];

	// 找出最大值和最小值
	for (int i = 1; i < n; i++){
		if (a[i] > max){
			max = a[i];
		}
		if (a[i] < min){
			min = a[i];
		}
	}
	
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int)* range);
	// 将 count 数组元素全部置为 0
	memset(count, 0, sizeof(int)* range);
	if (count == NULL){
		printf("malloc fail\n");
		exit(-1);
	}

	// 统计次数
	for (int i = 0; i < n; ++i){
		count[a[i] - min]++;
	}
	// 根据次数,进行排序
	int j = 0;
	for (int i = 0; i < range; ++i){
		while (count[i]--){
			a[j++] = i + min;
		}
	}
}

Test.c 文件

#include "Sort.h"

// 测试计数排序
void TestCountSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	CountSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}

int main(){
	TestCountSort();

	system("pause");
	return 0;
}

运行结果:

运行结果

计数排序的特性总结:
(1)计数排序适用于范围集中的数据,效率很高。但是适用范围及场景有限,支持负数但是不支持浮点数、字符串等。
(2)时间复杂度:O(MAX(N, 范围))。
(3)空间复杂度:O(范围)。
(4)稳定性:稳定。

3. 排序算法总结

3.1 时间复杂度及空间复杂度

复杂度

3.2 排序算法的稳定性

稳定性: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录。若经过排序,这些记录的相对次序保持不变,即在原序列中,r[ i ] = r[ j ],且 r[ i ] 在 r[ j ] 之前;而在排序后的序列中,r[ i ] 仍在 r[ j ] 之前,则称这种排序算法是稳定的,否则称为不稳定的。

稳定性

1、稳定的排序:
冒泡排序、直接插入排序、归并排序。

稳定的排序

2、不稳定的排序

选择排序、希尔排序、堆排序、快速排序、计数排序。

不稳定的排序

4. 源代码

Sort.h 文件

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <time.h>

// 打印数组
void PrintArrey(int* a, int n);

// 交换
void Swap(int* p1, int* p2);


// 直接插入排序
void InsertSort(int* a, int n);

// 希尔排序
void ShellSort(int* a, int n);

// 选择排序
void SelectSort(int* a, int n);

// 堆排序
void AdjustDown(int* a, int n, int root);
void HeapSort(int* a, int n);

// 冒泡排序
void BubbleSort(int* a, int n);

// 快速排序
int PartSort1(int* a, int begin, int end);
void QuickSort(int* a, int left, int right);

// 归并排序(递归实现)
void _MergeSort(int* a, int left, int right, int* tmp);
void MergeSort(int* a, int n);

// 计数排序
void CountSort(int* a, int n);

Sort.c 文件

#include "Sort.h"


// 打印数组
void PrintArrey(int* a, int n){
	for (int i = 0; i < n; ++i){
		printf("%d ", a[i]);
	}
	printf("\n");
}

// 交换
void Swap(int* p1, int* p2){
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}


// 直接插入排序
void InsertSort(int* a, int n){
	assert(a);

	for (int i = 0; i < n - 1; i++){
		// 将 x 插入 [0,end] 有序区间
		int end = i;
		int x = a[end + 1];
		while (end >= 0){
			if (a[end] > x){
				// 将前一个数向后挪动
				a[end + 1] = a[end];
				--end;
			}
			else{
				break;
			}
		}
		a[end + 1] = x;
	}
}


// 希尔排序
void ShellSort(int* a, int n){
	// 1、gap > 1 就相当于预排序,让数组接近有序
	// 2、gap == 1,最后一次相当于直接插入排序,保证有序
	int gap = n;
	while (gap > 1){
		// 保证了最后一次 gap 一定是 1,即最后是一次直接插入排序
		gap = gap / 3 + 1;
		// 为什么是 n - gap?
		// 因为最后一个 end 是 n-1-gap,end + gap 一定小于 n
		for (int i = 0; i < n - gap; i++){
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0){
				if (a[end] > tmp){
					a[end + gap] = a[end];
					end -= gap;
				}
				else{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}


// 选择排序
void SelectSort(int* a, int n){
	assert(a);
	
	int begin = 0;
	int end = n - 1;

	while (begin < end){
		// 在 [begin, end] 之间找出最小数和最大数的下标
		int mini = begin;
		int maxi = begin;
		
		for (int i = begin; i <= end; i++){
			if (a[i] < a[mini])
				mini = i;

			if (a[i] > a[maxi])
				maxi = i;
		}
		
		Swap(&a[begin], &a[mini]);
		// begin == maxi时,最大被换走了,修正一下 maxi 的位置
		if (begin == maxi)
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}


/* --------------------------------------------------- */
// 堆排序
// 向下调整建堆
void AdjustDown(int* a, int n, int root){
	int child = root * 2 + 1;
	while (child < n){
		if (child + 1 < n && a[child + 1] > a[child]){
			child++;
		}
		if (a[child] > a[root]){
			Swap(&a[child], &a[root]);
			root = child;
			child = root * 2 + 1;
		}
		else{
			break;
		}
	}
}

// 时间复杂度 O(N*logN),空间复杂度 O(1)
void HeapSort(int* a, int n){
	// 升序建大堆,降序建小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--){
		AdjustDown(a, n, i);
	}
	// 最后一个数据的下标
	// 排序
	int end = n - 1;
	while (end > 0){
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

/* --------------------------------------------------- */


// 冒泡排序
void BubbleSort(int* a, int n){
	int end = n;
	while (end > 0){
		int exchange = 0;
		for (int i = 1; i < end; i++){
			if (a[i - 1] > a[i]){
				exchange = 1;
				Swap(&a[i - 1], &a[i]);
			}
		}
		--end;
		// 如果一趟冒泡的过程中没有发生交换,则说明已经有序,不需要再进行排序
		if (exchange == 0){
			break;
		}
	}
}



/* --------------------------------------------------- */
// 快速排序

int PartSort1(int* a, int begin, int end){
	int keyi = begin;
	while (begin < end){
		// 右边先走,找小
		while (begin < end && a[keyi] <= a[end]){
			end--;
		}
		// 左边再走,找大
		while (begin < end && a[keyi] >= a[begin]){
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}
	// 交换(右边先走,能停在比 key 小的地方)
	Swap(&a[keyi], &a[begin]);

	return begin;
}

void QuickSort(int* a, int left, int right){
	// 当出现错位或者二者相等时就停止
	if (left >= right)
		return;
	
	int keyi = PartSort1(a, left, right);

	// 区间划分[begin , keyi-1] keyi [keyi+1 , end]
	QuickSort(a, left, keyi - 1);  // 左子区间
	QuickSort(a, keyi + 1, right); // 右子区间
}
/* --------------------------------------------------- */


/* --------------------------------------------------- */
// 归并排序(递归实现)

void _MergeSort(int* a, int left, int right, int* tmp){
	if (left >= right){
		return;
	}
	int mid = (left + right) / 2;
	// [left, mid] 和 [mid+1,right] 有序,则可以合并;无序,用子问题来解决
	// 区间分割
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	
	// 归并 [left, mid] 和[mid + 1, right] 有序
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;

	// 确定归并时存放数据的起始位置
	int index = begin1;

	// 进行合并
	while (begin1 <= end1 && begin2 <= end2){
		if (a[begin1] < a[begin2]){
			tmp[index++] = a[begin1++];
		}
		else{
			tmp[index++] = a[begin2++];
		}
	}
	
	// 没结束的区间直接归并到临时数组中
	while (begin1 <= end1){
		tmp[index++] = a[begin1++];
	}

	while (begin2 <= end2){
		tmp[index++] = a[begin2++];
	}

	// 将 tmp 中归并好的数据,再拷贝回原数组中
	for (int j = left; j <= right; ++j){
		a[j] = tmp[j];
	}
}

// 归并排序
void MergeSort(int* a, int n){
	assert(a);

	// 要用到临时数组
	int* tmp = (int*)malloc(sizeof(int)*n);
	assert(tmp);
	
	// 调用子函数进行区间的分割,合并
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}
/* --------------------------------------------------- */


// 计数排序
void CountSort(int* a, int n){
	int max = a[0];
	int min = a[0];

	// 找出最大值和最小值
	for (int i = 1; i < n; i++){
		if (a[i] > max){
			max = a[i];
		}
		if (a[i] < min){
			min = a[i];
		}
	}
	
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int)* range);
	// 将 count 数组元素全部置为 0
	memset(count, 0, sizeof(int)* range);
	if (count == NULL){
		printf("malloc fail\n");
		exit(-1);
	}

	// 统计次数
	for (int i = 0; i < n; ++i){
		count[a[i] - min]++;
	}
	// 根据次数,进行排序
	int j = 0;
	for (int i = 0; i < range; ++i){
		while (count[i]--){
			a[j++] = i + min;
		}
	}
}

Test.c 文件

#include "Sort.h"


// 测试直接插入排序
void TestInsertSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	InsertSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


// 测试希尔排序
void TestShellSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	ShellSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


// 测试选择排序
void TestSelectSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	SelectSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


// 测试堆排序
void TestHeapSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	HeapSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


// 测试冒泡排序
void TestBubbleSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	BubbleSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


// 测试快速排序
void TestQuickSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	QuickSort(arr, 0, sizeof(arr) / sizeof(int) - 1);
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


// 测试归并排序
void TestMergeSort(){
	int arr[] = { 10, 6, 7, 1, 9, 4, 2, 5 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	MergeSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}


// 测试计数排序
void TestCountSort(){
	int arr[] = { 2, 6, 10, 1, 9, 4, 8, 5, 3, 7 };
	PrintArrey(arr, sizeof(arr) / sizeof(int));
	CountSort(arr, sizeof(arr) / sizeof(int));
	PrintArrey(arr, sizeof(arr) / sizeof(int));
}

int main(){
	//TestInsertSort();
	//TestShellSort();
	//TestSelectSort();
	//TestHeapSort();
	//TestBubbleSort();
	//TestQuickSort();
	//TestMergeSort();
	TestCountSort();

	system("pause");
	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值