【C语言数据结构】排序

news2024/9/26 5:18:31

1.排序的概念

在深入研究各个排序算法之前,首先,我们要对排序有个大概的了解,即与排序相关的一些概念

Q:什么是排序?

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

Q:什么是内部排序与外部排序?

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

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

Q:稳定性是什么?

A:稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变

即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的

那关于排序的应用,这可太多了,可以说我们每天都在跟它打交道,这里就不展开了,接下来我们就来看看各个排序算法吧~

2.常见的排序算法的实现

a.插入排序

1.直接插入排序

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

 

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


直接插入排序代码实现

void PrintArray(int* a, int n)//用来辅助打印的函数 
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}


void InsertSort(int* a, int n)  //直接插入排序
{
	for (int i = 0; i <= n - 2; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

void TestInsertSort()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	InsertSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));//打印
}


int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestInsertSort();
	return 0;
}

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

 

2.希尔排序

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

希尔排序代码实现

void PrintArray(int* a, int n)//用来辅助打印的函数 
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}


void ShellSort(int* a, int n)//希尔排序
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}

	}
}

void TestShellSort()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	ShellSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));//打印
}


int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestShellSort();
	return 0;
}

希尔排序的特性总结:
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定

4.稳定性:不稳定
 

b.选择排序

1.直接选择排序

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

下面讲解一下这种排序的步骤:

1.在元素集合a[i] 到 a[n-1]中选择关键码最小的数据元素

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

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

直接选择排序代码实现

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

void PrintArray(int* a, int n)//用来辅助打印的函数 
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}


void SelectSort(int* a, int n)//直接选择排序
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		//  这里进行优化 ,每次都选出最小值和最大值两个值的下标而不是只选出最小值
		int mini = begin, maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}

			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}

		Swap(&a[begin], &a[mini]);

		if (maxi == begin)//重叠问题
		{
			maxi = mini;
		}

		Swap(&a[end], &a[maxi]);

		begin++;
		end--;
	}
}

void TestSelectSort()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	SelectSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));//打印
}


int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestSelectSort();
	return 0;
}

直接选择排序的特性总结:
1. 直接选择排序思考非常好理解,但是效率不是很好,所以实际中很少使用
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

 

2.堆排序

关于什么是堆:堆是一颗完全二叉树,这里的具体知识要结合二叉树部分进行讲解,就不是这篇博客的重点了,所以建议学完二叉树的小伙伴再来观看关于堆排序的内容哦~

堆排序是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。


 

下面是堆排序的具体步骤

1.建堆,需要注意的是排升序要建大堆,排降序建小堆

2.将最后一个数与堆顶进行交换,然后将除最后一个数据之外的所有数据重新向下调整

3.重复第2步,直至完全升序

堆排序代码实现

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

void PrintArray(int* a, int n)//用来辅助打印的函数 
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}


//向下调整 升序要建大堆
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 假设法,选出左右孩子中大的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}


void HeapSort(int* a, int n)
{
	// a数组直接建堆 O(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 TestHeapSort()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	HeapSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));//打印
}


int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestHeapSort();
	return 0;
}

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

 

c.交换排序

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

1.冒泡排序

冒泡排序,这是排序中我们最早接触的算法,这个排序的取名很形象,所以思想也很简单

冒泡排序代码实现

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

void PrintArray(int* a, int n)//用来辅助打印的函数 
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}


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

		if (flag == 0)
			break;
	}
}


void TestBubbleSort()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	BubbleSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));//打印
}


int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestBubbleSort();
	return 0;
}

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

 

2.快速排序

快速排序是Hoare大佬提出的一种二叉树结构的交换排序方法,其基本思想为:

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

快速排序是排序里的一块重点内容,也是面试容易考察的一种排序,所以下面我们重点来讲解一下这个排序~
 

版本一:hoare版本

代码实现如下:y

void QuickSort1(int* a, int left, int right)//快排hoare版本
{
	if (left >= right)// 区间只有一个值或者不存在就是最小子问题
		return;

 
	 int begin = left, end = right;
	 int keyi = left;
	 while (left < right)
	 {
		// right先走,找小
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}

		// left再走,找大
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}

		Swap(&a[left], &a[right]);
	 }

	 //出循环的时候left = right,相遇
	 Swap(&a[left], &a[keyi]);
	 keyi = left;//keyi来到相遇点

	 // [begin, keyi-1]  keyi   [keyi+1, end] 左右进行递归
	 QuickSort1(a, begin, keyi - 1);
	 QuickSort1(a, keyi + 1, end);
}


void TestQuickSort1()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	QuickSort1(a, 0, sizeof(a) / sizeof(int) - 1);

	PrintArray(a, sizeof(a) / sizeof(int));
}


int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestQuickSort1();

	return 0;
}

对于hoare版本,我们有两点可以优化,分别是

1.选key的优化

2.小区间优化

先说第一个,选key的优化

我们可以采用三数取中法

所谓三数取中法,就是我们在区间的第一个,中间位置,和最后一个位置中选出中间值作为key,下面来写一下

int GetMidi(int* a, int left, int right)// 三数取中优化  left  mid  right
{
	int mid = (left + right) / 2;

	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}

	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

然后我们可以把这个函数嵌入之前的快排程序了~

void QuickSort1(int* a, int left, int right)//快排hoare版本优化1.0
{
	if (left >= right)// 区间只有一个值或者不存在就是最小子问题
		return;

    
	 int begin = left, end = right;

	 // 三数取中
	 int midi = GetMidi(a, left, right);
	 Swap(&a[left], &a[midi]);

	 int keyi = left;
	 while (left < right)
	 {...//为了节约空间,这里就不重复写了~}

	 //出循环的时候left = right,相遇
	 Swap(&a[left], &a[keyi]);
	 keyi = left;//keyi来到相遇点

	 // [begin, keyi-1]  keyi   [keyi+1, end] 左右进行递归
	 QuickSort1(a, begin, keyi - 1);
	 QuickSort1(a, keyi + 1, end);
}

再说一下第二个,小区间优化

因为递归要开辟栈帧,越到后面消耗越大,所以当递归到小的子区间时,我们可以使用插入排序~

插入排序的代码上面写过了,我们之间拿来用就行~,下面是优化后的代码

void QuickSort1(int* a, int left, int right)//快排hoare版本优化2.0
{
	if (left >= right)// 区间只有一个值或者不存在就是最小子问题
		return;

    
	if (right - left + 1 < 10)
    // 小区间选择走插入,可以减少90%左右的递归,一般是区间长度小于10比较合理
	{
		InsertSort(a + left, right - left + 1);
	}

	else
	{
	 int begin = left, end = right;

	 // 三数取中
	 int midi = GetMidi(a, left, right);
	 Swap(&a[left], &a[midi]);

	 int keyi = left;
	 while (left < right)
	 {
		...//同样为了省空间。这里就不写了
	 }

	 //出循环的时候left = right,相遇
	 Swap(&a[left], &a[keyi]);
	 keyi = left;//keyi来到相遇点

	 // [begin, keyi-1]  keyi   [keyi+1, end] 左右进行递归
	 QuickSort1(a, begin, keyi - 1);
	 QuickSort1(a, keyi + 1, end);
   }
}

好,说完了hoare版本以及优化,

下面我们看一下快速排序的第二个版本:挖坑法

其实挖坑法的整体思路与hoare法类似,博主倾向于认为它是hoare版本的一个教学优化版

下面是挖坑法的过程图:

下面是代码:(注意,接下来的几个版本其实都是可以用上面的三数取中和小区间优化,这里博主就不再多写了,毕竟是一样的东西)

void QuickSort2(int* a, int left, int right)//快排挖坑法,其实是hoare版本的教学优化版
{
	if (left >= right)
		return;

	int begin = left, end = 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;

	QuickSort2(a, begin, hole - 1);
	QuickSort2(a, hole + 1, end);
}

void TestQuickSort2()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	QuickSort2(a, 0, sizeof(a) / sizeof(int) - 1);

	PrintArray(a, sizeof(a) / sizeof(int));
}



int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	
	TestQuickSort2();
	

	return 0;
}

版本三:前后指针法

这种方法博主认为理解后是这些快排里代码最好写的,如果面试时要求手写一个快排代码,用前后指针法绝对是一个不二的选择!

下面是前后指针法的图示过程

下面是代码:(同样,这里的优化也不加了)

void QuickSort3(int* a, int left, int right)// 前后指针法
{
	if (left >= right)
		return;
	
	int keyi = left;
	int prev = left;
	int cur = left + 1;

	while (cur <= right)
	{
		if (a[cur] < a[keyi])  //cur < key , prev++ , 交换prev与cur的位置
		{ 
			prev++;
			Swap(&a[prev], &a[cur]);
		}

		cur++;
	}

	Swap(&a[keyi], &a[prev]);
	keyi = prev;//将key的下标换到prev

	// [left, keyi-1] keyi [keyi+1, right]	左右进行递归
	QuickSort3(a, left, keyi - 1);
	QuickSort3(a, keyi + 1, right);
}

void TestQuickSort3()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	QuickSort3(a, 0, sizeof(a) / sizeof(int) - 1);

	PrintArray(a, sizeof(a) / sizeof(int));
}

int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 

	TestQuickSort3();

	return 0;
}

最后一种:  非递归实现

将快排本来要用的递归变成非递归,这也是面试是喜欢考的东西

这里我们需要用栈的数据结构,因为本来递归用的是栈帧,而非递归用的是数据结构的栈

因为在C语言阶段,我们也只能自己手写,到了C++阶段就有现成的可以使用咯,这里我们CV一下(doge)

非递归的步骤是:    取栈顶区间,单趟排序,右左子区间入栈

#include"Stack.h"

void QuickSortNonR(int* a, int left, int right)//快排非递归
{
	ST st;
	STInit(&st);
    // 取栈顶区间,保持先入右,再入左
	STPush(&st, right);
	STPush(&st, left);

	while (!STEmpty(&st))//栈不为空,还有区间要处理,就继续
	{
		int begin = STTop(&st);
		STPop(&st);

		int end = STTop(&st);
		STPop(&st);

		// 单趟排序
		int keyi = begin;
		int prev = begin;
		int cur = begin + 1;

		while (cur <= end)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
				Swap(&a[prev], &a[cur]);

			cur++;
		}

		Swap(&a[keyi], &a[prev]);
		keyi = prev;

		// [begin, keyi-1] keyi [keyi+1, end] 
         //右左子区间入栈
		if (keyi + 1 < end)
		{  
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}

		if (begin < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}

	STDestroy(&st);
}

void TestQuickSortNonR()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	QuickSortNonR(a, 0, sizeof(a) / sizeof(int) - 1);

	PrintArray(a, sizeof(a) / sizeof(int));

}

int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestQuickSortNonR();

	return 0;
}

好了,至此,我们的快速排序的各个方法也就讲完啦,下面小小的进行一个总结

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

 

讲完了快速排序,再来看一下归并排序吧~

d.归并排序

归并排序是建立在归并操作上的一种有效的排序算法,它也是采用分治法的一个非常典型的应用

将已有序的子序列合并,得到完全有序的序列 ,即先使每个子序列有序,再使子序列段间有序

归并排序核心步骤:
 

下面是归并排序的动态图示过程:

归并排序的实现可分为递归与非递归两种类型,我们先看一下递归的实现

//归并排序子函数
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin == end)
		return;

	int mid = (begin + end) / 2;
	// [begin, mid] [mid+1, end]
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	// 下面是归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	// 依次比较,取小的尾插到tmp数组
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

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

	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

//归并排序 
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

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

	free(tmp);
	tmp = NULL;
}

void TestMergeSort()
{

	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	MergeSortNonR(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}

int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 

	TestMergeSort();


	return 0;
}


其实这里用递归的方式写还是比较轻松的,毕竟我们之前学了二叉树,真正难的是下面的非递归

这里的非递归与之前的快速排序不同,我们如果用一个栈就不太好操作了

于是我们想着用一个变量gap来归并每组的数据个数,让gap从1到2到4一直增长直到这个数据排到有序为止

我们可能会写出下面的代码:


void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;//gap是每组的数据个数

	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			int i = j;

			// 依次比较,取小的尾插到tmp数组
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[i++] = a[begin1++];
				}
				else
				{
					tmp[i++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[i++] = a[begin1++];
			}

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

			memcpy(a + j, tmp + j, sizeof(int) * (2*gap));
		}

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

我们测试上面的代码,会得到这样的结果:

这里其实说明我们的数组越界了,这是怎么回事呢?

我们看一下下面这个数组,当gap = 2 的时候,它要怎么归并呢?

这里是不是就出现了越界,所以为了阻止这个问题,我们需要增加一处地方以及修改一处地方

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		return;
	}
    int gap = 1;//gap是每组的数据个数
    while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			int i = j;

			// 这里增加了对越界的问题处理
			if (end1 >= n || begin2 >= n)
				break;

			if (end2 >= n)
				end2 = n - 1;

			while (begin1 <= end1 && begin2 <= end2)
			{
				.....
			}

			while (begin1 <= end1)
			{
				...
			}

			while (begin2 <= end2)
			{
				...
			}

            //这里将拷贝的个数从2*gap修正为 end2 - j + 1
			memcpy(a + j, tmp + j, sizeof(int) * (end2 - j + 1));
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

在上面的代码中我们增加了对越界问题的处理以及将memcpy拷贝的个数这里进行了修改,下面我们在test.c文件测试一下吧

void TestMergeSortNonR()
{

	int a[] = { 6,1,2,7,9,3,4,5,10,8 };

	MergeSortNonR(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));

}

int main()
{
	// 原数组为 6,1,2,7,9,3,4,5,10,8
	
	TestMergeSortNonR();


	return 0;
}

Perfect!

最后我们看一下非比较排序吧~

e.非比较排序

之前我们所有的排序都属于 比较排序,它们通过比较大小来排序,

下面要介绍的是非比较排序,它的排序核心用的不是比较

非比较排序主要有:计数排序,基数排序和桶排序

不过这里要说的是,非比较排序都是小众的,局限的,所以下面我就说一种计数排序来给大家感受一下,它也是这三种里面应用性最高的

计数排序的操作步骤如下:

1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中

 

计数排序的代码如下:(yysy这个代码与之前几个相比就简单很多了)

void CountSort(int* a, int n)
{
	int min = a[0], max = 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);
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}

	memset(count, 0, sizeof(int) * range);//将range个0赋给count数组,完成初始化

	// 统计次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	// 排序
	int i = 0;
	int j = 0;
	for (i = 0;i<range;i++)
	{
		while (count[i])
		{
			count[i]--;
			a[j++] = i + min;
		}
	}

	free(count);
	count = NULL;
}



void TestCountSort()
{
	
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };

	CountSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}




int main()
{
	// 原数组为5, 3, 9, 6, 2, 4, 7, 1, 8 
	TestCountSort();

	return 0;
}

有这个算法思想我们可以看出,计数排序适合的是对数据的范围集中的数据进行排序,但如果数据比较分散就不太适合了,空间浪费有点大

另外,它只能排整型,如果是什么浮点型,字符型甚至结构体类型,它都不是不能用的,这也是它的局限性

好了,至此,排序的实现我们就都完成了,鼓掌鼓掌!

最后,我们把先前在开头的图拿过来,在我们学完所有的排序之后再看一遍一定会有新的感知

结语

好啦,到此为止,今天的排序内容就结束啦,如果文中分析,题解代码有不足的地方欢迎大家在评论区讨论和指正

让我们在接下来的时间里一起学习,一起进步吧~

c0fe1378f4b1464abb37998a472b5961.jpg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1542032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java中的SSM框架实现考研指导平台系统项目【项目源码+论文说明】

基于Java中的SSM框架实现考研指导平台系统演示 摘要 应对考研的学生&#xff0c;为了更好的使校园考研有一个更好的环境好好的学习&#xff0c;建议一个好的校园网站&#xff0c;是非常有必要的。提供学生的学习提供一个交流的空间。帮助同学们在学习高数、学习设计、学习统计…

使能 Linux 内核自带的 FlexCAN 驱动

一. 简介 前面一篇文章学习了 ALPHA开发板修改CAN的设备树节点信息&#xff0c;并加载测试过设备树文件&#xff0c;文件如下&#xff1a; ALPHA开发板修改CAN的设备树节点信息-CSDN博客 本文是学习使能 IMX6ULL的 CAN驱动&#xff0c;也就是通过内核配置来实现。 二. 使能…

Spring Cloud五:Spring Cloud与持续集成/持续部署(CI/CD)

Spring Cloud一&#xff1a;Spring Cloud 简介 Spring Cloud二&#xff1a;核心组件解析 Spring Cloud三&#xff1a;API网关深入探索与实战应用 Spring Cloud四&#xff1a;微服务治理与安全 文章目录 一、Spring Cloud在CI/CD中的角色1. 服务注册与发现&#xff1a;自动化管理…

YOLOV5 部署:TensorRT的安装和使用

1、介绍 TensorRT 可以加速神经网络的推理时间,常常在工业生产中使用 因为TensorRT需要使用到cuda和cudnn加速,所以需要安装这两个,安装的具体步骤参考前文: YOLOV5 部署:cuda和cuDNN安装-CSDN博客 2、TensorRT 下载 TensorRT下载地址:NVIDIA TensorRT Download | NV…

分类预测 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积神经网络-长短期记忆网络融合多头注意力机制多特征分类预测

分类预测 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积神经网络-长短期记忆网络融合多头注意力机制多特征分类预测 目录 分类预测 | Matlab实现CNN-LSTM-Mutilhead-Attention卷积神经网络-长短期记忆网络融合多头注意力机制多特征分类预测分类效果基本介绍模型描述程序设计参…

初识kafka-数据存储篇1

目录 背景 1 kafka总体体系结构 2 疑问解答 2.1 高吞吐低延迟 2.2 实现分布式存储和数据读取 2.3 如何保证数据不丢失 背景 最近在和产品过项目审批的时候&#xff0c;深刻感受到业务方对系统的时时响应提出了更高的要求。目前手上大部分的业务都是基础定时任务去实现的&…

[Java基础揉碎]单例模式

目录 什么是设计模式 什么是单例模式 饿汉式与懒汉式 饿汉式vs懒汉式 懒汉式存在线程安全问题 什么是设计模式 1.静态方法和属性的经典使用 2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。设计模式就像是经典的棋谱&am…

使用 RunwayML 对图像进行 Camera 操作

RunwayML 是一個功能強大的平台&#xff0c;可以讓您使用 AI 和机器学习来增强您的图像和视频。 它提供一系列预训练模型&#xff0c;可用于各种任务&#xff0c;包括图像编辑、风格化和特效。 在本文中&#xff0c;我们将介绍如何使用 RunwayML 对图像进行 Camera 操作。我们…

游戏引擎中的地形系统

一、地形的几何 1.1 高度图 记录不同定点的高度&#xff0c;对每个网格/顶点应用高度、材质等信息&#xff0c;我们每个顶点可以根据高度改变位移 但是这种方法是不适用于开放世界的。很难直接画出几百万公里的场景 1.2 自适应网格细分 当fov越来越窄的时候&#xff0c;网格…

Stable diffusion(四)

训练自己的Lora 【DataSet】【Lora trainer】【SD Lora trainer】 前置的知识 batch size&#xff1a;模型一次性处理几张图片。一次性多处理图片&#xff0c;模型能够综合捕捉多张图片的特征&#xff0c;最终的成品效果可能会好。但是处理多个batch size也意味着更大的显存…

第一篇:概述、 目录、适用范围及术语 --- IAB/MRC《增强现实(AR)广告(效果)测量指南1.0 》

第一篇&#xff1a;概述、目录、适用范围及术语 - IAB与MRC及《增强现实广告效果测量指南1.0》 --- 我为什么要翻译美国IAB科技公司系列标准 ​​​​​​​​​​​​​​ 翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇广告效…

【ArcGIS】统计土地利用数据每类面积及占比

1.栅格转面 转换工具/从栅格/栅格转面&#xff0c;得到面.shp 不要勾选简化面&#xff0c;否则会影响面积 2.添加面积字段&#xff0c;计算面积 面.shp添加双精度字段area 查看面.shp属性表中有没有gridcode为0的&#xff0c;如有的话编辑矢量&#xff0c;删除该要素。 计…

STM32学习笔记(5_2)- EXTI外部中断代码

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 本期介…

nodejs+vue高校奖助学金系统python-flask-django-php

高校奖助学金系统的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&#xff0c;…

Python Flask 自定义404错误

from flask import Flask, abort, make_response, request, render_templateapp Flask(__name__)# 重定向到百度 app.route(/index, methods["GET", "POST"]) def index():if request.method "GET":return render_template("index.html&q…

tabs自定义样式

使用el-tabs 去修改样式的话比较麻烦&#xff0c;索性直接用div来制作。 <div class"contain"><div class"tab_wrap"><div :class"[skew, first, active 1 ? isActive: ]" click"tabClick(1)"><span class&quo…

系统大屏可视化展示平台解决方案(原件)

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.7.系统权限设计 3.8.数据查询过程设…

从零开始的 dbt 入门教程 (dbt cloud 自动化篇)

一、引 在前面的几篇文章中&#xff0c;我们从 dbt core 聊到了 dbt 项目工程化&#xff0c;我相信前几篇文章足够各位数据开发师从零快速入门 dbt 开发&#xff0c;那么到现在我们更迫切需要解决的是如何让数据更新做到定时化&#xff0c;毕竟作为开发我们肯定没有经历每天定…

C++(类和对象)2

36 友元 1&#xff09;全局函数 全局函数做优元&#xff0c;就是把全局函数复制到类中&#xff0c;加个friend 同上&#xff0c;将class GoodGay前写个friend&#xff0c;就可以访问了 当然&#xff0c;还有成员函数做友元 39 运算符重载-加号 普通加号只知道两个整型撒的…

nodejs+vue高校社团管理系统设计与实现python-flask-django-php

系统根据现有的管理模块进行开发和扩展&#xff0c;采用面向对象的开发的思想和结构化的开发方法对高校社团的现状进行系统调查。采用结构化的分析设计&#xff0c;该方法要求结合一定的图表&#xff0c;在模块化的基础上进行系统的开发工作。在设计中采用“自下而上”的思想&a…