常见类型排序算法的代码实现

test.c


#include"Sort.h"

int main()
{
	TestSort();
	return 0;
}

sort.h

#pragma once
#include"stdio.h"


//比较类排序:
//插入排序
void InsertSort(int array[], int size);


//希尔排序
void ShellSort(int array[], int size);

//选择排序
void SelectSort(int array[], int size);

//选择排序
void SelectSortOP(int array[], int size);

//堆排序
void HeapSort(int array[], int size);

//冒泡排序
void BubbleSort(int array[], int size);

//改良版本
void BubbleSortOP(int array[], int size);

//快速排序
void QuickSort(int array[], int left, int right);

//将递归转换为循环
void QuickSortNor(int array[], int size);

//归并排序
void MergeSort(int array[], int size);

//将递归转换为循环  
void MergeSortNor(int array[], int size);


//非比较类排序
void CountSort(int array[], int size);


void TestSort();//测试

sort.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Sort.h"
#include <stdio.h>
#include "stack.h"
#include<malloc.h>
#include<string.h>

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

//插入排序(从小到大)
//每次读取数据时,我们将其进行插入:
//1.找待插入数据的位置
//2.插入
//在整个排序过程中,没有使用额外的辅助空间,空间复杂度:O(1)
void InsertSort(int array[], int size)
{
	//注意:i从1开始
	for (int i = 1; i < size; ++i)//外层循环控制:有size-1个数据需要插入
	{
		//模拟单个元素插入过程
		int key = array[i];
		int end = i - 1;

		//找待插入元素在区间中的位置
		//最差情况下:需要i位置之前所有已插入的元素整体往后进行搬移---O(n) 
		while (end >= 0 && key < array[end])// key < array[end] 这句中 没有=:算法稳定  有=:算法不稳定
		{
			array[end + 1] = array[end];
			end--;
		}

		//插入元素 
		//注意:当插入0元素时,end+1  = -1+1 = 0;
		array[end + 1] = key;
	}
}
//问题?什么情况下插入排序性能最差 (时间复杂度问题)
//给的数据为顺序,让你排列成降序--最差  时间复杂度:O(N^2)
//反之给的数据为顺序,让你排列成顺序--最好  时间复杂度:O(N)

//即:插入排序使用的场景:
//1.数据有序或者接近有序(注意:有序或接近有序与用户所需要序列一致)
//2.数据量少--搬移元素个数少

//问题:现实中数据量往往比较大,数据比较乱--要求:利用插入排序的思想来排序?
//解:将用户所给的场景向插入排序的场景进行转变
//数据凌乱--想办法将该数据逐渐接近有序--比较困难
//数据量大--想办法让数据逐渐变小--相对简单--将数组进行分组--最常见的(对数据进行平均分组)
//对数据进行平均分组:每组数据个数较少,用插入排序对每个分组进行排序
//接近有序:小的数据尽量靠前,大的数据尽量靠后,不大不小的数据尽量靠中间
//分组方式:让数据按照一定的间隔进行分组
//0.间隔(未知)个位置的所有数据进行排序
//。。。。。。
//1.间隔三个位置的所有数据进行排序
//2.间隔两个位置的所有数据进行排序
//3.间隔一个位置的所有数据进行排序
//---希尔排序(shellsort)

 //希尔排序
void ShellSort(int array[], int size)
{
	int gap = size;
	while (gap > 1)
	{
		//gap/=2//希尔给出的
		//gap每次取素数
		gap = gap / 3 + 1;//kunth大量实验证明此为最好
		//注意:i从1开始
		for (int i = gap; i < size; ++i)//外层循环控制:有size-1个数据需要插入
		{
			//模拟单个元素插入过程
			int key = array[i];
			int end = i - gap;

			//找待插入元素在区间中的位置
			//最差情况下:需要i位置之前所有已插入的元素整体往后进行搬移---O(n) 
			while (end >= 0 && key < array[end])// key < array[end] 这句中 没有=:算法稳定  有=:算法不稳定
			{
				array[end + gap] = array[end];
				end -= gap;
			}

			//插入元素:插入到当前分组的下一个位置
			array[end + gap] = key;
		}
	}


}
//看排序算法是否稳定:
//1.排序算法原理在插入时是否隔着区间(隔着元素)来进行插入
//2.排序算法原理在交换时是否隔着区间(隔着元素)来进行交换
//是:则不稳定(相等的元素则会跳过去)
//否:则稳定
//----》希尔排序不稳定
//空间复杂度:O(1)
//时间复杂度:O(N^1.25)~O(1.6N^1.25)

void Swap(int* left, int* right)
{
	int temp = *left;
	*left = *right;
	*right = temp;
}

//选择排序
//时间复杂度:O(N^2)
//空间复杂度:O(1)
//稳定性:不稳定
void SelectSort(int array[], int size)
{
	for (int i = 0; i < size - 1; ++i)
	{
		//找当前区间中最大元素的位置 
		int maxPos = 0;
		for (int j = 1; j < size - i; ++j)
		{
			if (array[j] > array[maxPos])
				maxPos = j;
		}
		if (maxPos != size - i - 1)
		{
			Swap(&array[maxPos], &array[size - i - 1]);
		}
	}
}

//选择排序
void SelectSortOP(int array[], int size)
{
	int begin = 0, end = size - 1;
	while (begin < end)
	{
		//在[begin,end]区间中找最大和最小的元素
		int maxPos = begin,minPos = begin;
		int j = begin + 1;

		while (j <= end)
		{
			if (array[j] >array[maxPos])
				maxPos = j;

			if (array[j] < array[minPos])
				minPos = j;
			++j;
		}

		//如果最大元素不在区间最后的位置、
		if (maxPos != end)
			Swap(&array[maxPos], &array[end]);

		//如果end位置存储的刚好是最小的元素,上面的交换就将最小的元素位置更改了--maxpos
		//最小元素的位置发生了改变,则必须要更新minPos
		if (minPos == end)//minPos和end刚好相等的情况下,说明在交换之前最小元素一定在最后一层
			minPos = maxPos;

		//最小的元素不在区间的起始位置
		if (minPos != begin)
			Swap(&array[minPos], &array[begin]);

		++begin;
		--end;
	}
}
//选择排序有什么缺陷?
//重复遍历太多
//怎么才能减少重复遍历次数?
//---堆排序
//1.建堆:建大堆还是建小堆? 取决于用户需要升序(大堆)还是降序(降序)
//问题:建堆需要用到什么调整?---向下调整
//______向下调整是从哪个位置开始调整的?倒数第一个非叶子节点的位置~到根节点所有的节点应用向下调整即可建成堆
//______倒数第一个非叶子节点是怎样算的?倒数第一个非叶子节点就是最后一个叶子节点的双亲:(size-1-1)/2
//2.利用堆删除的思想进行排序
//______将堆顶元素与队中最后一个元素进行交换(即将数组中最大或最小的元素放到末尾)
//______将堆中有效元素个数减少一个 
//______将堆顶的元素向下调整 
//核心:向下调整

void HeapAdjust(int array[], int size, int parent)
{
	//用child标记parent较大的孩子,默认先标记parent的左孩子
	//先标记左孩子的原因是:如果parent有孩子,parent一定是先有左孩子,然后才有右孩子
	int child = parent * 2 + 1;

	while (child < size)
	{
		//找parent中较大的孩子:parent左右孩子比较
		//必须先保证parent的右孩子存在
		if (child+1 < size && array[child + 1] > array[child])
			child += 1;

		//检查parent是否满足堆的性质
		if (array[child] > array[parent])
		{
			Swap(&array[child], &array[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;
	}
}

//堆排序
//空间复杂度:O(1)
//时间复杂度:O(Nlog2^N)
//稳定性:不稳定  
void HeapSort(int array[], int size)
{
	int end = size - 1;
	//1.建堆
	//注意从倒数第一个非叶子节点的位置开始使用堆调整,一直调整到根节点的位置 
	for (int root = (size - 2) / 2; root >= 0; root--)
		HeapAdjust(array, size, root);//向下调整


	//2.排序-->利用堆删除的思想进行排序 
	while (end)
	{
		Swap(&array[0], &array[end]);
		HeapAdjust(array, end, 0);
		end--;
	}
}

//交换排序:
//1.冒泡排序
//for()--》外层循环:控制冒泡的趟数 
//   for()--》内层循环:控制冒泡的方式--用相邻的两个元素来进行比较,不满则要求将相邻的元素进行交换,直到区间末尾
//2.快排


//冒泡排序
//时间复杂度:O(N^2)
//空间复杂度:O(1 )
//稳定性:稳定
//void BubbleSort(int array[], int size)
//{
//	//控制冒泡的趟数
//	for (int i = 0; i < size - 1; ++i)//-1的目的是可以少冒一趟,因为最后一次冒泡区间中只剩余一个元素
//	{
//		//具体冒泡的方式:用相邻位置的元素进行比较,如果不满足条件,就进行交换
//		/*方法一:
//		//j:表示前一个元素的下标
//		for (int j = 0; j < size - i - 1; ++j)//-1目的:j最多只能取到冒泡区间的倒数第二个元素
//		{
//			if (array[j] > array[j + 1])
//				Swap(&array[j], &array[j + 1]);
//		}
//        */
//		//方法二:方法二为方法一变换而来,注意方法代码第三行j-1和j的关系(防止写成降序)
//		//j:表示后一个元素的下标
//		for (int j = 1; j < size - i; ++j)//-1目的:j最多只能取到冒泡区间的倒数第二个元素
//		{
//			if (array[j-1] > array[j])
//				Swap(&array[j], &array[j - 1]);
//		}
//
//	}
//}

//问题:给出的序列为0123456798,这个序列在第一躺中便已经排好,如果按照上述方法,则会浪费大量时间
//void BubbleSortOP(int array[], int size)
//{
//	int flag = 0;
//	//控制冒泡的趟数
//	for (int i = 0; i < size - 1; ++i)//-1的目的是可以少冒一趟,因为最后一次冒泡区间中只剩余一个元素
//	{
//		//具体冒泡的方式:用相邻位置的元素进行比较,如果不满足条件,就进行交换
//		//j:表示后一个元素的下标
//		flag = 0;//该躺冒泡还没有比较,因此将flag设置为0
//		for (int j = 1; j < size - i; ++j)//-1目的:j最多只能取到冒泡区间的倒数第二个元素
//		{
//			if (array[j - 1] > array[j])
//			{
//				Swap(&array[j], &array[j - 1]);
//				flag = 1;//在该躺冒泡时区间还无序
//			}	
//		}
//		if (!flag)
//			return;
//	}
//}

//三数取中法:三个数据取最中间的数据作为基准值
int GetMiddleIndex(int array[], int left, int right)
{
	int mid = left + ((right - left) >> 1);

	//三个数据:left,mid,right-1
	if (array[left] < array[right - 1])
	{
		if (array[mid] < array[left])
			return left;
		else if (array[mid]>array[right - 1])
			return right - 1;
		else
			return mid;
	}
	else
	{
		if (array[mid] > array[left])
			return left;
		else if (array[mid] < array[right - 1])
			return right - 1;
		else
			return mid;
	}



}


int Partion1(int array[], int left, int right)
{
	int begin = left;
	int end = right - 1;
	int keyofindex = GetMiddleIndex(array, left, right);
	int key;
	if (keyofindex != right - 1)
	{
		Swap(&array[keyofindex], &array[right - 1]);
	}
	key = array[keyofindex];

	while (begin < end)
	{
		//让begin从前往后找,找比基准值大的元素,找到了就停下来
		while (begin < end && array[begin] <= key)
			begin++;

		//让end从后往前找,找比基准值小的元素,找到了就停下来
		while (begin < end && array[end] >= key)
			end--;

		if (begin < end)
		    Swap(&array[begin], &array[end]);
	}
	if (begin != right-1)
	    Swap(&array[begin], &array[right-1]);
	return begin;   
}
//如何进行区间的基准值分割?
//数据分割: 
//方法一:(hore)
//先取基准值为最后一个元素
//只要begin和end没有遇到或者没有错过(begin和end区间中有元素)
//begin从前往后移动,找比基准值大的元素,找到之后停止
//end从后往前找,找比基准值小的元素,找到之后停止 
//交换begin和end位置的元素--(交换时,是跨区间交换的,所以不稳定)
//当begin和end相遇时,交换最后一个元素和相遇位置的元素

//方法二:挖坑法
int Partion2(int array[], int left, int right)
{
	int begin = left;
	int end = right - 1;
	int keyofindex = GetMiddleIndex(array, left, right);
	int key;
	if (keyofindex != right - 1)
		Swap(&array[keyofindex], &array[right - 1]);

	key = array[right - 1];

	while (begin < end)
	{
		//end位置形成了一个新的坑
		//让begin从前往后找比基准值大的元素
		while (begin < end && array[begin] <= key)
			begin++;  
		 
		//让begin位置大的元素天end位置的坑
		if (begin < end)
		{
			array[end] = array[begin];
			end--;
		}

		//begin位置形成了一个新的坑
		//让end从后往前找,找比基准值小的元素,填begin位置的坑
		while (begin < end && array[end] >= key)
			end--;

		if (begin < end)
		{
			array[begin] = array[end];
			begin++;
		} 
	}
	//用基准值填最后一个坑
	array[begin] = key;
	return begin;
}

//方法三:前后指针法
//1.设置两个指针cur和prev,cur在前,prev在后
//2.当array[cur]<key&&++prev!=cur时,同时往后移动一个位置,否则cur移动一个位置
//3.当2满足时交换cur和prev
//4.最后交换基准值和Prev位置的值
//如果prev和cur紧挨着,说明cur还没有遇到比基准值大的元素
//如果prev和cur有差距,说明cur在遍历过程中,已经遇到比基准值大的元素了
//而且prev和cur之间的元素都是比基准值大的元素

int Partion3(int array[], int left, int right)
{
	int cur = left;
	int prev = cur - 1;

	int keyofindex = GetMiddleIndex(array, left, right);
	int key;
	if (keyofindex != right - 1)
	{
		Swap(&array[keyofindex], &array[right - 1]);
	}
	key = array[keyofindex];

	while (cur < right)
	{
		if (array[cur] < key && ++prev != cur)
			Swap(&array[cur], &array[prev]);
		++cur;
	}
	if (++prev != right -1)
		Swap(&array[right -1], &array[prev]);

	return prev;
}



//快速排序
//[left,right)表示待排序元素的区间
//void QuickSort(int array[], int left, int right)
//{
//	if (right - left > 1)
//	{
//		//Partion按照基准值(就是区间中的某个元素)对区间进行划分成两部分,左部分元素比基准值小,右部分比基准值大
//		//该函数返回基准值在区间中的位置
//		//[left,right)区间中的基准值位置已经存放好了,基准值的左侧和基准值的右侧不一定有序
//		int div = Partion3(array, left, right);
//	
//		//基准值左侧[left,div)
//		QuickSort(array, left, div);
//
//		//基准值右侧[div+1,right)
//		QuickSort(array, div + 1, right);
//	}
//}

//优化上述代码:
void QuickSort(int array[], int left, int right)
{
	if (right - left < 16)//为什么是16,经过大量实验表明16为插入排序的最合适数据量
	{
		//[left,right)区间中数据少到一定程度,使用插入排序来优化
		InsertSort(array + left, right - left);
	}
	else
	{
		//Partion按照基准值(就是区间中的某个元素)对区间进行划分成两部分,左部分元素比基准值小,右部分比基准值大
		//该函数返回基准值在区间中的位置
		//[left,right)区间中的基准值位置已经存放好了,基准值的左侧和基准值的右侧不一定有序
		int div = Partion3(array, left, right);

		//基准值左侧[left,div)
		QuickSort(array, left, div);

		//基准值右侧[div+1,right)
		QuickSort(array, div + 1, right);
	}
}

//快排原理:
//先按照基准值对数据来进行划分:(达到什么效果?)按照基准值将区间中的数据划分为两部分,一部分比基准值小,一部分比基准值大---》partion(用此来进行分割)
//返回基准值所在的位置div--》[left,div)div[div+1,right)
//使用快速排序递归对基准值的左侧进行排序--[left,div)
//使用快速排序递归对基准值的右侧进行排序--[div+1,right)
//快速排序递归框架与二叉树的前序遍历程序框架类似
//划分方法:按照基准值进行划分--》
//基准值获取方法:理论上区间中任意一个值都可以作为基准值,简单起见一般选择区间最左侧或者最右侧数据为基准值
//按照基准值进行划分,划分方法: partion123
//问题:为什么取基准值时,尽量靠近中间值
//以中间值作为基准值进行划分,划分好之后基准值左右两侧的数据基本上相等
//基准值选择越大或者越小,划分好之后基准值一侧数据非常多,一侧数据非常少
//极端情况:假设每次进行划分时,取的基准值都是区间中最大值或者区间中的最小值(最值) 
//按照基准值(最小值)进行划分:基准值左侧没有数据,数据全部集中到基准值的右侧
 //快速排序后图解退化成单支树--》时间复杂度:O(N^2)
//如果每次取基准值时,基准值时中间值,此时快速排序图解将为平衡二叉树
//最佳情况下时间复杂度:取决于树的高度log2^N--》元素个数--》Nlog2^N
//时间复杂度看的时最差情况,是否说明快排时间复杂度O(N^2)?
//一般情况下认为快排的时间复杂度为:Nlog2^N
//前提是尽量避免取到极值的概率--以前取极值的方式:取区间最右侧的值作为极值---按照该种方式取基准值拿到极值的概率非常高的
//最好避免该种情况  
//一般是采用三数取中法来避免的
//三数取中法:区间最左侧取一个数据,区间最右侧取一个数据,从区间最中间取一个数据,以三个数据中的中间值作为基准值
//按照三数取中法优化之后,在划分时取到极值的概率就降低了,因此说:快速排序平均时间复杂度为:Nlog2^N
//最终时间复杂度是按照平衡二叉树的图解来求的
//空间复杂度:递归函数空间复杂度求解方式--》每次递归的时间复杂度*递归的深度
//每次递归时并没有借助辅助中间--O(1)
//深度log2^N
//空间复杂度O(log2^N)
//问题:如果数据量特别大,递归的深度会非常深,递归深度达到一定程度会造成栈溢出
//原因:因为递归调用,最深层函数如果没有推出,上层的一些递归调用是不会结束的,而每次递归都是一次函数调用,每个函数调用都需要在栈上给函数划分一个栈帧(一段栈空间),函数递归调用深度越深,划分的栈空间就越多,而栈是有大小限制的,如果递归层次深到一定程度,栈空间不足时,就无法给该次递归函数划分栈空间导致递归失败,即栈溢出
//解决方法:
//1.让递归的深度不要那么深——随着我们在里面不断的进行划分,刚开始的数据非常大(就是有n个数据),递归图解中:越往下,每个分组中的数据量越少,(分组中只剩下一个数据时,就不会往下递归了),我们没有必要让区间中只有一个数据时再往回退,越往下数据量越少,当数据量少的时候,这样排序反而不好了(当数据量越少时,我们用插入排序效果会更好)
//2.不使用递归,使用循环----循环版本的快排
//将递归转换为循环--》有些算法可以直接转:比如Fac求阶乘--》因为问题就是一个迭代器问题
//有些算法不能直接转:借助stack实现
//为什么递归转循环可以使用栈:递归程序是后调用的先退出,与栈的特性完全符合 

//用栈来将递归转化为循环
void QuickSortNor(int array[], int size)
{
	int left = 0;//左边界 
	int right = size;//右边界
	Stack s; 
	StackInit(&s);//对栈进行初始化

	StackPush(&s, right);
	StackPush(&s, left);


	while (!StackEmpty(&s))
	{	
		//先按照基准值来进行划分
		left = StackTop(&s);
		StackPop(&s);

		right = StackTop(&s);
		StackPop(&s);

		if (right - left > 1)
		{
			int div = Partion2(array, left, right);
			
			//排基准值的左半侧--将右半部分的区间入栈[div+1, right)
			StackPush(&s, right);
			StackPush(&s, div+1);

			//排基准值的右半侧--将左半部分的区间入栈[left,div)
			StackPush(&s, div);
			StackPush(&s, left);
		}
	}
	StackDestroy(&s);
}
     
//思考:(帮助复习)
//1.快排的原理
//2.快排代码框架搭建
//3.按照基准值划分区间的三种方式:hore,挖坑法,前后指针法
//4.对快排最优和最差情况的分析
//5.采用三数取中法对快排进行优化
//6.采用插入排序对快排进行优化
//7.时间复杂度,空间复杂度,稳定性分析
//8.将递归转换为循环
//9.快排的应用场景:数据量大数据越随机越好
//快排是目前为止速度最快,堆排序与快排的时间复杂度皆为O(Nlog2^N),但是实际情况下快排比堆排序速度快
  
//归并排序:
//与快速排序类似的地方是:都需要对区间来进行划分
//不同点:快排是按照基准值对区间进行划分的,存在极端情况,归并排序是每次都是对数据进行均分的,不存在最差的情况
//原理:每次对区间中的数据进行均分,将左半部分归并排序好,再将右半部分归并排序好,将左右两半部分归并成一个有序的序列
//本质:先逐次对区间来进行均分,均分到区间中只有一个元素时然后再向上进行归并
//归并:将两个有序的区间归并成一个有序的区间---给两个有序的数组,将两个有序的数组合并成一个有序的数组

//问题:4325,我们先将其分为4 3 2 5 四个数据,合并时34 25  ,再次合并时为什么可以直接将其合并为2345(按照什么原理?)
//答:此时我们有两个有序的数组,借助一段辅助空间,在辅助空间中,两个数组依次比较,然后插入数组
 
//注意:
//快速排序中我们会寻找基准值,然后将数据分为两部分,此时我们对这两部分的称呼应该为左侧,右侧,(因为基准值为随机数,不一定为数据的中间值,从而左右两侧的数据量不同)而归并排序中我们将数据进行均分成两部分,我们将这两部分称为左半侧和右半侧,(归并排序中,我们是将数据均分,所有我们可以将之称为左半侧,右半侧)

//归并排序
//将两个分组中的数据归并成一个
//时间复杂度:O(N)
//稳定性:稳定的
void MergeData(int array[], int left, int mid, int right,int temp[])
{
	int index1 = left, index2 = mid, index = left;
	while (index1 < mid && index2 < right)
	{
		if (array[index1] <= array[index2])
			temp[index++] = array[index1++];
		else
			temp[index++] = array[index2++];
	}
	//此时我们不清楚是index1中的数据还是index2中的数据被搬运完

	//[left,mid)区间中的数据还没有搬移完
	while(index1 < mid)
	{
		temp[index++] = array[index1++];
	}

	//[left,right)区间中的数据还没有搬移完
	while (index2 < right)
	{
		temp[index++] = array[index2++];

	}

}

void _MergeSort(int array[], int left, int right, int temp[])
{
	if (right - left >1)
	{
		int mid = left + ((right - left) >> 1);

		//[left,mid)
		_MergeSort(array, left, mid, temp);

		//[mid,right)
		_MergeSort(array, mid, right, temp);

		//将[left,mid)和[mid,right)有序区间进行归并
		MergeData(array, left, mid, right, temp);

		//归并好之后,有序的数据在temp,将temp中有序的结果拷贝到原数组中
		memcpy(array + left, temp + left, (right - left)*sizeof(array[left]));
	}
}

//为什么写_MergeSort?
//用户在调用MergeSort时,只需要传递两个参数(类比以上所有方法,都是传递两个参数)
//而_MergeSort需要四个参数
//防止出现问题
//由此得出:有些数据不需要别人给出,我们可以根据已有数据来进行推算得出我们需要的数据时,我们尽量自己推算,防止出现问题	 
void MergeSort(int array[], int size)
{
	int* temp = (int*)malloc(size*sizeof(array[0]));
	if (NULL == temp)
		return;

	_MergeSort(array, 0, size, temp);

	free(temp);
}
//快排中时间复杂度会存在最优和最差的情况,归并排序没有最优和最差的场景--》因为:每次划分都是将区间进行均分
//递归函数时间复杂度:递归总的次数*每次递归耗费的时间
//因为归并排序每次都是对区间进行均分,因此数据在分解的时,肯定是平衡二叉树
//高度:log2^N
//时间复杂度:O(Nlog2^N)
//空间复杂度:O(N)

//将递归转换为循环  
void MergeSortNor(int array[], int size)
{
	int gap = 1;
	int* temp = (int*)malloc(size*array[0]);
	if (NULL == temp)
		return;

	while (gap < size)
	{
		for (int i = 0; i < size;i += 2*gap)
		{
			int left = i;
			int mid = left + gap;
			int right = mid + gap;

			if (mid > size)
				mid = size;

			if (right > size)
				right = size;

			//[left,mid)和[mid,right)每个分组中右gap
			MergeData(array, left, mid, right, temp);
		}

		memcpy(array, temp, size*sizeof(array[0]));
		gap *= 2;
	}
	free(temp);
}


//插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序--》内部排序:在排序时需要一次性将所有数据加载到内存中
//归并排序:属于外部排序——》在排序时不需要一次性将所有数据加载到内存中
//统称为比较排序,你只要进行排序,元素与元素之间就需要进行比较



//非比较排序,不需要进行元素之间比较,也可以将数据排列好
//假设:数据密集集中在0~9范围中间 
//此时我们创造一段数组,数组的下标就是有序的,而且元素和数组的下标可以对应起来
//设原数组为array,我们创造的数组为Count
//方法:
//1.统计原数组中每个元素出现的次数----Count[array[i]]++
//2.按照统计的次数结果对数据进行回收
//自我理解:
//随便给出一段数子789456123123456789456000
//我们创造个数组(十个单位)
//上面数字中1出现了两次,我们在数组第二个位置写下2,以此类推,我们的到一段数组3222333222
//然后我们在解释我们创造的这个数组即:000112233444555666778899   这个就是我们所求的排列最后顺序

//非比较类排序
//1.计数排序
//场景:数据密集集中在某个范围中 
// 计数排序---鸽巢原理
// 时间复杂度:O(N)  N: 表示元素的个数
// 空间复杂度:O(M)  M:表示数据范围中包含数据的种类的个数
// 稳定性: 稳定
// 场景:数据密集集中在某个范围中
void CountSort(int array[], int size)
{
	// 1.找数据范围--->O(N)
	int minValue = array[0];
	int maxValue = array[0];
	for (int i = 1; i < size; ++i)
	{
		if (array[i] > maxValue)
			maxValue = array[i];

		if (array[i] < minValue)
			minValue = array[i];
	}

	// 2.计算用来计数的空间个数:maxValue-minValue+1, 申请用来保存计数的空间
	int range = maxValue - minValue + 1;
	int* temp = (int*)malloc(range*sizeof(int));
	memset(temp, 0, sizeof(int)*range);

	// 3. 统计区间中每个元素出现的个数--->O(N)
	for (int i = 0; i < size; ++i)
		temp[array[i] - minValue]++;

	// 4. 回收: 按照计数数组下标从小到大进行回收--->O(N)
	int index = 0;
	for (int i = 0; i < range; ++i)
	{
		while (temp[i]--)
			array[index++] = i + minValue;
	}

	free(temp);
}

//2.基数排序--桶排序--多关键码排序
//a.高关键码游优先:MSD
//b.低关键码优先:LSD

//多关键码排序
//扑克牌排序:数字+花色信息(♥,♣,♠,♦)

//我们随便给出一些数据,(123 654 789 456 123 789)
//我们根据它的个十百位来进行比较
//个--十--百:低关键码优先LSD
//百--十--个:高关键码优先MSD
//该排序不需要元素之间相互比较--将数据划分为:百十个
//发现:每个位置上的数据都是0~9之间的数字
//因此:给出十个桶,每个桶编号0~9(桶:实际就是一段内存空间)

//LSD:需要用到循环
//for ()//取个十百位置上的数字
//{
	//1.获取集合中的每个数据,将该数据按照当前位上的数字放到对应桶中
	//例如:个位上的数字是多少,就将数组放到对应的桶中

	//2.对每个桶中的数据进行回收
	//桶按照桶的编号从小到大回收
	//每个桶按照先放入的数据先回收
//}
//基数排序:
//稳定性:稳定的
//时间复杂度:O(MN)--》M:表示最大数据的位数  N:表示数据个数
//空间复杂度:O(N)

//MSD:用不了循环(排列完百位后,排列十位上的数字时,会错乱)
//解决方法:排列完百位后,直接对桶中的数据进行桶内进行是十位上的数据进行桶排序,然后再对十位上的桶进行排序,最后进行整体回收

//
void TestSort()
{
	int array[] = { 4, 1, 7, 6, 3, 9, 5, 8, 0, 2 };
	PrintArray (array, sizeof(array) / sizeof(array[0]));
	//InsertSort(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	/*ShellSort(array, sizeof(array) / sizeof(array[0]));
	PrintArray(array, sizeof(array) / sizeof(array[0]));*/
    
	//SelectSort(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	//SelectSortOP(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	//HeapSort(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	//BubbleSort(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	//BubbleSortOP(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));
	
	//QuickSort(array, 0, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	//QuickSortNor(array, sizeof(array) / sizeof(array[0]));
    //PrintArray(array, sizeof(array) / sizeof(array[0]));

	//MergeSort(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	//MergeSortNor(array, sizeof(array) / sizeof(array[0]));
	//PrintArray(array, sizeof(array) / sizeof(array[0]));

	CountSort(array, sizeof(array) / sizeof(array[0]));
	PrintArray(array, sizeof(array) / sizeof(array[0]));
}


//排序的概念:
//排序的稳定性:算法是否稳定,参考:在排序过程中是否隔着区间或者隔着元素来进行交换或者插入
//内部排序和外部排序:目前我们所学的排序中除了归并排序是外部排序,其余都是内部排序
//在排序中需要将所有数据一次性全部加载到内存中,这些都是内部排序,反之则为外部排序
//对与所讲过的排序算法:
//1.原理  2.代码实现  3.时间复杂度  4.空间复杂度  5.稳定性  6.应用场景
//排序的重要性:在正常工作中,我们处理一些实际问题时,排序算法一般不需要我们去写,(对于c语言来说,处理排序算法时,我们需要去写相关代码)其他类型的算法,他们的库函数比较多,我们可以直接应用
//在面试期间,面试官非常喜欢用排序来考察我们的代码功底以及逻辑思维能力----快速排序,堆排序,冒泡排序 

stack.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once//防止重复包含

typedef int SDataType;

/*
静态栈
#define MAXSIZE 100
typedef struct Stack
{
SDataType array[MAXSIZE];
int top;//标记栈顶
}stack;
如果在笔试时需要用到栈,自己可以快速定义一个静态栈,不用考虑扩容
*/
typedef struct Stack
{
	SDataType* array;
	int capacity;//容量
	int size;//栈中元素的个数   栈顶
}Stack;

//注意:
//1.栈中没有任意位置的插入和删除操作  
//2.栈不需要查找--遍历

//初始化
void StackInit(Stack* ps);

//入栈:尾插
void StackPush(Stack* ps, SDataType data);

//出栈:尾删
void StackPop(Stack* ps);

//获取栈顶的元素
SDataType StackTop(Stack* ps);

//获取栈中有效元素的个数
int StackSize(Stack* ps);

//检测栈是否为空
int StackEmpty(Stack* ps);

//销毁
void StackDestroy(Stack* ps);


stack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "stack.h"

#include <malloc.h>
#include <assert.h>
#include <stdio.h>
#include <string.h> 

//初始化
void StackInit(Stack* ps)
{
	assert(ps);
	ps->array = (SDataType*)malloc(sizeof(SDataType)* 10);
	if (NULL == ps->array)
	{
		assert(0);
		return;
	}

	ps->capacity = 10;
	ps->size = 0;
}

//扩容
void CheckCapacity(Stack* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		//1.开辟新空间
		SDataType* temp = (SDataType*)malloc(sizeof(SDataType)*ps->size * 2);
		if (temp == NULL)
		{
			assert(0);
			return;
		}
		//2.拷贝元素
		memcpy(temp, ps->array, sizeof(SDataType)*ps->size);
		//3.释放就空间
		free(ps->array);
		//4.使用新空间
		ps->array = temp;
		ps->capacity *= 2;
	}
}


//入栈:尾插
void StackPush(Stack* ps, SDataType data)
{
	assert(ps);
	CheckCapacity(ps);
	ps->array[ps->size++] = data;
}

//出栈:尾删
void StackPop(Stack* ps)
{
	if (StackEmpty(ps))
		return;

	ps->size--;
}

//获取栈顶的元素
SDataType StackTop(Stack* ps)
{
	assert(!StackEmpty(ps));
	return ps->array[ps->size - 1];
}

//获取栈中有效元素的个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->size;
}

//检测栈是否为空
int StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->size == 0;
}

//销毁
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->array);
	ps->array = NULL;
	ps->capacity = 0;
	ps->size = 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值