Vibe算法原理与实践(C++)

2024-05-20 19:48
文章标签 算法 c++ 实践 原理 vibe

本文主要是介绍Vibe算法原理与实践(C++),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

ViBe - a powerful technique for background detection and subtraction in video sequences
ViBe是一种像素级视频背景建模或前景检测的算法,效果优于所熟知的几种算法,对硬件内存占用也少。ViBe是一种像素级的背景建模、前景检测算法,该算法主要不同之处是背景模型的更新策略,随机选择需要替换的像素的样本,随机选择邻域像素进行更新。在无法确定像素变化的模型时,随机的更新策略,在一定程度上可以模拟像素变化的不确定性。

背景差方法实现运动物体检测面临的挑战主要有:

  • 可直接应用在产品中,软硬件兼容性好;
  • 必须适应环境的变化(比如光照的变化造成图像色度的变化);
  • 相机抖动引起画面的抖动(比如手持相机拍照时候的移动);
  • 图像中密集出现的物体(比如树叶或树干等密集出现的物体,要正确的检测出来);
  • 须能够正确的检测出背景物体的改变(比如新停下的车必须及时的归为背景物体,而有静止开始移动的物体也需要及时的检测出来)。
  • 物体检测中往往会出现Ghost区域,Ghost区域也就是指当一个原本静止的物体开始运动,背景差检测算法可能会将原来该物体所覆盖的区域错误的检测为运动的,这块区域就成为Ghost,当然原来运动的物体变为静止的也会引入Ghost区域,Ghost区域在检测中必须被尽快的消除。一个Ghost区域的实例如图1,在图中可以发现相比于原图,检测的结果中错误的多出现了两个人的形状,这就是Ghost。
    在这里插入图片描述
ViBe检测方法

ViBe是本篇论文中所提出的一个检测方法,相比于其他方法它有很多的不同和优点。具体的思想就是为每个像素点存储了一个样本集,样本集中采样值就是该像素点过去的像素值和其邻居点的像素值,然后将每一个新的像素值和样本集进行比较来判断是否属于背景点。

该模型主要包括三个方面:

模型的工作原理;
模型的初始化方法;
模型的更新策略。

模型的工作原理

背景物体就是指静止的或是非常缓慢的移动的物体,而前景物体就对应移动的物体。所以我们可以把物体检测看成一个分类问题,也就是来确定一个像素点是否属于背景点。在ViBe模型中,背景模型为每个背景点存储了一个样本集,然后将每一个新的像素值和样本集进行比较来判断是否属于背景点。可以知道如果一个新的观察值属于背景点那么它应该和样本集中的采样值比较接近。

具体的讲,我们记V(x):x点处的像素值;M(x)={V1,V2,…VN}为x处的背景样本集(样本集大小为N);SR(v(x)):以x为中心R为半径的区域,如果M(x) [{SR(v(x))∩ {v1,v2, . . . , vN}}]大于一个给定的阈值min,那么就认为x点属于背景点。

背景模型的初始化

初始化是建立背景模型的过程,一般的检测算法需要一定长度的视频序列学习完成,影响了检测的实时性,而且当视频画面突然变化时,重新学习背景模型需要较长时间。

ViBe算法主要是利用单帧视频序列初始化背景模型,对于一个像素点,结合相邻像素点拥有相近像素值的空间分布特性,随机的选择它的邻域点的像素值作为它的模型样本值。ViBe的初始化仅仅通过一帧图像即可完成。ViBe初始化就是填充像素的样本集的过程但是由于在一帧图像中不可能包含像素点的时空分布信息,我们利用了相近像素点拥有相近的时空分布特性,具体来讲就是:对于一个像素点,随机的选择它的邻居点的像素值作为它的模型样本值。M0(x)=v0(y|y∈NG(x)),t=0初始时刻,NG(x)即为邻居点 。这种初始化方法优点是对于噪声的反应比较灵敏,计算量小速度快,可以很快的进行运动物体的检测,缺点是容易引入Ghost区域。

  • 优点:不仅减少了背景模型建立的过程,还可以处理背景突然变化的情况,当检测到背景突然变化明显时,只需要舍弃原始的模型,重新利用变化后的首帧图像建立背景模型。
  • 缺点:由于可能采用了运动物体的像素初始化样本集,容易引入拖影(Ghost)区域。
前景检测过程

1.背景模型为每个背景点存储一个样本集,然后每个新的像素值和样本集比较判断是否属于背景。
2.计算新像素值和样本集中每个样本值的距离,若距离小于阈值,则近似样本点数目增加。
3.如果近似样本点数目大于阈值,则认为新的像素点为背景。
4.检测过程主要由三个参数决定:样本集数目N,阈值min和距离相近判定的阈值R,一般具体实现,参数设置为N=20,min=2,R=20。

背景模型的更新策略

背景模型的更新就是使得背景模型能够适应背景的不断变化,比如光照的变化,背景物体的变更等等。

保守的更新策略:前景点永远不会被用来填充背景模型,会引起死锁,比如初始化的时候如果一块静止的区域被错误的检测为运动的,那么在这种策略下它永远会被当做运动的物体来对待;

Blind策略:对死锁不敏感,前景背景都可以来更新背景模型,缺点是缓慢移动的物体会融入背景中无法被检测出来。在本方法中采用的更新策略是保守的更新策略+前景点计数方法。

前景点计数:对像素点进行统计,如果某个像素点连续N次被检测为前景,则将其更新为背景点。

随机的子采样:在每一个新的视频帧中都去更新背景模型中的每一个像素点的样本值是没有必要的,当一个像素点被分类为背景点时,它有1/ φ的概率去更新背景模型。

具体的更新方法:每一个背景点有1/ φ的概率去更新自己的模型样本值,同时也有1/ φ的概率去更新它的邻居点的模型样本值。更新邻居的样本值利用了像素值的空间传播特性,背景模型逐渐向外扩 散,这也有利于Ghost区域的更快的识别。同时当前景点计数达到临界值时将其变为背景,并有1/ φ的概率去更新自己的模型样本值。

在选择要替换的样本集中的样本值时候,我们是随机选取一个样本值进行更新,这样可以保证样本值的平滑的生命周期由于是随机的更新,这样一个样本值在时刻t不被更新的概率是 (N-1)/N,假设时间是连续的,那么在dt的时间过去后,样本值仍然保留的概率是
P ( t , t + d t ) = ( N − 1 N ) ( t + d t ) − t P(t,t+dt) = (\frac{N-1}{N})^{(t+dt)-t} P(t,t+dt)=(NN1)(t+dt)t
也可以写作:
P ( t , t + d t ) = e − l n ( N N − 1 ) d t P(t,t+dt) = e^{-ln(\frac{N}{N-1})dt} P(t,t+dt)=eln(N1N)dt
这就表明一个样本值在模型中是否被替换与时间t无关 ,随机策略是合适的。

1).无记忆更新策略
每次确定需要更新像素点的背景模型时,以新的像素值随机取代该像素点样本集的一个样本值。

2).时间取样更新策略
并不是每处理一帧数据,都需要更新处理,而是按一定的更新率更新背景模型。当一个像素点被判定为背景时,它有1/rate的概率更新背景模型。rate是时间采样因子,一般取值为16。

3).空间邻域更新策略
针对需要更新像素点,随机的选择一个该像素点邻域的背景模型,以新的像素点更新被选中的背景模型。

ViBe的改进
  • 不同的距离函数和二值化标准
  • 对更新掩膜和输出掩膜的分割进行适当的滤波操作
  • 抑制邻域更新
  • 检测闪烁的像素点
  • 增加更新因子
距离计算方法

以自适应阈值代替原来固定的距离判定阈值,阈值大小与样本集的方差成正比,样本集方差越大,说明背景越复杂,判定阈值应该越大.
在这里插入图片描述

分离UpdatingMask和SegmentationMask

引入目标整体的概念,弥补基于像素级前景检测的不足。针对updating mask和segmentation mask采用不同尺寸的形态学处理方法,提高检测准确率。

抑制邻域更新

在updating mask里,计算像素点的梯度,根据梯度大小,确定是否需要更新邻域。梯度值越大,说明像素值变化越大,说明该像素值可能为前景,不应该更新。

检测闪烁像素点

引入闪烁程度的概念,当一个像素点的updating label与前一帧的updating label不一样时,blinking level增加15,否则,减少1,然后根据blinking level的大小判断该像素点是否为闪烁点。闪烁像素主要出现在背景复杂的场景,如树叶、水纹等,这些场景会出现像素背景和前景的频繁变化,因而针对这些闪烁应该单独处理,可以作为全部作为背景。

增加更新因子

ViBe算法中,默认的更新因子是16,当背景变化很快时,背景模型无法快速的更新,将会导致前景检测的较多的错误。因而,需要根据背景变化快慢程度,调整更新因子的大小,可将更新因子分多个等级,如rate = 16,rate = 5,rate = 1。

vibeRely.h

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>using namespace std;
using namespace cv;#define defaultNbSamples 20		//每个像素点的样本个数
#define defaultReqMatches 2		//#min指数
#define defaultRadius 20		//Sqthere半径
#define defaultSubsamplingFactor 16	//子采样概率
#define background 0		//背景像素
#define foreground 255		//前景像素void Initialize(CvMat* pFrameMat,RNG rng);//初始化
void update(CvMat* pFrameMat,CvMat* segMat,RNG rng,int nFrmNum);//更新

vibeRely.cpp

#include "vibeRely.h"
#include <opencv2/opencv.hpp>
#include "highgui.h"
#include <math.h>
#include <iostream>using namespace std;
using namespace cv;static int c_xoff[9] = {-1,  0,  1, -1, 1, -1, 0, 1, 0};//x的邻居点
static int c_yoff[9] = {-1,  0,  1, -1, 1, -1, 0, 1, 0};//y的邻居点float samples[1024][1024][defaultNbSamples+1];//保存每个像素点的样本值//初始化
void Initialize(CvMat* pFrameMat,RNG rng){//记录随机生成的 行(r) 和 列(c)int rand,r,c;//对每个像素样本进行初始化for(int y=0;y<pFrameMat->rows;y++){//Heightfor(int x=0;x<pFrameMat->cols;x++){//Widthfor(int k=0;k<defaultNbSamples;k++){//随机获取像素样本值rand=rng.uniform( 0, 9 );r=y+c_yoff[rand]; if(r<0) r=0; if(r>=pFrameMat->rows) r=pFrameMat->rows-1;	//行c=x+c_xoff[rand]; if(c<0) c=0; if(c>=pFrameMat->cols) c=pFrameMat->cols-1;	//列//存储像素样本值samples[y][x][k]=CV_MAT_ELEM(*pFrameMat,float,r,c);}samples[y][x][defaultNbSamples]=0;}}
}//更新函数
void update(CvMat* pFrameMat,CvMat* segMat,RNG rng,int nFrmNum){for(int y=0;y<pFrameMat->rows;y++){	//Heightfor(int x=0;x<pFrameMat->cols;x++){	//Width//用于判断一个点是否是背景点,index记录已比较的样本个数,count表示匹配的样本个数int count=0,index=0;float dist=0;//while((count<defaultReqMatches) && (index<defaultNbSamples)){dist=CV_MAT_ELEM(*pFrameMat,float,y,x)-samples[y][x][index];if(dist<0) dist=-dist;if(dist<defaultRadius) count++;index++;}if(count>=defaultReqMatches){//判断为背景像素,只有背景点才能被用来传播和更新存储样本值samples[y][x][defaultNbSamples]=0;*((float *)CV_MAT_ELEM_PTR(*segMat,y,x))=background;int rand=rng.uniform(0,defaultSubsamplingFactor);if(rand==0){rand=rng.uniform(0,defaultNbSamples);samples[y][x][rand]=CV_MAT_ELEM(*pFrameMat,float,y,x);}rand=rng.uniform(0,defaultSubsamplingFactor);if(rand==0){int xN,yN;rand=rng.uniform(0,9);yN=y+c_yoff[rand];if(yN<0) yN=0; if(yN>=pFrameMat->rows) yN=pFrameMat->rows-1;rand=rng.uniform(0,9);xN=x+c_xoff[rand];if(xN<0) xN=0; if(xN>=pFrameMat->cols) xN=pFrameMat->cols-1;rand=rng.uniform(0,defaultNbSamples);samples[yN][xN][rand]=CV_MAT_ELEM(*pFrameMat,float,y,x);} }else {//判断为前景像素*((float *)CV_MAT_ELEM_PTR(*segMat,y,x))=foreground;samples[y][x][defaultNbSamples]++;if(samples[y][x][defaultNbSamples]>50){    保守策略的阈值,大于该数则为前景 默认50int rand=rng.uniform(0,defaultNbSamples);if(rand==0){rand=rng.uniform(0,defaultNbSamples);samples[y][x][rand]=CV_MAT_ELEM(*pFrameMat,float,y,x);}}}}}}

main.cpp

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include<cxcore.h>
#include <cvaux.h>
#include <ctime>
#include<direct.h>
#include<time.h>
#include "vibeRely.h"using namespace std;
using namespace cv;int nFrmNum = 0;//记录帧数
int Width;//记录帧的宽度
int Height;//记录帧的高度
int FrameRate;//记录视频帧率int main(int argc, char* argv[])
{clock_t startTime;clock_t endTime;double duration;IplImage* pFrame=NULL;CvMat* pFrameMat = NULL;//pFrame对象IplImage* pAfter=NULL;CvMat* pAfterMat=NULL;//保存pFrame对应的灰度图像IplImage* segMap=NULL;CvMat* segMat=NULL;//保存处理后的信息IplImage* bFrame=NULL;RNG rng(0xFFFFFFFF);         //创建一个随机数生成器char pBuf[100];_getcwd(pBuf,sizeof(pBuf));  //获得当前工程所在路径strcat_s(pBuf,"//car.flv");   //pbuf为当前工程目录下的需要读取的视频文件char outfile[7] ;//char filename[100];//保存的图像位置和名称outfile[0] = 'o';outfile[1] = 'u';outfile[2] = 't';outfile[3] = '.';outfile[4] = 'a';outfile[5] = 'v';outfile[6] = 'i';//打开视频文件CvCapture* pCapture=cvCreateFileCapture(pBuf);if(pCapture==NULL) {cout<<"video file open error!"<<endl;return -1;}int array_cross[] = { 0,  0xff,   0,0xff,0xff,  0xff,0   ,0xff,  0};IplConvKernel * rectCross = cvCreateStructuringElementEx(3, 3, 1, 1, CV_SHAPE_CROSS, array_cross);//获取视频相关信息,帧率和大小double fps = cvGetCaptureProperty(pCapture, CV_CAP_PROP_FPS);CvSize pSize=cvSize((int)cvGetCaptureProperty(pCapture,CV_CAP_PROP_FRAME_WIDTH),(int)cvGetCaptureProperty(pCapture,CV_CAP_PROP_FRAME_HEIGHT));//创建输出视频文件CvVideoWriter* Save_result = NULL;Save_result = cvCreateVideoWriter(outfile, CV_FOURCC('X', 'V', 'I', 'D'), fps, pSize, 1);
//	Save_result = cvCreateVideoWriter(outfile, CV_FOURCC('M', 'J', 'P', 'G'), fps, pSize, 1);IplImage* dstImg = cvCreateImage(pSize, IPL_DEPTH_8U, 1);//创建要保存的图像cvNamedWindow("src",CV_WINDOW_AUTOSIZE);cvMoveWindow("src",  100,200);cvNamedWindow("dst",CV_WINDOW_AUTOSIZE);cvMoveWindow("dst",100,200);//VideoWriter w_cap("re_video.avi", CV_FOURCC('M', 'J', 'P', 'G'), rate, cv::Size(width, height));//逐帧读取视频并进行处理while(pFrame = cvQueryFrame( pCapture )){		    nFrmNum++;startTime = clock();//如果是第一帧,申请内存并进行初始化if(nFrmNum==1){segMap = cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,1);segMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);//原始图像的灰度图pAfter=cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,1);// 原始图像灰度图矩阵pAfterMat=cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);//转化成单通道图像再处理cvCvtColor(pFrame, pAfter, CV_BGR2GRAY);cvConvert(pAfter, pAfterMat);Initialize(pAfterMat,rng);}else {IplConvKernel * myModel;myModel=cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_CROSS);cvCvtColor(pFrame,pAfter,CV_BGR2GRAY);cvConvert(pAfter,pAfterMat);update(pAfterMat,segMat,rng,nFrmNum);      //更新背景cvConvert(segMat,segMap);//cvErode( segMap,segMap,myModel,1);// cvDilate(segMap,segMap, NULL,1);cvReleaseStructuringElement(&myModel);//载入原图像到目标图像cvSetImageROI(dstImg, cvRect(0, 0, pFrame->width, pFrame->height));cvCopy(pFrame, dstImg);cvResetImageROI(dstImg);//将segMap转换成三通道图像存在pFrame中cvCvtColor(segMap, pFrame, CV_GRAY2BGR);//载入检测后的图像到目标图像cvSetImageROI(dstImg, cvRect(pFrame->width, 0, pFrame->width, pFrame->height));cvCopy(pFrame, dstImg);cvResetImageROI(dstImg);cvSetImageROI(dstImg, cvRect(pFrame->width * 2, 0, pFrame->width, pFrame->height));cvDilate(pFrame, dstImg);cvResetImageROI(dstImg);//显示提示文字//cvPutText(dstImg, "Origin Video", cvPoint(0, pFrame->height - 5), &font, CV_RGB(255, 255, 0));//cvPutText(dstImg, "ViBe Video", cvPoint(pFrame->width, pFrame->height - 5), &font, CV_RGB(255, 255, 0));}//保存视频和输出//cvWriteFrame(Save_result,dstImg);//cvWriteFrame(Save_result, segMap);/*输出图片if(nFrmNum<11)sprintf(filename,"E:\\Result\\Vibe\\AVSS_PV_Easy_Divx_Xvid\\000%d_Vibe.jpg",nFrmNum-1);else if(nFrmNum<101)sprintf(filename,"E:\\Result\\Vibe\\AVSS_PV_Easy_Divx_Xvid\\00%d_Vibe.jpg",nFrmNum-1);else if(nFrmNum<1001)sprintf(filename,"E:\\Result\\Vibe\\AVSS_PV_Easy_Divx_Xvid\\0%d_Vibe.jpg",nFrmNum-1);else if(nFrmNum<10001)sprintf(filename,"E:\\Result\\Vibe\\AVSS_PV_Easy_Divx_Xvid\\%d_Vibe.jpg",nFrmNum-1);cvSaveImage(filename,dstImg);*/endTime = clock();cvShowImage("src",pFrame);  cvShowImage("dst",segMap);  cvWaitKey(1);duration = (double)(endTime-startTime)/CLOCKS_PER_SEC*1000;    //计算该次循环所以时间cout<<duration<<"ms"<<endl;//保存视频和输出//cvWriteFrame(Save_result,dstImg);cvWriteFrame(Save_result, segMap);}cvReleaseImage(&pFrame);cvReleaseMat(&pFrameMat);cvReleaseImage(&pAfter);cvReleaseMat(&pAfterMat);cvReleaseImage(&segMap);cvReleaseMat(&segMat);cvReleaseVideoWriter(&Save_result);cvReleaseImage(&dstImg);cvDestroyWindow("dst");cvDestroyWindow("src");return 0;}

在这里插入图片描述
在这里插入图片描述

以上都是借鉴的别人的代码,我想把输出的视频个保存下来,但是出现错误:OpenCV Error: Assertion failed (src.channels() == dst.channels()) in cvCopy, file C:\build\master_winpack-build-win64-vc14\opencv\modules\core\src\copy.cpp, line 1297

网上查是说dst和src图片之间的深度和位数不一致,需要做什么改动我也不清楚。希望有大神可以帮我看看。

这篇关于Vibe算法原理与实践(C++)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/995927

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig