数据结构——排序

大家好我是小锋今天我们来学习排序,准备好我们开始发车了

排序的概念

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

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

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

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

排序在我们生活中运用非常多,我们在这举些例子,比如我们在网购时,按照价格排序,按照口碑排序,

类似这些例子在我们的生活中非常多。

那让我们接下来走近排序的世界,体会发明这些算法的前辈的智慧。

常见的排序算法

以上就是常见的排序,我们一起来一个一个学习

在此之前我们先把准备工作做好,为了可以直观的看出各个排序的速度,以及更有条理,我们还是老样子分装三个文件,rost.c用来写各个排序的实现函数,rost.h放函数的头文件,test.c我们来进行函数的测试与比较。

直接插入排序

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

具体思路如下图所示

那我们写一个函数来实现插入排序

//插入排序
//a是数组,n是数组元素个数
void Isertrost(int* a, int n) {
	for (int j = 0; j < n-1; j++) {
		int end = j;
		int fat = a[j+1];//待插数据
		while (end >= 0) {
			if (fat < a[end]) {
				a[end + 1] = a[end];
				--end;
			}
			else {
				break;
			}
			a[end + 1] = fat;
		}
	}
}

接下来我们测试插入排序

//打印数组
void rost_printf(int* a,int n) {
	for (int i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}
//测试插入排序
void Isertcsrost() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf( a,  n);//排序前
	Isertrost(a, n);
	printf("排序后\n");
	rost_printf( a,  n);//排序后
}
int main() {
	//插入排序测试
	Isertcsrost();
	return 0;
}

直接插入排序的特性总结:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定

希尔排序

希尔排序法又称缩小增量法。

希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成个gap组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后取gap/2重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。

思路如下图

希尔排序是直接插入排序的优化

gap我们通常取数据个数/2,或者/3+1;

我们来看看代码实现

//希尔排序
//多组并进
void shellrost(int* a, int n) {
	int gar = n;
	while (gar > 1) {
		gar = gar / 2;
			for (int j = 0; j < n - gar; j++) {
				int end = j;
				int fat = a[end + gar];
				while (end >= 0) {
					if (a[end] > fat) {
						a[end + gar] = a[end];
						end -= gar;
					}
					else {
						break;
					}
				}
				a[end + gar] = fat;
			}
	}
}

这里我们是将多组gar一起排序

//希尔排序
//单组进行
void shellrost(int* a, int n) {
	int gar = n;
	while (gar > 1) {
		gar = gar / 2;
		for (int i = 0; i < gar; i++) {
			for (int j = i; j < n - gar; j+=gar) {
				int end = j;
				int fat = a[end + gar];
				while (end >= 0) {
					if (a[end] > fat) {
						a[end + gar] = a[end];
						end -= gar;
					}
					else {
						break;
					}
				}
				a[end + gar] = fat;
			}
		}
	}
}

这里我们是单个的gar一组一组排,

以上两种思路本质是一样的,只是gar组在排序时多种并排和一次一组的区别,并无优劣之分

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定:

因为他们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:n^1.25到1.6*n^1.25来算。

选择排序

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

直接选择排序:

1,在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素

2,若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换

3,在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

主要思路如下图

我们通过代码来实现一下

//选择排序
void selectsort(int* a, int n) {
	for (int j = 0; j < n; j++) {
		int min = j;
		for (int i = j; i < n; i++) {
			if (a[min] > a[i]) {
				min = i;
			}
		}
		int mon = a[j];
		a[j] = a[min];
		a[min] = mon;
	}
}

我们还可以对选择排序进行优化,我们可以在原函数的基础上把最大的向最右移动

//选择排序
void selectsort(int* a, int n) {
	int mon = n-1;
	for (int j = 0; j < mon; j++) {
		int right=j;
		int left=j;
		for (int i = j; i <= mon; i++) {
			if (a[left] > a[i]) {
				left = i;
			}
			if (a[right] < a[i]) {
				right = i;
			}
		}
		saw(&a[j], &a[left]);
		if (j == right) {
			right = left;
		}
		saw(&a[mon], &a[right]);
		mon--;
	}
}

测试如下

直接选择排序的特性总结:

1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:不稳定

堆排序

在二叉树时我们就学习过堆排序了,这里我们再写一遍,回顾一下

堆排序的思路主要分成以下几步

1,建堆(升序建大堆,降序建小堆)

2,用删除堆中元素的思路将堆顶与堆的最后一个元素交换,并删除

3,再对没有删除的元素进行建堆,

4,重复该过程直到完全删除。

//替换函数
void saw(int* x, int* y) {
	int n = *x;
	*x = *y;
	*y = n;
}
//向下堆排序
void AdjustDwon(int* a, int n, int i) {
	int childe = 2 * i + 1;
	while (childe < n) {
		if (childe + 1 < n && a[childe] < a[childe + 1]) {
			childe++;
		}
		if (a[i] < a[childe]) {
			saw(&a[i], &a[childe]);
			i = childe;
			childe = 2 * i + 1;
		}
		else {
			break;
		}
	}
}
//堆排序
void HeapSort(int* a, int n) {
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
		int childe = i*2+1;
		if (a[childe] < a[childe + 1]) {
			childe++;
		}
		if (a[childe] > a[i]) {
			saw(&a[childe], &a[i]);
		}
	}
	//删除思路堆排
	int d = n;
	while (d > 0) {
		saw(&a[0], &a[d - 1]);
		d--;
		AdjustDwon(a, d, 0);
	}
}

我们测试一下

堆排序的特性总结:

1. 堆排序使用堆来选数,效率就高了很多。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(1)

4. 稳定性:不稳定

冒泡排序

冒泡排序也是老朋友了,我们看下思路直接上代码

思路如下

// 冒泡排序
void BubbleSort(int* a, int n) {
	for (int j = 0; j < n; j++) {
		for (int i = 0; i < n-j-1; i++) {
			if (a[i] > a[i + 1]) {
				saw(&a[i], &a[i + 1]);
			}
		}
	}
}

我们还可以优化一下:当我们已经遍历一次数组后如果没有数据发生交换说明,该数组已经是有序的,所以可以直接跳出循环

// 冒泡排序
void BubbleSort(int* a, int n) {
	for (int j = 0; j < n; j++) {
		bool add = true;
		for (int i = 0; i < n - j - 1; i++) {
			if (a[i] > a[i + 1]) {
				saw(&a[i], &a[i + 1]);
				add = false;
			}
		}
		if (add) {
			break;
		}
	}
}

我们用布尔类型来进行判断,如果发生了交换就为真就继续循环否则为假跳出循环。

冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:稳定

快速排序

下面我们来看看排序里的老大哥快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。

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

我们通过下面的图来了解一下,快排的基本思路

我们就用这种思路先来写一趟排序的过程

霍尔法

int partsort(int* a, int left,int right) {
	int keyi = left;
	while (left < right) {
		while (left<right && a[right] >= a[keyi]) 
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi]) 
		{
			left++;
		}
			saw(&a[left], &a[right]);
	}
	saw(&a[keyi], &a[left]);
	return left;
}

后续的排序过程都在执行这个过程,所以这里我们想到了递归来实现,

//快速排序霍尔版本
int partsort(int* a, int left,int right) {
	int keyi = left;
	while (left < right) {
		while (left<right && a[right] >= a[keyi]) 
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi]) 
		{
			left++;
		}
			saw(&a[left], &a[right]);
	}
	saw(&a[keyi], &a[left]);
	return left;
}
void Quicksort(int* a, int bgin, int end) {
	if (bgin >= end) {
		return;
	}
	int key=partsort(a, bgin, end);
	Quicksort(a, bgin, key-1);
	Quicksort(a, key+1, end);
}

我们测试一下

挖坑法

我们来看看挖坑法的基本思路

//快速排序挖坑法
int partsort(int* a, int left, int right) {
	int key = a[left];
	int hole = left;
	while (left < right) {
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}
void Quicksort(int* a, int bgin, int end) {
	if (bgin >= end) {
		return;
	}
	int key = partsort(a, bgin, end);
	Quicksort(a, bgin, key - 1);
	Quicksort(a, key + 1, end);
}

测试一下

前后指针法

我们还是看看图解来了解基本思路

主要思路:

两指针cur与prev在遇到比key小的元素时就都++当遇到大于key的元素时cur++,当遇到比可以小的元素且cur!=prev时,就交换,按照这个思路一直走下去直到cur越界就停止。

我们代码实现一下

//快速排序前后下标法
int partsort(int* a, int left, int right) {
	int key = left;
	int prev = left;
	int cur = prev+1;
	while (cur<=right) {
		if (a[cur] < a[key] && ++prev!=cur) {
			saw(&a[prev], &a[cur]);
		}
		cur++;
	}
	saw(&a[key],&a[prev]);
	return prev;
}
void Quicksort(int* a, int bgin, int end) {
	if (bgin >= end) {
		return;
	}
	int key = partsort(a, bgin, end);
	Quicksort(a, bgin, key - 1);
	Quicksort(a, key + 1, end);
}

我们还是测试一下

我们思考一下快速排序在什么情况下速度最快?

每次快速排序的key在整个数组的中间位置时是最快的。

当数组有序时我们的快排是最慢的

所以我们为了防止这种情况,我们在取key时,可以采用三值取中法,也就是说取出数组中的第一个,中间,和最后一个元素取三个元素的中间值,

我们可以实现一下

int threesort(int* a, int left, int right) {
	int min = (left + right) / 2;
	if (a[min] > a[left]) 
	{
		if (a[right] > a[min]) 
		{
			return min;
		}
		else if (a[right] < a[left]) 
		{
			return left;
		}
		else 
		{
			return right;
		}
		}
	else 
	{
			if (a[right] > a[min]) 
			{
				return min;
			}
			else if (a[right] > a[left]) 
			{
				return left;
			}
			else 
			{
				return right;
			}
			}
     }

使用时我们直接调用再交换就行了,这样就不会改变key的位置

接下来我们来学习快速排序的非递归实现

快速排序非递归实现

我们思考一下,快排的递归主要是通过下标来控制排序的区间,那我们可不可以把每次排序的区间储存起来,等到要使用时再拿出来用,所以我们这里可以用栈来实现

还是老规矩,我们先要建一个栈,先造个轮子

为了方便拷贝,我们再创建两个文件,stack.c    stack.h

栈的创建与实现已经学习过了,过程我们就不赘述了,大家不知道的可以去看看我们的往期数据结构的章节里有,我们直接上代码

stack.h

#pragma once


typedef struct Stack Stack;
struct Stack {
	int size;
	int capccity;
	int* head;
};

//初始化
void stackinit(Stack* pead);
//栈销毁
void stackDestroy(Stack* pead);
//压栈
void stackpush(Stack* pead,int n);
//出栈
void stackpop(Stack* pead);
//判断栈为空
bool stackEmpty(Stack* pead);
//获取栈顶元素
int stacktop(Stack* pead);
//获取栈中的有效元素个数
int stacksize(Stack* pead);

库函数的头文件的声明我就放在rost.h中了

stack.c

//初始化
void stackinit(Stack* pead) {
	assert(pead);
	pead->capccity = 0;
	pead->head = NULL;
	pead->size = 0;
}
//栈销毁
void stackDestroy(Stack* pead) {
	assert(pead);
	free(pead->head);
	pead->head = NULL;
	pead->capccity = 0;
	pead->size = 0;
}
//压栈
void stackpush(Stack* pead, int n) {
	assert(pead);
	if (pead->size == pead->capccity) {
		int cur = pead->head == NULL ? 4 : pead->capccity * 2;
		int* ps = (int*)realloc(pead->head, sizeof(int) * n);
		if (ps == NULL) {
			printf("%s", strerror(errno));
			return;
		}
		pead->capccity = n;
		pead->head = ps;
	}
	pead->head[pead->size] = n;
	pead->size++;
}

//判断栈为空
bool stackEmpty(Stack* pead) {
	assert(pead);
	return pead->size == 0;
}
//出栈
void stackpop(Stack* pead) {
	assert(pead);
	assert(!stackEmpty(pead));
	pead->size--;
}
//获取栈顶元素
int stacktop(Stack* pead) {
	assert(pead);
	assert(!stackEmpty(pead));
	return pead->head[pead->size - 1];
}
//获取栈中的有效元素个数
int stacksize(Stack* pead) {
	assert(pead);
	return pead->size;
}

栈的实现代码如上大家可以直接拿来用

那么我们来看看快排的非递归实现

//快排的非递归实现
void Quicksort(int* a, int bgin, int end) {
	Stack pead;
	stackinit(&pead);
	stackpush(&pead, end);
	stackpush(&pead, bgin);
	while (!stackEmpty(&pead)) {
		int left = stacktop(&pead);
		stackpop(&pead);
		int right = stacktop(&pead);
		stackpop(&pead);
		int key = partsort(a, left, right);
		if (right > key + 1) {
			stackpush(&pead, right);
			stackpush(&pead, key + 1);
		}
		if (left < key - 1) {
			stackpush(&pead, key - 1);
			stackpush(&pead, left);
		}
	}
	stackDestroy(&pead);

}

我们可以测试一下

快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)

4. 稳定性:不稳定

接下来我们来学习归并排序

归并排序

基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序核心步骤:

我们可以来看看思路图

对于归并排序我们的有递归与非递归两种写法

递归的过程类似于二叉树的后序。

代码实现如下

//归并排序递归实现
void Zmergesort(int* a, int left, int right, int* tmp) {
	if (left >= right) {
		return;
	}
	int mid = (left + right) / 2;
	Zmergesort(a,left, mid,tmp);
	Zmergesort(a,mid+1, right,tmp);
	int bgin1 = left, end1 = mid;
	int bgin2 = mid + 1, end2 = right;
	int i = left;
	while (bgin1 <= end1 && bgin2 <= end2) {
		if (a[bgin1] < a[bgin2]) {
			tmp[i++] = a[bgin1++];
		}
		else {
			tmp[i++] = a[bgin2++];
		}
	}
	while (bgin1 <= end1) {
		tmp[i++] = a[bgin1++];
	}
	while (bgin2 <= end2) {
		tmp[i++] = a[bgin2++];
	}
	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}

void mergesort(int* a, int n) {
	//开辟一块空间用于拷贝
	int* tmp = (int*)malloc(sizeof(int) * n);
	//因为要进行递归所以创建一个子函数来实现
	Zmergesort(a, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
}

我们可以测试一下

归并排序的非递归实现

我们的思路是用一个gap来控制区间的大小,然后再用一个循环来实现递归的过程,主要要注意的是在循环时最后的区间问题

主要有三种情况

1, end1>=n

2,egin2>=n

3,end2>=n

对于前两种情况我们可以直接跳出循环,但要注意拷贝时要排序一组拷贝一组

至于第三种情况我们要进行修正end2,end2=n-1

其他思路排序与递归的过程类似

我们来看代码

//归并排序非递归
void mergesort1(int* a, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n) {
		int j = 0;
		for (int i = 0; i < n; i += gap * 2) {
			int egin1 = i, end1 = i + gap - 1;
			int egin2 = i + gap, end2 = i + gap * 2 - 1;
			if (egin2 > n-1) {
				break;
			}
			if (end2 > n - 1) {
				end2 = n - 1;
			}
			while (egin1 <= end1 && egin2 <= end2) {
				if (a[egin1] < a[egin2]) {
					tmp[j++] = a[egin1++];
				}
				else {
					tmp[j++] = a[egin2++];
				}
			}
			while (egin1 <= end1) {
				tmp[j++] = a[egin1++];
			}
			while (egin2 <= end2) {
				tmp[j++] = a[egin2++];
			}
				memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
		free(tmp);
	tmp = NULL;
}

我们测试一下

对于非递归的区间问题,我们还有一种方法,我们可以针对所有的情况进行修正

当end1>=n时

我们可以将end=n-1;让egin2到end2这个区间不存在即egin2>end2;

下面的思路都是相同的

当egin2>=n时

我们令egin2>end2;

当end2>=n时

我们令end2=n-1

这样操作后我们就可以直接拷贝数组不用归并一组拷贝一组

代码如下

//归并排序非递归区间处理的第二个方法
void mergesort1(int* a, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n) {
		int j = 0;
		for (int i = 0; i < n; i += gap * 2) {
			int egin1 = i, end1 = i + gap - 1;
			int egin2 = i + gap, end2 = i + gap * 2 - 1;
			if (end1 >= n) {
				end1 = n - 1;
				egin2 = n;
				end2 = n - 1;
			}
			else if (egin2 >= n) {
				egin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n) {
				end2 = n - 1;
			}
			while (egin1 <= end1 && egin2 <= end2) {
				if (a[egin1] < a[egin2]) {
					tmp[j++] = a[egin1++];
				}
				else {
					tmp[j++] = a[egin2++];
				}
			}
			while (egin1 <= end1) {
				tmp[j++] = a[egin1++];
			}
			while (egin2 <= end2) {
				tmp[j++] = a[egin2++];
			}
		}
		memcpy(a, tmp, sizeof(int) *n);

		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

测试一下肯定是可行的

归并排序的优化

这里主要是针对递归版本的优化,思路如下,如果我们要排序很多的数据的时候,我们可以在区间数据为10个时停止使用递归来排序,我们可以使用其他更简单的排序来排,这种方法叫小区间优化

实现思路如下

//归并排序递归实现
//小区间优化
void Zmergesort(int* a, int left, int right, int* tmp) {
	if (left >= right) {
		return;
	}
	//小区间优化
	if ((right - left + 1) <= 10) {
		//插入排序
		Isertrost(a, right - left + 1);
		return;
	}

	int mid = (left + right) / 2;
	Zmergesort(a,left, mid,tmp);
	Zmergesort(a,mid+1, right,tmp);
	int bgin1 = left, end1 = mid;
	int bgin2 = mid + 1, end2 = right;
	int i = left;
	while (bgin1 <= end1 && bgin2 <= end2) {
		if (a[bgin1] < a[bgin2]) {
			tmp[i++] = a[bgin1++];
		}
		else {
			tmp[i++] = a[bgin2++];
		}
	}
	while (bgin1 <= end1) {
		tmp[i++] = a[bgin1++];
	}
	while (bgin2 <= end2) {
		tmp[i++] = a[bgin2++];
	}
	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}

void mergesort(int* a, int n) {
	//开辟一块空间用于拷贝
	int* tmp = (int*)malloc(sizeof(int) * n);
	//因为要进行递归所以创建一个子函数来实现
	Zmergesort(a, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
}

归并排序的特性总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(N)

4. 稳定性:稳定

快排的优化

我们前面讲了针对有序的数据可以采用三数取中来进行优化,但是如果数据全是相同的数我们又该怎么排序呢,这种虽然采用老方法也可以排序出来但是它的时间复杂度是N^2,快排秒变冒泡这怎么可以容忍,所以针对这种情况我们可以采用三路划分来解决

思路是选出key小于key的放左边,等于key的放中间,大于key放右边这样就完美解决了这个问题

要实现三路划分的函数我们思路如下

我们看看代码

//三值取中
int threesort(int* a, int left, int right) {
	int min = (left + right) / 2;
	if (a[min] > a[left]) 
	{
		if (a[right] > a[min]) 
		{
			return min;
		}
		else if (a[right] < a[left]) 
		{
			return left;
		}
		else 
		{
			return right;
		}
		}
	else 
	{
		//a[min]<a[left]
			if (a[right] < a[min]) 
			{
				return min;
			}
			else if (a[right] > a[left]) 
			{
				return left;
			}
			else 
			{
				return right;
			}
			}
     }


//快速排序三路划分
void Quicksort1(int* a, int bgin, int end) {
	if (bgin >= end) {
		return;
	}
	int left = bgin;
	int right = end;
	int cur = left+1;
	int mid = threesort(a, bgin, end);
	saw(&a[left], &a[mid]);
	int keyi = a[left];
	while (cur <= right) {
		if (a[cur] > keyi) {
			saw(&a[cur], &a[right]);
			right--;
		}
		else if (a[cur] == keyi) {
			cur++;
		}
		else {
			saw(&a[cur], &a[left]);
			cur++;
			left++;
		}
	}
	Quicksort1(a, bgin, left-1);
	Quicksort1(a, right+1, end);
}

我们可以测试一下

外排序思路

对于排序我们有两种排序,内排序和外排序

内排序是在内存中进行排序,而外排序是对磁盘空间的数据进行排序

那么大家来看看这个问题?

假如我们有一百亿个整数要排序,我们应该怎么排序?

首先一百亿个数据大约要占40G的空间所以肯定是无法存放在内存中的,所以我们只能用文件来存储。

我们先思考我们用什么方法来进行排序?

这里我们用归并排序来排序,因为我们目前所学的所有的方法都需要下标的随机访问来实现,

对于排序我们的思路是,将40G的数据分成40小份每一份就是1G,接着用归并排序的思想先完成一组有序,然后再把文件两两归并再排序,重复几次就可以对这一百亿的数据完成排序

计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

操作步骤:

1. 统计元素出现次数

2. 根据统计的结果将序列回收到原来的序列中

主要思路如下

我们代码实现一下

//计数排序
void countsort(int* a, int n) {
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++) {
		if (max < a[i]) {
			max = a[i];
		}
		if (min > a[i]) {
			min = a[i];
		}
	}
	int nax = max - min + 1;
	int* tmp = (int*)malloc(sizeof(int) * nax);
	memset(tmp, 0, sizeof(int) * nax);
	for (int j = 0; j <n; j++) {
		tmp[a[j] - min]++;
	}
	int p = 0;
	for (int k = 0; k < nax; k++) {
		while (tmp[k]--) {
			a[p++] = k + min;

		}
	}
}

我们测试一下

计数排序的特性总结:

1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。

2. 时间复杂度:O(MAX(N+范围))

3. 空间复杂度:O(范围)

4. 稳定性:稳定

排序算法复杂度及稳定性分析

下面就是本期的所有代码了大家可以参考一下

rost.h 

# define _CRT_SECURE_NO_WARNINGS
#pragma once

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

//插入排序
void Isertrost(int* a, int n);
//希尔排序
void shellrost(int* a, int n);
//选择排序
void selectsort(int* a, int n);
//堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n);
//快速排序霍尔法
int partsort(int* a, int left, int right);
//快排挖坑
int partsort1(int* a, int left, int right);
//快排前后下标法
int partsort2(int* a, int left, int right);
//快排非递归实现
void Quicksort1(int* a, int bgin,int end);
//快排递归实现
void Quicksort(int* a, int bgin, int end);
//归并排序递归实现
void mergesort(int* a, int n);
//归并排序非递归
void mergesort1(int* a, int n);
//快速排序三路划分
void Quicksort1(int* a, int bgin, int end);
//计数排序
void CountSort(int* a, int n);

rost.c

# include"rost.h"
# include"stack.h"





//插入排序
//a是数组,n是数组元素个数
void Isertrost(int* a, int n) {
	for (int j = 0; j < n-1; j++) {
		int end = j;
		int fat = a[j+1];//待插数据
		while (end >= 0) {
			if (fat < a[end]) {
				a[end + 1] = a[end];
				--end;
			}
			else {
				break;
			}
			a[end + 1] = fat;
		}
	}
}
希尔排序
多组并进
//void shellrost(int* a, int n) {
//	int gar = n;
//	while (gar > 1) {
//		gar = gar / 2;
//			for (int j = 0; j < n - gar; j++) {
//				int end = j;
//				int fat = a[end + gar];
//				while (end >= 0) {
//					if (a[end] > fat) {
//						a[end + gar] = a[end];
//						end -= gar;
//					}
//					else {
//						break;
//					}
//				}
//				a[end + gar] = fat;
//			}
//	}
//}


//希尔排序
//单组进行
void shellrost(int* a, int n) {
	int gar = n;
	while (gar > 1) {
		gar = gar / 2;
		for (int i = 0; i < gar; i++) {
			for (int j = i; j < n - gar; j+=gar) {
				int end = j;
				int fat = a[end + gar];
				while (end >= 0) {
					if (a[end] > fat) {
						a[end + gar] = a[end];
						end -= gar;
					}
					else {
						break;
					}
				}
				a[end + gar] = fat;
			}
		}
	}
}


选择排序
//void selectsort(int* a, int n) {
//	for (int j = 0; j < n; j++) {
//		int min = j;
//		for (int i = j; i < n; i++) {
//			if (a[min] > a[i]) {
//				min = i;
//			}
//		}
//		int mon = a[j];
//		a[j] = a[min];
//		a[min] = mon;
//	}
//}


//替换函数
void saw(int* x, int* y) {
	int n = *x;
	*x = *y;
	*y = n;
}

//选择排序
void selectsort(int* a, int n) {
	int mon = n-1;
	for (int j = 0; j < mon; j++) {
		int right=j;
		int left=j;
		for (int i = j; i <= mon; i++) {
			if (a[left] > a[i]) {
				left = i;
			}
			if (a[right] < a[i]) {
				right = i;
			}
		}
		saw(&a[j], &a[left]);
		if (j == right) {
			right = left;
		}
		saw(&a[mon], &a[right]);
		mon--;
	}
}



//向下堆排序
void AdjustDwon(int* a, int n, int i) {
	int childe = 2 * i + 1;
	while (childe < n) {
		if (childe + 1 < n && a[childe] < a[childe + 1]) {
			childe++;
		}
		if (a[i] < a[childe]) {
			saw(&a[i], &a[childe]);
			i = childe;
			childe = 2 * i + 1;
		}
		else {
			break;
		}
	}
}
//堆排序
void HeapSort(int* a, int n) {
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
		int childe = i*2+1;
		if (a[childe] < a[childe + 1]) {
			childe++;
		}
		if (a[childe] > a[i]) {
			saw(&a[childe], &a[i]);
		}
	}
	//删除思路堆排
	int d = n;
	while (d > 0) {
		saw(&a[0], &a[d - 1]);
		d--;
		AdjustDwon(a, d, 0);
	}
}


 冒泡排序
//void BubbleSort(int* a, int n) {
//	for (int j = 0; j < n; j++) {
//		for (int i = 0; i < n-j-1; i++) {
//			if (a[i] > a[i + 1]) {
//				saw(&a[i], &a[i + 1]);
//			}
//		}
//	}
//}



// 冒泡排序
void BubbleSort(int* a, int n) {
	for (int j = 0; j < n; j++) {
		bool add = true;
		for (int i = 0; i < n - j - 1; i++) {
			if (a[i] > a[i + 1]) {
				saw(&a[i], &a[i + 1]);
				add = false;
			}
		}
		if (add) {
			break;
		}
	}
}




快速排序霍尔版本
//int partsort(int* a, int left,int right) {
//	int keyi = left;
//	while (left < right) {
//		while (left<right && a[right] >= a[keyi]) 
//		{
//			right--;
//		}
//		while (left < right && a[left] <= a[keyi]) 
//		{
//			left++;
//		}
//			saw(&a[left], &a[right]);
//	}
//	saw(&a[keyi], &a[left]);
//	return left;
//}
//void Quicksort(int* a, int bgin, int end) {
//	if (bgin >= end) {
//		return;
//	}
//	int key=partsort(a, bgin, end);
//	Quicksort(a, bgin, key-1);
//	Quicksort(a, key+1, end);
//}





快速排序挖坑法
//int partsort1(int* a, int left, int right) {
//	int key = a[left];
//	int hole = left;
//	while (left < right) {
//		while (left < right && a[right] >= key)
//		{
//			right--;
//		}
//		a[hole] = a[right];
//		hole = right;
//		while (left < right && a[left] <= key)
//		{
//			left++;
//		}
//		a[hole] = a[left];
//		hole = left;
//	}
//	a[hole] = key;
//	return hole;
//}
//void Quicksort(int* a, int bgin, int end) {
//	if (bgin >= end) {
//		return;
//	}
//	int key = partsort(a, bgin, end);
//	Quicksort(a, bgin, key - 1);
//	Quicksort(a, key + 1, end);
//}

三值取中
//int threesort(int* a, int left, int right) {
//	int min = (left + right) / 2;
//	if (a[min] > a[left]) 
//	{
//		if (a[right] > a[min]) 
//		{
//			return min;
//		}
//		else if (a[right] < a[left]) 
//		{
//			return left;
//		}
//		else 
//		{
//			return right;
//		}
//		}
//	else 
//	{
//			if (a[right] > a[min]) 
//			{
//				return min;
//			}
//			else if (a[right] > a[left]) 
//			{
//				return left;
//			}
//			else 
//			{
//				return right;
//			}
//			}
//     }
//
//
//
快速排序前后下标法
//int partsort2(int* a, int left, int right) {
//	int min = threesort(a, left, right);
//	saw(&a[min], &a[left]);
//	int key = left;
//	int prev = left;
//	int cur = prev+1;
//	while (cur<=right) {
//		if (a[cur] < a[key] && ++prev!=cur) {
//			saw(&a[prev], &a[cur]);
//		}
//		cur++;
//	}
//	saw(&a[key],&a[prev]);
//	return prev;
//}
//void Quicksort(int* a, int bgin, int end) {
//	if (bgin >= end) {
//		return;
//	}
//	int key = partsort(a, bgin, end);
//	Quicksort(a, bgin, key - 1);
//	Quicksort(a, key + 1, end);
//}


//快排的非递归实现
//void Quicksort1(int* a, int bgin, int end) {
//	Stack pead;
//	stackinit(&pead);
//	stackpush(&pead, end);
//	stackpush(&pead, bgin);
//	while (!stackEmpty(&pead)) {
//		int left = stacktop(&pead);
//		stackpop(&pead);
//		int right = stacktop(&pead);
//		stackpop(&pead);
//		int key = partsort1(a, left, right);
//		if (right > key + 1) {
//			stackpush(&pead, right);
//			stackpush(&pead, key + 1);
//		}
//		if (left < key - 1) {
//			stackpush(&pead, key - 1);
//			stackpush(&pead, left);
//		}
//	}
//	stackDestroy(&pead);
//}


归并排序递归实现
//void Zmergesort(int* a, int left, int right, int* tmp) {
//	if (left >= right) {
//		return;
//	}
//	int mid = (left + right) / 2;
//	Zmergesort(a,left, mid,tmp);
//	Zmergesort(a,mid+1, right,tmp);
//	int bgin1 = left, end1 = mid;
//	int bgin2 = mid + 1, end2 = right;
//	int i = left;
//	while (bgin1 <= end1 && bgin2 <= end2) {
//		if (a[bgin1] < a[bgin2]) {
//			tmp[i++] = a[bgin1++];
//		}
//		else {
//			tmp[i++] = a[bgin2++];
//		}
//	}
//	while (bgin1 <= end1) {
//		tmp[i++] = a[bgin1++];
//	}
//	while (bgin2 <= end2) {
//		tmp[i++] = a[bgin2++];
//	}
//	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
//}
//
//void mergesort(int* a, int n) {
//	//开辟一块空间用于拷贝
//	int* tmp = (int*)malloc(sizeof(int) * n);
//	//因为要进行递归所以创建一个子函数来实现
//	Zmergesort(a, 0, n - 1, tmp);
//	free(tmp);
//	tmp = NULL;
//}


//归并排序非递归
void mergesort1(int* a, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n) {
		int j = 0;
		for (int i = 0; i < n; i += gap * 2) {
			int egin1 = i, end1 = i + gap - 1;
			int egin2 = i + gap, end2 = i + gap * 2 - 1;
			if (egin2 > n-1) {
				break;
			}
			if (end2 > n - 1) {
				end2 = n - 1;
			}
			while (egin1 <= end1 && egin2 <= end2) {
				if (a[egin1] < a[egin2]) {
					tmp[j++] = a[egin1++];
				}
				else {
					tmp[j++] = a[egin2++];
				}
			}
			while (egin1 <= end1) {
				tmp[j++] = a[egin1++];
			}
			while (egin2 <= end2) {
				tmp[j++] = a[egin2++];
			}
				memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
		free(tmp);
	tmp = NULL;
}



归并排序非递归区间处理的第二个方法
//void mergesort1(int* a, int n) {
//	int* tmp = (int*)malloc(sizeof(int) * n);
//	int gap = 1;
//	while (gap < n) {
//		int j = 0;
//		for (int i = 0; i < n; i += gap * 2) {
//			int egin1 = i, end1 = i + gap - 1;
//			int egin2 = i + gap, end2 = i + gap * 2 - 1;
//			if (end1 >= n) {
//				end1 = n - 1;
//				egin2 = n;
//				end2 = n - 1;
//			}
//			else if (egin2 >= n) {
//				egin2 = n;
//				end2 = n - 1;
//			}
//			else if (end2 >= n) {
//				end2 = n - 1;
//			}
//			while (egin1 <= end1 && egin2 <= end2) {
//				if (a[egin1] < a[egin2]) {
//					tmp[j++] = a[egin1++];
//				}
//				else {
//					tmp[j++] = a[egin2++];
//				}
//			}
//			while (egin1 <= end1) {
//				tmp[j++] = a[egin1++];
//			}
//			while (egin2 <= end2) {
//				tmp[j++] = a[egin2++];
//			}
//		}
//		memcpy(a, tmp, sizeof(int) *n);
//
//		gap *= 2;
//	}
//	free(tmp);
//	tmp = NULL;
//}





归并排序递归实现
小区间优化
//void Zmergesort(int* a, int left, int right, int* tmp) {
//	if (left >= right) {
//		return;
//	}
//	//小区间优化
//	if ((right - left + 1) <= 10) {
//		//插入排序
//		Isertrost(a, right - left + 1);
//		return;
//	}
//
//	int mid = (left + right) / 2;
//	Zmergesort(a,left, mid,tmp);
//	Zmergesort(a,mid+1, right,tmp);
//	int bgin1 = left, end1 = mid;
//	int bgin2 = mid + 1, end2 = right;
//	int i = left;
//	while (bgin1 <= end1 && bgin2 <= end2) {
//		if (a[bgin1] < a[bgin2]) {
//			tmp[i++] = a[bgin1++];
//		}
//		else {
//			tmp[i++] = a[bgin2++];
//		}
//	}
//	while (bgin1 <= end1) {
//		tmp[i++] = a[bgin1++];
//	}
//	while (bgin2 <= end2) {
//		tmp[i++] = a[bgin2++];
//	}
//	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
//}
//
//void mergesort(int* a, int n) {
//	//开辟一块空间用于拷贝
//	int* tmp = (int*)malloc(sizeof(int) * n);
//	//因为要进行递归所以创建一个子函数来实现
//	Zmergesort(a, 0, n - 1, tmp);
//	free(tmp);
//	tmp = NULL;
//}





//三值取中
int threesort(int* a, int left, int right) {
	int min = (left + right) / 2;
	if (a[min] > a[left]) 
	{
		if (a[right] > a[min]) 
		{
			return min;
		}
		else if (a[right] < a[left]) 
		{
			return left;
		}
		else 
		{
			return right;
		}
		}
	else 
	{
			if (a[right] > a[min]) 
			{
				return min;
			}
			else if (a[right] > a[left]) 
			{
				return left;
			}
			else 
			{
				return right;
			}
			}
     }


//快速排序三路划分
void Quicksort1(int* a, int bgin, int end) {
	if (bgin >= end) {
		return;
	}
	int left = bgin;
	int right = end;
	int cur = left+1;
	int mid = threesort(a, bgin, end);
	saw(&a[left], &a[mid]);
	int keyi = a[left];
	while (cur <= right) {
		if (a[cur] > keyi) {
			saw(&a[cur], &a[right]);
			right--;
		}
		else if (a[cur] == keyi) {
			cur++;
		}
		else {
			saw(&a[cur], &a[left]);
			cur++;
			left++;
		}
	}
	Quicksort1(a, bgin, left-1);
	Quicksort1(a, cur+1, end);
}


//计数排序
void countsort(int* a, int n) {
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++) {
		if (max < a[i]) {
			max = a[i];
		}
		if (min > a[i]) {
			min = a[i];
		}
	}
	int nax = max - min + 1;
	int* tmp = (int*)malloc(sizeof(int) * nax);
	memset(tmp, 0, sizeof(int) * nax);
	for (int j = 0; j <n; j++) {
		tmp[a[j] - min]++;
	}
	int p = 0;
	for (int k = 0; k < nax; k++) {
		while (tmp[k]--) {
			a[p++] = k + min;

		}
	}
}

test.c

# include"rost.h"
# include"stack.h"





//打印数组
void rost_printf(int* a,int n) {
	for (int i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}
测试插入排序
void Isertcsrost() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf( a,  n);//排序前
	Isertrost(a, n);
	printf("排序后\n");
	rost_printf( a,  n);//排序后
}
测试希尔排序
void shellcsrost() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf(a, n);//排序前
	shellrost(a, n);
	printf("排序后\n");
	rost_printf(a, n);//排序后
}
//选择排序测试
void selectcssort() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf(a, n);//排序前
	selectsort(a, n);
	printf("排序后\n");
	rost_printf(a, n);//排序后
}

//堆排序测试
void HeapSortcs() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf(a, n);//排序前
	HeapSort(a, n);
	printf("排序后\n");
	rost_printf(a, n);//排序后
}
冒泡排序测试
void BubblecsSort() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf(a, n);//排序前
	BubbleSort(a, n);
	printf("排序后\n");
	rost_printf(a, n);//排序后
}

快速排序测试
void Quickcssort() {
	int a[] = { 9,3,5,1,7,5,2,5,8,10,12,5,5,5,5, };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf(a, n);//排序前
	Quicksort1(a, 0,n-1);
	printf("排序后\n");
	rost_printf(a, n);//排序后
}
 
 
归并排序测试
void mergecssort() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf(a, n);//排序前
	mergesort1(a, n);
	printf("排序后\n");
	rost_printf(a, n);//排序后
}
//计数排序测试
void countcssort() {
	int a[] = { 9,3,4,1,7,4,3,2,5,8,10,12 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序前\n");
	rost_printf(a, n);//排序前
	countsort(a, n);
	printf("排序后\n");
	rost_printf(a, n);//排序后
}


int main() {
	插入排序测试
	Isertcsrost();
	希尔排序测试
	shellcsrost();
	//选择排序测试
	selectcssort();
	//堆排序测试
	HeapSortcs();
	//冒泡排序测试
	BubblecsSort();
   //快速排序测试
	Quickcssort();
	//归并排序测试
	mergecssort();
	//计数排序
	countcssort();
	return 0;
}

 以上就是全部内容了,如果有错误或者不足的地方欢迎大家给予建议。 

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值