C语言实现快速排序(hoare法、挖坑法、前后指针法与非递归实现)——不看后悔系列

news2025/7/16 21:38:41

目录

1. hoare法

方法与步骤

代码实现

2. 挖坑法

方法与步骤

代码实现

3. 前后指针法

方法与步骤

代码实现 

4. 快速排序的缺点与优化

1.快速排序的缺点

2.快速排序的优化

① 三数取中法选 key

代码实现

② 小区间优化

代码实现

5. 快速排序的非递归实现

附录﹡完整源码

快速排序递归实现

快速排序非递归实现


快速排序是霍尔大佬在1962年提出的排序方法,因其出色的排序效率使得它成为使用最广泛的排序算法。快速排序之所以敢叫做快速排序,自然是有一定的道理,今天我们就来看看快速排序是如何凌驾于其它算法之上的。

快速排序的基本思想是:任取待排序数列中的一个数作为 key 值,通过某种方法使得 key 的左边所有的数都比它小,右边的数都比它大;以 key 为中心,将 key 左边的数列与右边的数列取出,做同样的操作(取 key 值,分割左右区间),直至所有的数都到了正确的位置。

上述所提到的某种方法可以有很多种,例如:hoare法、挖坑法、前后指针法。它们虽然做法不相同,但做的都是同一件事——分割出 key 的左右区间(左边的数比 key 小,右边的数比 key 大)。

我们首先来看看霍尔大佬所用的方法——hoare法。

1. hoare法

方法与步骤

以数列 6,1,2,7,9,3,4,5,8,10 为例:

1.取最左边为 key ,分别有 left 和 right 指向数列的最左端与最右端;

2. right 先走,找到比 key 小的数就停下来;

3. left 开始走,找到比 key 大的数就停下来;

4. 交换 left 与 right 所在位置的数;

5.重复上述操作,right 找小,left 找大,进行交换;

 

6. right 继续找小;

7. left 继续找大,若与 right 就停下来;

8.交换二者相遇位置与 key 处的值;

此时一趟排序就完成了,此时的数列有两个特点:

1. key 所指向的值(6)已经到了正确的位置;

2. key 左边的数字都比 key 要小,右边的都比 key 要大;

接下来就是递归的过程了,分别对左右区间进行同样的操作:

代码实现

知道了详解步骤,用代码来实现并不困难,但是有很多很多的细节需要注意。(这里的代码未经优化,当前的代码有几种极端的情况不能适应)

void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

void QuickSort(int* a, int begin, int end)
{
    //数列只有一个数,或无数列则返回
	if (begin >= end)
	{
		return;
	}

	int left = begin;
	int right = end;

	int keyi = left;

	while (left < right)
	{
        //右边先走
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}

		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}

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

	Swap(&a[keyi], &a[left]);

	QuickSort(a, begin, left - 1);
	QuickSort(a, left + 1, end);
}

2. 挖坑法

挖坑法相比于hoare法,思路上更为简单易懂。

方法与步骤

还是以同样的数列 6,1,2,7,9,3,4,5,8,10 为例:

1. 先将第一个数存放到 key 中,形成一个坑位:分别有 left 和 right 指向数列的最左端与最右端; 

2.  right 先走,找到比 key 小的数,将该数丢到坑里;同时又形成了一个新的坑;

3. left 开始走,找到比 key 大的数,将该数丢到坑里;同时形成一个新的坑;

 4. right继续找小,进行重复的操作; 

5. left 找大;

6. right 找小;

7. left 找大;

8.若二者相遇就停下来;将 key 值放入坑;

至此,一趟排序已经完成,我们发现此时的数列与hoare具有相同的特点:

1. key 所指向的值(6)已经到了正确的位置;

2. key 左边的数字都比 key 要小,右边的都比 key 要大;

挖坑法、hoare、前后指针法完成一趟排序后都具有相同的特点,所以不同版本的快速排序不一样的只有单趟排序的实现,总体思路都是相同的。

代码实现

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	int left = begin;
	int right = end;

	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;

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

3. 前后指针法

方法与步骤

以同样的数列为例:

1. 取第一个值为 key ;有 prev 和 cur 分别指向数列开头和 prev 的下一个数;

2. cur 先走,找到比 key 小的数就停下来;

3. ++prev ,交换 prev 与 cur 位置的数;(前两次无需交换,因为自己与自己换没有意义)

4. 重复此步骤;

5. 直到 cur 走完整个数列,交换 prev 与 key 处的值;

至此,第一趟排序就结束了,又是与前两种方法相同的结果;

代码实现 

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	int prev = begin;
	int cur = prev + 1;

	int keyi = begin;

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

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

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

4. 快速排序的缺点与优化

1.快速排序的缺点

我们用三种方式实现了快速排序,其实这三种方式并无明显的优劣之分。但是我们前面设计的快速排序其实是有两个缺点的:

1.在最坏情况下它的的效率极慢;

2.在数据量太大时会造成栈溢出。

那么什么情况是最坏情况呢?答案是,当数据本身就是有序的时候(无论是逆序还是顺序)。在最坏情况下,每次我们的 key 值都是最大或者最小,这样就会使 key 与数列的每个数都比较一次,它的时间复杂度为 O(n^2);

为什么会发生栈溢出呢?因为我们的快速排序是利用递归实现的,有递归调用,就要建立函数栈帧,并且随着递归的深度越深所要建立的函数栈帧的消耗就越大 。如这幅图所示:

2.快速排序的优化

① 三数取中法选 key

为了应对最坏情况会出现时间复杂度为 O(N^2) 的情况,有人提出了三数取中的方法。

旧方法中,我们每次选 key 都是数列的第一个元素。三数取中的做法是,分别取数列的第一个元素、最后一个元素和最中间的元素,选出三个数中不是最大也不是最小的那个数当作 key 值。

有了三数取中,之前的最坏情况立马变成了最好情况。

代码实现

由于hoare法、挖坑法、前后指针法最终的效果都相同且效率差异很小,所以就任意选取一个为例,其余两者都类似。

//三数取中的函数
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;

	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if (a[begin] > a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
	else // a[begin] > a[mid]
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}
//hoare法
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	int mid = GetMidIndex(a, begin, end);
	Swap(&a[mid], &a[begin]);

	int left = begin;
	int right = end;
	int keyi = left;

	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}

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

	Swap(&a[keyi], &a[left]);

	QuickSort(a, begin, left - 1);
	QuickSort(a, left + 1, end);
}

② 小区间优化

随着递归的调用越深入,此时有个很大的缺点就是函数栈帧的消耗很大。但是同时又有一个好处,就是越往下,数列就越接近有序,且此时每个小区间的数据个数特别少。

那么有什么办法可以取其长处避其短处呢?不知道你是否还记得插入排序的特点——数据越接近有序,效率就越高。并且,在数据量极少的情况下,时间复杂度为 O(N^2) 的插入排序与时间复杂度为 O(N*log N) 的快速排序基本没有什么区别。所以,我们干脆就在排序数据量少的数列时,采用插入排序代替。

代码实现

//三数取中的函数
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;

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

//插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)         //大于tmp,往后挪一个
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;          //把tmp插入空隙
	}
}

//hoare法
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	if ((end - begin + 1) < 15)
			{
				// 小区间用直接插入替代,减少递归调用次数
				InsertSort(a+begin, end - begin + 1);
			}
	else
	{
		int mid = GetMidIndex(a, begin, end);
		Swap(&a[mid], &a[begin]);

		int left = begin;
		int right = end;
		int keyi = left;

		while (left < right)
		{
			while (left < right && a[right] >= a[keyi])
			{
				right--;
			}
			while (left < right && a[left] <= a[keyi])
			{
				left++;
			}

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

		Swap(&a[keyi], &a[left]);

		QuickSort(a, begin, left - 1);
		QuickSort(a, left + 1, end);
	}
}

两外两种方法的代码实现已打包完成,可在文末直接取用。

5. 快速排序的非递归实现

快速排序的非递归思路与递归相差无几,唯一不同的是,非递归用栈或队列模拟函数递归建立栈帧的过程。

void QuickSortNonR(int* a, int begin, int end)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);

	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort1(a, left, right);//三种方法任选其一
		//int keyi = PartSort2(a, left, right);
		//int keyi = PartSort3(a, left, right);

		if (keyi + 1 < right)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}

		if (left < keyi - 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
	}

	StackDestroy(&st);
}

附录﹡完整源码

快速排序递归实现

//交换函数
void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}

//三数取中
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;

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

//插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)         //大于tmp,往后挪一个
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;          //把tmp插入空隙
	}
}

// Hoare法
int PartSort1(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

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

		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

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

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

	return keyi;
}

// 挖坑法
int PartSort2(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

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

//前后指针法
int PartSort3(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

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

		++cur;
	}

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

//快速排序(递归)
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	if ((end - begin + 1) < 15)
	{
		// 小区间用直接插入替代,减少递归调用次数
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort1(a, begin, end);
		//int keyi = PartSort2(a, begin, end);
		//int keyi = PartSort3(a, begin, end);

		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

快速排序非递归实现

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

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;  //动态开辟数组
	int capacity; //记录栈的容量大小
	int top; //记录栈顶的位置
}Stack;

//栈的初始化
void StackInit(Stack* ps);
//释放动态开辟的内存
void StackDestroy(Stack* ps);
//压栈
void StackPush(Stack* ps, STDataType data);
//出栈
void StackPop(Stack* ps);
//读取栈顶的元素
STDataType StackTop(Stack* ps);
//判断栈是否为空
bool StackEmpty(Stack* ps);


// Hoare法
int PartSort1(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

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

		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

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

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

	return keyi;
}

// 挖坑法
int PartSort2(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

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

int PartSort3(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

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

		++cur;
	}

	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}
void QuickSortNonR(int* a, int begin, int end)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);

	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort1(a, left, right);//三种方法任选其一
		//int keyi = PartSort2(a, left, right);
		//int keyi = PartSort3(a, left, right);

		if (keyi + 1 < right)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}

		if (left < keyi - 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
	}

	StackDestroy(&st);
}

//栈的实现_函数定义

void StackInit(Stack* ps)
{
	assert(ps);
	//初始化时,可附初值,也可置空
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

void StackDestroy(Stack* ps)
{
	assert(ps);
	//若并未对ps->a申请内存,则无需释放
	if (ps->capacity == 0)
		return;
	//释放
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

void StackPush(Stack* ps,STDataType data)
{
	assert(ps);
	//若容量大小等于数据个数,则说明栈已满,需扩容
	if (ps->capacity == ps->top)
	{
		//若为第一次扩容,则大小为4,否则每次扩大2倍
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	//压栈
	ps->a[ps->top] = data;
	ps->top++;
}

void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//出栈
	ps->top--;
}

STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//返回栈顶的数据
	return ps->a[ps->top - 1];
}

bool StackEmpty(Stack* ps)
{
	assert(ps);
	//返回top
	return ps->top == 0;
}

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

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

相关文章

数据结构与算法基础(王卓)(16):KMP算法详解(代码实现)

实现代码的过程中 具体细节、问题&#xff1a; &#xff08;1&#xff09;&#xff1a;关于写Get_next函数的标题&#xff1a; 现象&#xff1a; PPT上写的是&#xff1a; void get_next(SString T, int &next[]) 然而并不能运行&#xff0c;而当我们去掉了引用符号&…

记录踩过的坑-Git

Git命令克隆很慢原命令&#xff1a;git clone -b r1.13.0 https://github.com/tensorflow/models.git现在替换为&#xff1a;git clone -b r1.13.0 https://github.com.cnpmjs.org/tensorflow/models.git也就是把原 URL 中的 github.com 替换为 github.com.cnpmjs.org&#xff…

设计模式第9式:迭代器模式

前言 我们有很多种方法可以把对象集中到一个集合中&#xff0c;比如列表、堆栈、散列表中。每种集合都有自己的特点和使用时机&#xff0c;但都有一个共同的需求&#xff1a;用户想要遍历这些对象。同时我们并不想用户看到集合的实现&#xff0c;本文将讲解如何让用户遍历对象…

JVM垃圾回收器详解

垃圾收集器没有在规范中进行过多的规定&#xff0c;可以由不同的厂商、不同版本的JVM来实现。由于JDK的版本处于高速迭代过程中&#xff0c;因此Java发展至今已经衍生了众多的GC版本。从不同角度分析垃圾收集器&#xff0c;可以将GC分为不同的类型。1、垃圾回收器分类1.1、按线…

国际安全领域顶会NDSS 2023录稿整理 (下)

隐私计算研习社 NDSS是网络和系统安全领域的四大顶级国际学术会议&#xff08;BIG4&#xff09;之一&#xff0c;第三十届会议于2023年2月27日到3月3日&#xff0c;在美国圣迭戈举办。本文将接着整理剩余论文&#xff0c;并对论文进行分类&#xff0c;感兴趣的小伙伴可以访问论…

【Linux】信号+再谈进程地址空间

目录 一、Linux中的信号 1、Linux中的信号 2、进程对信号的处理 3、信号的释义 二、信号的捕捉 1、信号的捕捉signal() 2、信号的捕捉sigaction() 三、信号如何产生&#xff1f; 1、kill()用户调用kill向操作系统发送信号 通过命令行参数模仿写一个kill命令 2、rais…

pinctrl和gpio子系统

一、pinctrl子系统简介Linux驱动讲究驱动分离与分层&#xff0c;pinctrl和gpio子系统就是驱动分离与分层思想下的产物&#xff0c;pinctrl子系统主要工作内容如下&#xff1a;获取设备树中的pin信息根据获取到的pin信息来设置pin的复用功能根据获取到的pin信息来设置pin的电气属…

Day914.安全认证架构演进:单块阶段 -SpringBoot与K8s云原生微服务实践

安全认证架构演进&#xff1a;单块阶段 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于安全认证架构演进&#xff1a;单块阶段的内容。 讲到安全认证的内容&#xff0c;就必然会提到两个点&#xff1a;认证 和 授权。 认证&#xff1a;我是谁授权&#xff1a;我能…

Spring中的事务@Transactional

Transactional可以添加在方法上 添加在方法上时&#xff0c;表示该方法出现了异常或者报错&#xff0c;而导致之前数据库没有进行回滚事件&#xff0c;也就是说如果在方法中&#xff0c;有报错&#xff0c;但是添加了Transactional 则会开始回滚。 Transactional 在异常被捕获…

剑指 Offer 29. 顺时针打印矩阵

剑指 Offer 29. 顺时针打印矩阵 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 输入一个矩阵&#xff0c;按照从外向里以顺时针的顺序依次打印出每一个数字。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2…

【OpenCV技能树】——OpenCV基础

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; 目前正在进行 OpenCV技能树的学习&#xff0c;OpenCV是学习图像处理理论知识比较好的一个途径&#xff0c;至少比看书本来得实在。本专栏文章主要记录学习Op…

Apache apisix默认密钥漏洞(CVE-2020-13945)

目录漏洞描述影响版本漏洞复现声明&#xff1a;本文仅供学习参考&#xff0c;其中涉及的一切资源均来源于网络&#xff0c;请勿用于任何非法行为&#xff0c;否则您将自行承担相应后果&#xff0c;本人不承担任何法律及连带责任。漏洞描述 Apache APISIX 是一个动态、实时、高…

LearnOpenGL-模型加载-3.渲染模型

本人刚学OpenGL不久且自学&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/LearnOpenGLProject 文章目录模型加载重要代码读取3D模型递归处理结点的网格加载纹理优化程序代码Model类加载模型流程例子1&…

【运筹优化】拉格朗日松弛 次梯度算法求解整数规划问题 + Java调用Cplex实战

文章目录一、拉格朗日松弛二、次梯度算法三、案例实战一、拉格朗日松弛 当遇到一些很难求解的模型&#xff0c;但又不需要去求解它的精确解&#xff0c;只需要给出一个次优解或者解的上下界&#xff0c;这时便可以考虑采用松弛模型的方法加以求解。 对于一个整数规划问题&…

Python交通标志识别基于卷积神经网络的保姆级教程(Tensorflow)

项目介绍 TensorFlow2.X 搭建卷积神经网络&#xff08;CNN&#xff09;&#xff0c;实现交通标志识别。搭建的卷积神经网络是类似VGG的结构(卷积层与池化层反复堆叠&#xff0c;然后经过全连接层&#xff0c;最后用softmax映射为每个类别的概率&#xff0c;概率最大的即为识别…

chromium 50 chromium57 版本编译启用 widevine 功能

本实验中 chrome 版本为 57.0.2987.98 01 chromium57 在 win11 版本中启用 widevine 功能 01.01 启用 enable_widevine 选项生成 widevine 相关动态库 在chromium 57 版本中&#xff0c;编译时秩序设置 enable_widevinetrue 即可生成 widevinecdm.dll 和 widevinecdmadapter…

windows下maven更新/安装

写在前面: 我的maven学习的比较早&#xff0c;后面windows文件管理分类的时候&#xff0c;把学习用的全部在一个文件夹了&#xff0c;而这个又不好移动进去。 正好也更新一下maven的版本了&#xff0c;不过和重新安装好像差不多了。 现在写的windows的以后&#xff0c;在看看要…

若依框架部署从零开始2023版(前后端分离)

前言电脑最近重装了一次系统&#xff0c;目前什么都没有安装&#xff0c;记录一下从零开始部署前后端分离版本的若依框架系统先去官网把若依源码拉下来代码克隆若依目前已经有很多的版本了&#xff0c;因为现在开发比较流行前后端分离&#xff0c;因此这里演示前后端分离版本点…

外卖点餐系统小程序 PHP+UniAPP

一、介绍 本项目是给某大学餐厅开发的外面点餐系统&#xff0c;该项目针对校内的学生&#xff0c;配送由学校的学生负责配送。因此&#xff0c;该项目不同于互联网的外卖点餐系统。 该系统支持属于 Saas 系统&#xff0c;由平台端、商家端、用户端、以及配送端组成。 其中&a…

从功能测试进阶自动化测试,爆肝7天整理出这一份超全学习指南【附网盘资源】

因为我最近在分享自动化测试技术&#xff0c;经常被问到&#xff1a;功能测试想转自动化&#xff0c;请问应该怎么入手&#xff1f;有没有好的资源推荐&#xff1f;那么&#xff0c;接下来我就结合自己的经历聊一聊我是如何在工作中做自动化测试的。&#xff08;学习路线和网盘…