7大排序的亿点细节(1)

 或许有的排序算法在生活中并不常用,但是学习他们往往会给我们日后的学习中提供思路。学习排序算法重要的不只是他们的思想,还有那些容易出问题的细节。掌握这些细节也就迈出了由 我们脑海中的算法思想 到 代码实现的重要一步。

目录

 1.准备工作

2.插入排序:

3.希尔排序:

4.选择排序:

5.堆排序:

6.冒泡排序:


 

 1.准备工作

Sort.h:

#include <stdio.h>
#include <stdlib.h>

//以升序为例

//1.插入排序
void InsertSort(int* a, int n);
//2.希尔排序
void ShellSort(int* a, int n);
//3.选择排序
void SelectSort(int* a, int n);
//4.堆排序
void HeapSort(int* a, int n);
//5.冒泡排序
void BubbleSort(int* a, int n);
//6.快速排序
void QuickSort(int* a ,int begin, int end);

void Print(int* a, int n);

void Sweap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
void Print(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

2.插入排序:

//插入排序
//直接排序
void InsertSort(int* a, int n)
{
	//i这里是小于n-1,因为如果i<n,a[end+1]就会出现越界访问
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			//tmp小于a[end],那么a[end]往后挪一位覆盖掉end+1的位置。tmp在和end前一个位置比较
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			//直到tmp大于了a[end]就跳出了
			else
			{
				break;
			}
			//这里要注意不要写成下面注释的代码 
			//如:{100,2,....}这种情况,应该派成:{2,100,...}
			//但是end=0的时候发生if语句的操作之后,end = -1,while循环外面如果没有a[end + 1] = tmp;那么第一个位置就永远是100,2被删除了
/*			else
			{
				a[end + 1] = tmp;
				break;
			}*/	
		}
		a[end + 1] = tmp;
	}
}

3.希尔排序:

//希尔排序
//1.预排序 gap>1
//2.直接插入排序  gap==1
//gap越小越接近有序
//gap越大大的更快的到后面,小的更快的到前面,越不接近有序
//缺点:如果数据本身有序那么预排序无用,但是预排序本身很快。
void ShellSort(int* a, int n)
{
	//值得注意的是gap不能赋固定的值,如果gap=5,n=10万,那么效率会很低
	int gap = n ; 
	while (gap > 1)
	{
		//初始gap可以是 n / 3 + 1,也可以是n / 2 ;//+1是为了让gap=1便于进行直接插入排序
		gap = gap / 3 + 1;
		//循环条件是n-gap,要保证end+gap再数组范围内,可以参考InsertSort中的条件判断
		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;
		}
	}
}

4.选择排序:

//选择排序  (优化后的)
void SelectSort(int* a, int n)
{
	int left = 0, right = n - 1;
	while (left < right)
	{
		int maxi = left, mini = left;
		//每次从i=left开始比,而不是i=0
		for (int i = left; i <= right; i++)
		{
			if (a[i] > a[maxi])
				maxi = i;
			if (a[i] < a[mini])
				mini = i;
		}
		Sweap(&a[left], &a[mini]);
		//如果a[left]是最大的那么一交换就把a[maxi]掉包了
		if (left == maxi)
		{
			maxi = mini;
		}
		Sweap(&a[right], &a[maxi]);
		left++, right--;
	}
}

5.堆排序:

//堆排序 
// 升序建大堆,降序建小堆
// 1.建大堆;2.排序
//第一次建的大堆是从倒数第一个双亲结点开始每个双亲结点都要建一次每次都到最后一个元素停止;
//第二次是每次都要从下标为0的位置建大堆到排好的元素前为止
void HeapSort(int* a, int n)
{
	//建大堆,
	
	//一共n个数,最后一个孩子的下标是n-1,所以最后一个双亲的下标是 : (最后一个孩子的下标-1)/2   :(n-1-1)/2
	int parent = (n - 1 - 1) / 2;

	//让 从最后一个双亲节点开始往上的所有节点都是大堆,因为是从最后的双亲结点往上走所以循环条件是 parent>=0
	while (parent >= 0)
	{
		int child = parent * 2 + 1;

		//从双亲结点往下迭代 直到child>=n越了界了,就说明这个双亲结点下的数都排成大堆了
		while (child < n)
		{

			//从孩子中的选出最大的数和双亲结点比
			if (child + 1 < n && a[child + 1] > a[child])
			{
				child++;
			}

			//如果孩子大,就交换 并进行迭代
			if (a[child] > a[parent])
			{
				Sweap(&a[child], &a[parent]);
				parent = child;
				child = parent * 2 + 1;
			}
			//如果双亲结点大就说明大堆建好了,不用再迭代了
			else
			{
				break;
			}
		}
		parent--;
	}


	for (int j = n - 1; j > 0; j--)
	{
			//交换
			Sweap(&a[0], &a[j]);//把堆顶的元素和a[j]交换,这样大的数就归位了,现在要派的数就少一个
			//从下标为0开始建大堆
			int parent = 0;
			int child = parent * 2 + 1;
			//注意这里边界条件是 child<j 是随着排好的数的个数变化的
			while (child < j)
			{
				if (child + 1 < j && a[child + 1] > a[child])
				{
					child++;
				}
				if (a[child] > a[parent])
				{
					Sweap(&a[child], &a[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
	}
}

6.冒泡排序:

//冒泡排序
//冒泡排序是最挫的(没优化的选择排序除外),与时间复杂度相同的插入排序相比还是挫不少,因为插入排序如果有一段是有序的,那么也会剩下很多时间
void BubbleSort(int* a, int n)
{
	//拍好n-1个数就拍好了整个数组
	for (int j = 0; j < n - 1; j++)
	{
		
		int flag = 0;
		//因为循环了j趟就拍好了j个数 ,那么只需要比较剩下的n-j个数就好了,也就是一共比较n-1-j次。如:j=0(第一趟),只要比较n-1次;j=1(第二趟),只要比n-1-1次。
		for (int i = 0; i < n - 1 - j; i++)
		{
			if (a[i] > a[i + 1])
			{
				Sweap(&a[i], &a[i + 1]);
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

快排是重中之重单独成篇。下一篇将重点讲述快排的思想和几种重要而且常考的实现方法。

在使用 OpenGL 绘制上亿时,性能优化是关键。以下是一些最佳实践和优化方法: ### 1. 使用 VBO 和 VAO 使用顶缓冲对象 (VBO) 和顶数组对象 (VAO) 来存储和管理顶数据。VBO 可以将顶数据存储在 GPU 上,减少 CPU 和 GPU 之间的数据传输开销。VAO 则可以存储顶属性的配置,简化绘制调用。 ```cpp GLuint vbo, vao; glGenBuffers(1, &vbo); glGenVertexArrays(1, &vao); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 假设顶属性包含位置和颜色 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); ``` ### 2. 使用 Instancing 如果需要绘制量相同的,可以使用 instancing 技术。通过 `glDrawArraysInstanced` 或 `glDrawElementsInstanced`,可以在一次绘制调用中绘制多个实例,减少 API 调用的开销。 ```cpp glVertexAttribDivisor(0, 0); // 每个顶数据 glVertexAttribDivisor(1, 1); // 每个实例数据 glDrawArraysInstanced(GL_POINTS, 0, vertexCount, instanceCount); ``` ### 3. 使用精灵 (Point Sprites) 精灵是一种特殊的绘制模式,可以用来绘制纹理化的。通过 `GL_POINT_SPRITE` 和 `GL_COORD_REPLACE`,可以在顶着色器中生成纹理坐标。 ```cpp glEnable(GL_POINT_SPRITE); glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE); ``` ### 4. 使用着色器优化 在着色器中,避免不必要的计算。例如,可以将一些计算移到 CPU 上,或者使用更简单的数学公式。此外,确保所有对输出变量没有贡献的源代码都会被编译器优化掉,以减少工作量 [^2]。 ### 5. 使用多线程和异步传输 在 CPU 上生成顶数据时,可以使用多线程来加速数据生成。同时,使用异步传输技术(如像素缓冲对象 PBO)来减少 CPU 和 GPU 之间的数据传输延迟。 ### 6. 使用 LOD (Level of Detail) 根据视距离,动态调整细节级别。对于远处的,可以减少小或数量,从而减少绘制负载。 ### 7. 使用 GPU 加速的计算 对于规模的云数据,可以考虑使用 GPU 进行计算,如使用 Compute Shader 来处理的更新和变换,从而减少 CPU 的负担。 ### 8. 使用高效的绘制模式 选择合适的绘制模式,如 `GL_POINTS`、`GL_LINES` 或 `GL_TRIANGLES`,根据需求选择最高效的模式。 ### 9. 使用视锥体剔除 在绘制之前,进行视锥体剔除,只绘制可见区域内的,减少不必要的绘制操作。 ### 10. 使用硬件加速 确保使用支持硬件加速的 OpenGL 实现,以充分利用 GPU 的性能。 通过以上方法,可以在 OpenGL 中高效地绘制上亿,同时保持良好的性能和响应速度。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值