各大排序总结以及性能测试(c语言)

文章目录

  • 排序
  • 一、排序概念
  • 二、排序的种类
    • 1.插入排序
    • 2.选择排序
    • 3.交换排序
    • ** 快速排序
    • 1)将key值放到正确位置的三种方法:
    • (1)左右指针法(升序)
    • (2)挖坑法
    • (3)前后指针法
    • 2)优化(三数取中法):
    • 3)非递归
    • 用栈实现:
    • 用队列实现:
    • 4.归并排序
    • 用递归的方式进行实现:
    • 用非递归的方式实现:
  • 三、排序复杂度
  • 四、排序性能测试

排序

一、排序概念


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


二、排序的种类

各大排序总结以及性能测试(c语言)_第1张图片


1.插入排序

插入排序的核心思想是:设置一个有序区,并将后面的数不断与有序区的数进行比较,然后插入合适的位置中。

各大排序总结以及性能测试(c语言)_第2张图片

代码如下(直接插入排序):

void InsertSort(int* a, int end)//end是下标
{
	for (int i = 1; i <= end; i++)
	{
		int tmp = a[i];
		int k = i;
		for (int j = i - 1; j >= 0; j--)
		{
			if (tmp < a[j])
			{
				Swap(&a[j], &a[k]);
				k = j;
			}
		}
	}
}

当要进行排序的序列是接近有序的时候,用插入排序的时间复杂度接近于O(N)。对于最坏的情况逆序而言,其时间复杂度为O(N^2)。于是人们思考要如何使一个序列变得接近有序,这时希尔排序就出现了:

代码如下(希尔排序):

void ShellSort(int* a, int end)//希尔排序
{
	int gap = end + 1;
	while(gap>1){ 
	//比如gap=2时,/3直接变成0,没有进行gap=1的插入
		gap = (gap / 3+ 1);
		for (int i = gap; i <= end; i++)
		{
			int end = i;
			while (end >=gap) 
			{
				if (a[end ] < a[end- gap ])
				{
					Swap(&a[end], &a[end - gap]);
					end -= gap;
				}
				else {
					break;
				}
			}
		}
	}
}

希尔排序巧妙地使用了预分组的思想,将序列变得接近有序。

希尔排序通过设置不同的gap进行多次分组,然后分别在组内进行插入排序。

随着gap的逐渐减小,序列变得越来越接近有序。最后当gap=1时,最后一趟排序过称其实就变成了直接插入排序,而此时序列已经接近有序,所以最后一趟的时间复杂度就是O(N)。


2.选择排序

代码如下(选择排序):

void SelectSort(int* a, int end)//选择排序(优化版:一次选两个数)
{
	int maxIndex = 0;//找到最大值的下标
	int minIndex = 0;//找到最小值的下标
	int left = 0;//左边界
	int right = end;//右边界
	while (left < right)//当左边界小于右边界时,循环继续
	{
		for (int j = left; j <= right; j++)
			//每次挑选两个数之后左右边界都会发生变化
			//左边界++,右边界--
		{
			maxIndex = j;
			minIndex = j;
			if (a[maxIndex] < a[j])
			{
				maxIndex = j;
			}
			if (a[minIndex] > a[j])
			{
				minIndex = j;
			}
		}
		Swap(&a[maxIndex], &a[right--]);
		Swap(&a[minIndex], &a[left++]);
	}
}

选择排序:通过每次对序列进行遍历挑选出最小和最大的数,并分别与左右边界的数字进行交换,最后实现有序。同时选择排序的缺点也十分明显,就是当序列接近有序甚至是有序的时候,选择排序还是通过一次次遍历进行选数,从其时间复杂度的角度上就比不上同一梯队的直接插入排序和冒泡排序。

选择排序还有变种鸡尾酒排序,感兴趣的铁子可以去看看(这里不做介绍)。

各大排序总结以及性能测试(c语言)_第3张图片

代码如下(堆排序):

void AdjustDown(int* a, int parent,int end)//排升序,要建大堆
{
	int child = parent * 2 + 1;
	while (child < end)
	{
		if (child + 1 < end && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}

void HeapSort(int* a, int end)//堆排序
{
	int parent = (end - 1) / 2;
	for (int i = 0; i < parent; i++)//建大堆
	{
		AdjustDown(a, parent - i, end );
	}
	for (int j =0 ;j<end;j++)//每次把堆顶的数换到数组尾部
	{
		Swap(&a[0], &a[end - j]);
		AdjustDown(a, 0, end - j);
	}
}

向下调整法:以建大根堆(root结点为例),先对root的两个孩子结点进行比较,用那个大的结点与父亲结点(也就是根节点)进行比较,如果比root大,就将父亲节点与那个大的孩子结点进行交换,通过一次次的重复比较,之前的那个root结点要么到达了叶子节点,要么就大于他现在的两个孩子结点。

建堆:堆本身就是一颗完全二叉树,堆有大根堆和小根堆之分。大根堆就是任意父亲节点大于与之相连的孩子节点,所以大根堆的root结点就是最大值。当我们得到一串无序序列时,我们可以去寻找其最后一个父亲结点,通过向下调整法进行向下调整,将小的数交换到下面。并以最后一个父亲结点为起点,一步一步进行调整直到调整到root结点(最顶端的结点),这样大根堆就建好了。

选数:根据大根堆的特性,root结点的数字最大,通过将他与最后的叶子结点(边界)进行交换,将最大的数字就排到了最后,小的数字就到达了堆顶,然后将排好的最大数字除外,重新进行向下调整,然后将次大的数排到边界,依此类推,直到最后的root结点,序列就有序了。

注意!!!排升序的时候要建大堆,排降序的时候要建小堆。原因时:以升序为例,如果建小堆,那么我们每次可以选出最小的数,但是选出来之后,整棵树的父子关系就完全乱套了,那我们就要重新建堆,建堆的时间复杂度为O(N),若每次都要重新建堆,其时间复杂度就达到了O(N^2),那还不如直接选择排序。(费时费力,代码都要敲半天)


3.交换排序

代码如下(冒泡排序):

void BubbleSort(int* a, int end)
{
	for (int i = 0; i <= end; i++)
	{
		for (int j = 1; j <= end - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				Swap(&a[j - 1], &a[j]);
			}
		}
	}
}

各大排序总结以及性能测试(c语言)_第4张图片
冒泡排序:(升序)就是通过一次次相邻元素的比较,把较大的数字排到后面,较小的数字排到前面。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。


各大排序总结以及性能测试(c语言)_第5张图片

** 快速排序

快速排序:运用了分治思想,每次都选出一个key(一般是最左边或者最右边),通过一系列的操作使这个key的到达它的正确位置。即(升序)它左边的数字都小于它,它右边的数字都大于它,这样这个key的位置就不会再发生改变了。同样的,key值将原本的一个序列分成了两个序列,这个时候我们就分别再重新选取key值,将它排好,又将两边序列分别分成了两段,以此类推,最后序列就可以达到有序。

1)将key值放到正确位置的三种方法:

(1)左右指针法(升序)

如下图,p为key值,若选取最右边为基准值(key),那么指向最左边的指针就应该先向右移动,去找比基准值大的数,如果找到了,再移动右边的指针,向左去找比基准值小的数,找到后,将两者交换,直到左右指针相遇,此时将基准值与相遇的地点进行交换,那么基准值左边的数字都比它小,右边的数字都比它大。

各大排序总结以及性能测试(c语言)_第6张图片
注意!!如果把最右边的数定为基准值的话,一定要先移动左边的指针。否则如下图:(升序)最后那个比基准值小的4被交换到了最右边。

(2)挖坑法

同样的先选取最左边或者最右边为基准值(这里选取最左边),将基准值保存在一个临时变量中,这样原存放key值的地方就空了,然后右边的R先走去找比key小的数,找到了就把它换到key的坑位,这时候右边小人的这个位置上因为数字被换走了,就出现了新的坑位,然后左边的L向右走去找比key大的数,找到后将它填到右边R的那个坑位。以此类推,当二者相遇时直接将key填到R和L脚下的坑里面。

(3)前后指针法

先取基准值(此处为最右边),cur指针向右去找比key大的数字,如果找到了,prev就加一,同时交换两个指针所指向的数字。以此类推,最后当cur指针指向最右边时,prev加一,同时将key值和prev所指向的数字进行交换。(这个方法中的prev和cur可以同时和key在同一侧)

三种方法的代码如下:

int  PartSort1(int* a, int begin, int end)//左右指针法
{
	Swap(&a[begin], &a[SeletMidIndex(a, begin, end)]);
	int key = begin;
	int left = begin;//最左边界
	int right = end;//最右边界

	while (begin < end) {

		while (end > begin && a[end] >= a[key])//end>=begin等号不能要,有等号数组就会越界 
		{
			end--;//key选取的最左边,则从右边开始找大于a[key]的
		}

		while (begin < end && a[begin] <= a[key])//end>=begin等号不能要
		{
			begin++;//右边找完后,在从左边开始找小于a[key]的
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[key]);
	return begin;
}

int  PartSort2(int* a, int begin, int end)//前后指针法
{
	Swap(&a[begin], &a[SeletMidIndex(a, begin, end)]);
	int key = begin;
	int cur = begin + 1;//从key的右边开始
	int prev = begin;

	while (cur <= end) {//找到了小于a[key]的数,就把prev++,然后交换
		if (a[cur] < a[key])
		{
			++prev;
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}

	Swap(&a[prev], &a[key]);
	return prev;
}

int PartSort3(int* a, int begin, int end)//挖坑法
{
	Swap(&a[begin], &a[SeletMidIndex(a, begin, end)]);
	int tmp = a[begin];//保存key值
	int left = begin;
	int right = end;

	while (begin < end)
	{
		while (end > begin && a[end] >= tmp)
		{
			end--;
		}

		a[begin] = a[end];//begin中的a[begin]值已空,可以将a[end]赋给a[begin]
		while (end > begin && a[begin] <= tmp)
		{
			begin++;
		}
		a[end] = a[begin];//end中的a[end]值已空,可以将a[end]赋给a[begin]
	}

	a[begin] = tmp;
	return begin;
}

三种方法中最好理解的是挖坑法(个人看法)。

2)优化(三数取中法):

快速排序的优点很明显,通过分治思想将序列一分为二。但是,不是每一个序列中key的值都在中间,如果取极端情况(逆序),其时间复杂度就变成了O(N^2),三数取中法就很好地避免这种极端情况。通过取出两端和中间的三个数字,将他们一一比较,得到大小是中间的那个数字,然后将这个数字与key的位置进行交换,保证了key既不是最小的数字,也不是最大的数字。(效果十分明显,这点在第四点性能测试中有很好的表现)

代码如下:



int SeletMidIndex(int* a, int begin, int end)//三数取中
{
	int mid = (begin + end) >> 1;

	if (a[begin] < a[end])
	{
		if (a[end] < a[mid])
			return end;
		else//[end] > a[mid]
		{
			if (a[mid] < a[begin])
				return begin;
			return mid;//a[mid] > a[begin]
		}
	}
	else//a[begin] > a[end]
	{
		if (a[begin] < a[mid])
			return begin;
		else //a[begin] > a[mid]
		{
			if (a[mid] < a[end])
				return end;
			return mid;//a[mid] > a[end]
		}
	}
}

关于另一种优化方法是小区间优化法。随着每一次的序列划分,序列的个数呈指数增长,尤其是最后的几层。于是就这点引入了小区间优化,选定一个适当的数字,如果序列的元素个数小于这个数字,就换用别的排序方法对这个序列进行排序。(常用的是直接插入法)


3)非递归

为什么会使用非递归的方法来实现快速排序呢?因为当所要进行排序的序列中元素数量过大的话,递归就不再适用了。随着递归的次数增加,终究会有把栈撑破的一天(栈溢出),这个时候我们就不能使用递归的方式来进行排序了。

用栈实现:

栈的性质:先进后出。
根据这个性质我们要把序列的左右边界存入数组之中,然后取出来。通过key的位置的确定左右两段序列的边界,并将他们存入栈中,然后再取出来,划分后再存入,直到栈中的元素全部都被取出来,序列也就有序了。

代码如下:


typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;//栈顶
	int capacity;//容量
}Stack;

void PrintStack(Stack pa);//打印
void InitStack(Stack* pa);//初始化
void DestroyStack(Stack* pa);//销毁栈
void StackPush(Stack* pa, STDataType x);//入栈
void StackPop(Stack* pa);//出栈
bool StackEmpty(Stack pa);//判断栈是否为空
STDataType StackTop(Stack pa);//获得栈顶元素
void PrintStack(Stack pa)//栈元素的打印
{
	assert(pa.a);
	for (int i = 0; i <= pa.top; i++)
		printf("%d ", pa.a[i]);
	printf("\n");
}

void InitStack(Stack* pa)//栈的初始化
{
	assert(pa);
	pa->a = (STDataType*)malloc(sizeof(int) * 4);//开辟空间

	if (NULL == pa->a)//判断空间是否开辟成功
	{
		printf("InitStack fail\n");
		exit(-1);
	}

	pa->capacity = 4;//初始容量为4
	pa->top = -1;//初始top设为1,就可以使top变成数组元素下标
}

void DestroyStack(Stack* pa)//销毁栈
{
	assert(pa);
	free(pa->a);
	pa->a = NULL;
}
void StackPush(Stack* pa, STDataType x)//入栈
{
	assert(pa);
	if (pa->top == pa->capacity - 1)//判断栈是否已满,如果满了就进行开辟
	{
		STDataType* newA = (STDataType*)realloc
		(pa->a, sizeof(int) * pa->capacity * 2);

		if (NULL == newA)//判断开辟是否成功
		{
			printf("realloc fail\n");
			exit(-1);
		}

		pa->a = newA;//realloc开辟时,新的地址可能和原地址不同,需要重新赋给a
		pa->capacity *= 2;//容量翻倍
	}

	pa->top++;//栈顶元素的下标
	pa->a[pa->top] = x;
}
void StackPop(Stack* pa)//出栈
{
	assert(pa);
	pa->top--;
}
bool StackEmpty(Stack pa)//判断栈空
{
	return pa.top == -1;
}
STDataType StackTop(Stack pa)//获得栈顶元素
{
	assert(pa.a);
	return pa.a[pa.top];
}
//非递归的快速排序(用栈来实现)
void QuickSort2(int* a, int begin, int end)
{
	Stack s;
	InitStack(&s);//对栈进行初始化
	StackPush(&s, begin);//插入初始位置
	StackPush(&s, end);//插入末尾位置

	while (!StackEmpty(s))//如果栈为空,则循环停止
	{
		int right = StackTop(s);//根据栈先进后出的性质,栈顶元素为right
		StackPop(&s);
		int left = StackTop(s);//删除了栈顶元素后,下一个栈顶元素为left
		StackPop(&s);
		int key = PartSort1(a, left, right);//获得通过左右指针法排序一次后key的位置

		if (left < key - 1)//对左边数组进行判断
		{
			StackPush(&s, left);
			StackPush(&s, key - 1);
		}

		if (right > key + 1)
		{
			StackPush(&s, key + 1);
			StackPush(&s, right);
		}
	}
	DestroyStack(&s);//销毁栈,防止内存泄漏
}

用队列实现:

队列的性质:先进先出。
与栈类似,需要注意的是,元素的取出顺序,因为栈中是先进后出。

代码如下:

typedef int QUDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QUDataType val;
}QueueNode;

typedef struct Queue//定义结构体类型(头尾指针)
{
	QueueNode* front;
	QueueNode* rear;
}Queue;

void PrintQueue(Queue* pq);//打印
void InitQueue(Queue* pq);//初始化
void DestroyQueue(Queue* pq);//销毁队列
void QueuePush(Queue* pq, QUDataType x);//队尾入
void QueuePop(Queue* pq);//队头出
bool QueueEmpty(Queue* pq);//判断队列是否为空
QUDataType QueueFront(Queue* pq);//获得队头元素
QUDataType QueueRear(Queue* pq);//获得队尾元素
void PrintQueue(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(&pq));
	QueueNode* cur = pq->front;
	while (cur)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
}
void InitQueue(Queue* pq)
{
	assert(pq);
	pq->front = pq->rear = NULL;//头指针和尾指针都置为空
}
void DestroyQueue(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->front;//保存要销毁的结点
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->front = pq->rear = NULL;
}
void QueuePush(Queue* pq, QUDataType x)
{
	assert(pq);
	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NULL == newNode)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newNode->val = x;
	newNode->next = NULL;
	if (pq->front == NULL)//只有头尾指针,没有结点
	{
		pq->front = pq->rear = newNode;

	}
	else//有一个及以上结点
	{
		pq->rear->next = newNode;
		pq->rear = newNode;

	}
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	QueueNode* cur = pq->front;
	if (pq->front == pq->rear)//一个结点
	{
		free(cur);
		pq->front = pq->rear = NULL;
	}
	else
	{
		pq->front = cur->next;
		free(cur);
		cur = NULL;
	}
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->front == NULL;
}
QUDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->front->val;
}
QUDataType QueueRear(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->rear->val;
}
//非递归的快速排序(用队列来实现)
void QuickSort3(int* a, int begin, int end)
{
	Queue pq;
	InitQueue(&pq);//队列初始化
	QueuePush(&pq, begin);//队列插入左边界
	QueuePush(&pq, end);//队列插入右边界

	while (!QueueEmpty(&pq))//队列不为空就继续
	{
		int left = QueueFront(&pq);//队列性质:先进先出
		QueuePop(&pq);
		int right = QueueFront(&pq);
		QueuePop(&pq);
		int key = PartSort1(a, left, right);

		if (left < key - 1)
		{
			QueuePush(&pq, left);
			QueuePush(&pq, key - 1);
		}

		if (key + 1 < right)
		{
			QueuePush(&pq, key + 1);
			QueuePush(&pq, right);
		}

	}
	DestroyQueue(&pq);//销毁队列
}


4.归并排序

归并排序:通过分治思想,每次将序列均分,直到序列只有一个元素的时候,此时一个元素算有序了。然后我们就按照原路径一次返回,此过程必须创建一个和原序列有相同元素个数的数组。比如说我现在有两个数字4和2,他们在被分成单个之后进行返回,就要将他们进行比较大小,数字小的先进入数组,这样这两个数字就有序了。以此类推,两两有序后,将他们赋值给原数组,然后返回到之前的四个数字序列中,依旧小的先进入临时数组。最后回到原序列中就会有序了。
各大排序总结以及性能测试(c语言)_第7张图片

用递归的方式进行实现:

代码如下

//1)递归
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	//将数组拆分成有序的
	int mid = (begin + end) >> 1;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1 , end, tmp);
	
	//开始合并数组
	int left1 = begin, right1=mid;
	int left2 = mid + 1, right2 = end;
	int i = begin;

	while (left1<= right1 && left2<= right2)
	{
		if (a[left1] < a[left2])
			tmp[i++] = a[left1++];
		else
			tmp[i++] = a[left2++];
	}

	while (left1 <= right1)
		tmp[i++] = a[left1++];
	while (left2 <=right2)
		tmp[i++] = a[left2++];
	
	//归并后将数组拷回原数组
	for (int k = begin; k <= end;k++)
	{
		a[k] = tmp[k];
	}
}

void MergeSort(int* a, int end)//归并排序
{
	int *tmp = (int*)malloc(sizeof(int) * end );
	if (NULL == tmp)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	_MergeSort(a, 0, end-1, tmp);

	free(tmp);
}

用非递归的方式实现:

同快排的原因一样,防止栈溢出。
归并排序实质就是将序列划分成一个个元素,然后两两比较,四四比较,最后有有序了。

代码如下:


//非递归
void _MergeSortNonR(int* a, int begin, int end, int* tmp)
{
	int gap = 1;
	int left1 = begin;int right1 = end-1;
	int left2 = begin; int right2 = end-1;

	while (gap <end+1)//+1很重要,防止9个数字这种情况,最末尾一个没有被排序
	{

		int k = 0;
		for (int i = 0; i <= end; i += 2 * gap)
		{
			left1 = i , right1 = i+gap - 1;//第一组数组
			left2 = i+gap, right2 = i +2* gap - 1;//第二组数组

			if (right1 >= end)//第二组数组不存在时
			{
				while (left1 <= end)//如果第一组数组有剩余,则直接入tmp数组
				{
					tmp[k++] = a[left1++];
				}
			}

			else if (right2 > end)//第二组数组存在一部分
			{
				right2 = end;
				while (left1 <= right1 && left2 <= right2)//判断两组数组是否有到达边界
				{
					//按小到大的顺序入数组
					if (a[left1] < a[left2])
						tmp[k++] = a[left1++];
					else
						tmp[k++] = a[left2++];
				}

				while (left1 <= right1)//如果第一组数组有剩余,则直接入tmp数组
				{
					tmp[k++] = a[left1++];
				}
				while (left2 <= right2)//如果第二组数组有剩余,则直接入tmp数组
				{
					tmp[k++] = a[left2++];
				}

			}
			else {

				while (left1 <= right1 && left2 <= right2)//判断两组数组是否有到达边界
				{
					//按小到大的顺序入数组
					if (a[left1] < a[left2])
						tmp[k++] = a[left1++];
					else
						tmp[k++] = a[left2++];
				}

				while (left1 <= right1)//如果第一组数组有剩余,则直接入tmp数组
				{
					tmp[k++] = a[left1++];
				}
				while (left2 <= right2)//如果第二组数组有剩余,则直接入tmp数组
				{
					tmp[k++] = a[left2++];
				}

			}
		}
		for (int j = 0; j <= end; j++)//将tmp数组的值给原数组
			a[j] = tmp[j];
		gap *= 2;
	}

}

void MergeSortNonR(int* a, int end)//归并排序
{
	int* tmp = (int*)malloc(sizeof(int) * end);//开辟一个动态数组
	if (NULL == tmp)//判断数组是否开辟成功
	{
		printf("malloc fail\n");
		exit(-1);
	}

	_MergeSortNonR(a, 0, end -1, tmp);

	free(tmp);//摧毁数组
}

三、排序复杂度

各大排序总结以及性能测试(c语言)_第8张图片


四、排序性能测试

测试代码:

#define _CRT_SECURE_NO_WARNING
#include"Sort.h"

void TestStack()//测试栈
{
	Stack s;
	InitStack(&s);
	StackPush(&s, 1);
	StackPush(&s, 1);
	StackPop(&s);
	PrintStack(s);
	printf("%d\n", StackEmpty(s));
	DestroyStack(&s);
}

void TestQueue()//测试队列
{
	Queue pq;
	InitQueue(&pq);
	QueuePush(&pq,1);
	QueuePush(&pq,2);
	QueuePush(&pq,3);
	QueuePop(&pq);
	printf("%d\n", QueueEmpty(&pq));
	printf("%d\n", QueueFront(&pq));
	printf("%d\n", QueueRear(&pq));
	PrintQueue(&pq);
	DestroyQueue(&pq);
}


void TestInsertSort()//插入
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	InsertSort(arr, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
}

void TestQuickSort1()//递归
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	QuickSort1(arr, 0, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
}

void TestQuickSort2()//非递归,栈实现
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	QuickSort2(arr, 0, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
}

void TestQuickSort3()//非递归,队列实现
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	QuickSort3(arr, 0, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
}

void TestSelectSort()//选择
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	SelectSort(arr, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
}

void TestBubbleSort()//冒泡
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	BubbleSort(arr, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
}

void TestShellSort()//希尔
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	ShellSort(arr, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
}
void TestHeapSort()//堆排
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	HeapSort(arr, sizeof(arr) / sizeof(int) - 1);
	PrintSort(arr, sizeof(arr) / sizeof(int));
} 
void TestMergeSort()//递归
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	MergeSort(arr, sizeof(arr) / sizeof(int) );
	PrintSort(arr, sizeof(arr) / sizeof(int));
}
void TestMergeSortNonR()//非递归
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0};
	MergeSortNonR(arr, sizeof(arr) / sizeof(int));
	PrintSort(arr, sizeof(arr) / sizeof(int));
}
void TestOP()//排序的性能测试
{
	srand(time(0));
	const int N = 5000000;

	int* b1 = (int*)malloc(sizeof(int) * N);
	int* b2 = (int*)malloc(sizeof(int) * N);
	int* b3 = (int*)malloc(sizeof(int) * N);
	int* b4 = (int*)malloc(sizeof(int) * N);
	int* b5 = (int*)malloc(sizeof(int) * N);
	int* b6 = (int*)malloc(sizeof(int) * N);
	int* b7 = (int*)malloc(sizeof(int) * N);
	int* b8 = (int*)malloc(sizeof(int) * N);
	int* b9 = (int*)malloc(sizeof(int) * N);
	int* b10 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; i++)
	{
		b1[i] = rand();
		b2[i] = b1[i];
		b3[i] = b1[i];
		b4[i] = b1[i];
		b5[i] = b1[i];
		b6[i] = b1[i];
		b7[i] = b1[i];
		b8[i] = b1[i];
		b9[i] = b1[i];
		b10[i] = b1[i];
	}

		/*int begin1 = clock();
		InsertSort(b1,N-1);
		int end1 = clock();
		printf("InsertSort->:%d\n", end1 - begin1);*/

		int begin2 = clock();
		SelectSort(b2,N-1);
		int end2 = clock();
		printf("SeletSort->:%d\n", end2 - begin2);

		//int begin3 = clock();
		//BubbleSort(b3,  N - 1);
		//int end3 = clock();
		//printf("BubbleSort->:%d\n", end3 - begin3);

		int begin4 = clock();
		ShellSort(b4,  N - 1);
		int end4 = clock();
		printf("ShellSort->:%d\n", end4 - begin4);

		int begin5 = clock();
		HeapSort(b5,  N - 1);
		int end5 = clock();
		printf("HeapSort->:%d\n", end5 - begin5);

		int begin6 = clock();
		QuickSort1(b6, 0, N - 1);
		int end6 = clock();
		printf("QuickSort1(递归)->:%d\n", end6 - begin6);

		int begin7 = clock();
		QuickSort2(b7, 0, N - 1);
		int end7 = clock();
		printf("QuickSort2(非递归,栈)->:%d\n", end7 - begin7);

		//用单链表实现的,cup命中率不高
		int begin8 = clock();
		QuickSort3(b8, 0, N - 1);
		int end8 = clock();
		printf("QuickSort3(非递归,队列)->:%d\n", end8 - begin8);
	
		int begin9 = clock();
		MergeSort(b9,  N );
		int end9 = clock();
		printf("MergeSort(递归)->:%d\n", end9 - begin9);

		int begin10 = clock();
		MergeSortNonR(b10, N);
		int end10 = clock();
		printf("MergeSortNonR(非递归)->:%d\n", end10 - begin10);

			free(b1);
			free(b2);
			free(b3);
			free(b4);
			free(b5);
			free(b6);
			free(b7);
			free(b8);
			free(b9);
			free(b10);
		}

void TestSort()
{
	TestQuickSort1();
}

int main() {

	TestOP();
	//TestSort();
	//TestStack();
	//TestQueue();
	return 0;
}



注:

本测试通过生成随机数数组进行测试。
本次测试在Release版本中进行。
Relsase的优化程度比Debug高。在Debug中递归的消耗较大,而在Release版本中,递归得到了极大的优化。
解释一下:因为队列是用单链表写的,而栈是用数组写的,数组cpu命中率高于链表,所以速度会更快。
(其中N为随机数的个数)

1)先放100个数小测一下:

各大排序总结以及性能测试(c语言)_第9张图片

2)没有啥反应,继续继续。

把N改成1000(一千)

各大排序总结以及性能测试(c语言)_第10张图片
3)还没反应,再加个0试试。

N=10000(一万)

各大排序总结以及性能测试(c语言)_第11张图片
区别这就出来了,冒泡是最慢的,其次是直接插入。

4)N=100000(十万)

各大排序总结以及性能测试(c语言)_第12张图片
5)N=1000000(一百万)

(由于冒泡太慢了,就把冒泡去掉了,给我卡了好久都没出来)
各大排序总结以及性能测试(c语言)_第13张图片

冒泡去掉了都给我跑了这么久。。

6)N=5000000(五百万)

选择丢了,卡了好久都没出来,我放弃了各大排序总结以及性能测试(c语言)_第14张图片
直接看希尔,堆排,快排和归并吧。
各大排序总结以及性能测试(c语言)_第15张图片
果然一去掉选择就出来了。

7)N=10000000(一千万)

各大排序总结以及性能测试(c语言)_第16张图片

这几个果然厉害,继续继续。

8)N=50000000(五千万)

各大排序总结以及性能测试(c语言)_第17张图片

9)N=100000000

挑战一亿了
各大排序总结以及性能测试(c语言)_第18张图片


(各位老哥,码字不易,球球了,给个赞吧。)

你可能感兴趣的:(c语言,排序算法)