【数据结构】排序合集(万字详解)

news2024/7/4 4:28:44

文章目录

  • 前言
    • 插入排序
    • 希尔排序
    • 选择排序
    • 堆排序
    • 快速排序
      • hoare原生版本
      • 挖坑法
      • 前后指针法
      • 三数取中优化
      • 随机数取key优化
      • 三路划分版
      • 非递归
    • 归并排序
      • 递归
      • 非递归
        • 调整边界
        • 单次归并单次拷贝
  • 总结

前言

排序,以字面意思来说就是通过特定的算法将一组或多组无序或者接近有序的数据,以升序或者降序的方式重新进行排序组合;

[7,4,2,9,8,6,5,1,3];

以升序的方式进行排序最终为:

[1,2,3,4,5,6,7,8,9];

排序算法就是如何使得数据按照要求排列的方法;
排序的算法多种多样,基本的排序算法共有八种,分别为:

冒泡排序选择排序插入排序希尔排序归并排序快速排序堆排序计数排序

插入排序

插入排序作为排序中较为简单和经典的排序,插入排序的思维可以看作为一个打牌中摸牌顺牌的阶段一样;
假设手里的牌都是顺序的,摸下一张牌与之前的牌一张一张进行比较,设这张牌为n,将n与之前顺序的牌进行比较,当遇到比它小的牌的时候即将该牌放到此处;

在这里插入图片描述

设[0,end]区间为有序,cur为end后的下一个数据,将该数据进行保存,若是遇到比cur大的数据则挪动数据,将cur放至比cur小的数据之前,若是cur比end位置大则不需要进行上步操作,若是cur比[0,end]区间内的任意数据都小,那就挪动数据,将cur放置0处,同时++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) {
				a[end + 1] = a[end];
				end--;
			}
			else break;
		}
		a[end+1] = tmp;
	}
}

时间复杂度:
插入排序的时间复杂度有两种情况:

  • 当数据为有序的情况下,只需要遍历n-1次,为最优的情况,时间复杂度为O(N);
  • 当数据为逆序的情况时,为最坏的情况,最需要遍历1+2+3+4+…+N-1次,时间复杂度为O(N2);

空间复杂度:

  • 插入排序的空间复杂度为常数阶O(1);

稳定性

  • 稳定

希尔排序

希尔排序是在以插入排序为前提下进行的一种优化的排序,由于插入排序不太擅长排序逆序的情况,所以一位叫做Donald L. Shell的大佬对该排序进行了改进;
希尔排序又被称为" 缩小增量排序 ",即在插入排序之前进行预排序;

设gap,将间隔为gap的数据分为一组,共分为gap组;
gap没有具体的规范,但是一般而言gap可以跟数据量有关系;
将每组数据利用插入排序的方法进行一次预排序,当预排序结束时,数据并没有完全有序,但是较为接近有序;

在这里插入图片描述
当然,根据数据量的变化,预排序可以进行不止一次;
再预排序过后即可进行插入排序;

void ShellSort(int* a, int n)
{	
	/*
	* gap根据数据个数进行变化
	* 故初始值赋值数据个数
	*/
	int gap = n;

	while(gap>1){
		/*
		* 控制gap使其不停进行变化
		* 当gap为1时则为最后一次排序,也是直接插入排序
		*/
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++) {//n-gap防止越界
			/*
			* 直接插入排序的思路
			*/
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0) {
				if (a[end] > tmp) {
					a[end + gap] = a[end];
					end-=gap;
				}
				else break;
			}
			a[end + gap] = tmp;
		}
	}
}

时间复杂度

  • 希尔排序的时间复杂度为O(N3/2),相对于直接插入排序的最坏情况O(N2)来说要快一些;

空间复杂度

  • 希尔排序的空间复杂度也为O(1);

稳定性

  • 不稳定

选择排序

选择排序的思路为双指针:
设begin为0,cur为begin+1,cur遍历一遍数据,找到最小或者最大的那个数据并将该位置保存,最后与begin所在的数据进行交换;
交换过后begin++;

在这里插入图片描述
为了使排序不那么繁琐可以使用begin与end两个指针记录头尾,cur指针从前往后分别找到最小和最大的数据并保存位置,最小的数据与begin交换,最大的数据与end进行交换,交换过后++begin与–end从而控制区间;

void SelectSort(int*a ,int n)
{
	int begin = 0, end = n - 1;
	//[begin,end]左闭右闭区间
	while (begin < end) {
		int min = begin, max = begin;
		for (int i = begin + 1; i <= end; i++) {
			
			if (a[i] < a[min]) {
				min = i;
			}
			if (a[i] > a[max]) {
				max = i;
			}
		}
		swap(&a[begin], &a[min]);
		if (begin == max) {
			max = min;
		}
		swap(&a[max], &a[end]);
		++begin, --end;
	}
}

时间复杂度

  • 当数据为完全顺序的情况下,不需要进行交换,则时间复杂度为O(N);
  • 若是数据为逆序的情况下则为最坏的情况,交换次数即为O(N-1)次;

空间复杂度

  • 选择排序的空间复杂度为常数阶O(1);

稳定性

  • 不稳定

堆排序

堆排序是基于堆而做的一种排序,同时堆排序也算是选择排序中的一种排序;
既然是基于堆的排序,首先数据必须为一个堆(升序建大堆,降序建小堆),而所给的每组数据中是堆的可能性并不大;
所以再进行排序之前应该先进行建堆,建堆的同时分为向上调整建堆与向下调整建堆;

堆排的思路为将头尾数据进行交换再进行向下调整,故向下调整是必须具有的一个,同时基于向下调整建堆的时间复杂度(O(N))优于向上调整建堆(O(N*logN)),故在这里只需要写一个向下调整算法就能实现堆排序;

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

void AdjustDown(int*a,int parent,int n)//parent==0 n == 6
{
	int child = parent * 2 + 1;//1
	while(child<n) {//child<6
		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 HeatSort(int* a, int n)//n==7
{
	/*建堆*/
	
	for (int i = (n-2)/2; i >= 0; i--) {
		AdjustDown(a, i, n);
	}

	int end = n - 1;
	while (end>0) {
		swap(&a[0], &a[end]); //交换头尾
		AdjustDown(a, 0,end);//传值传的是个数
		end--;
	}
}

时间复杂度

  • 堆排是在堆中进行选书,相对传统的选择排序来说效率快了很多,时间复杂度为O(N*logN);

空间复杂度

  • 堆排的空间复杂度为常数阶O(1);

稳定性

  • 不稳定

快速排序

快速排序是一个相对来说应用最广泛的一种排序算法,它一开始为一名英国计算机科学家霍尔于1960年所发明;
流行的原因是这个排序算法实现简单,且适用于不同的数据,同时相对于排序算法中的其他算法要快得多;
快速排序的基本思想为一种左右分治的思想,就如二叉树相同,将问题分为两个子问题进行解决;

基本思想为:

任取待排元素序列中的其中一个元素作为key值基准值;
按照待排序的区间分为两个序列,key值左边的数据均小于key,key值右边的数据均大于key;
再进行不断分治,最终将对应顺序的数据排列再相应位置即结束;

hoare原生版本

这个版本的也是最初的版本
其单次排序的思想即为:

选取数据中最左边的数据作为基准值key,保存该数据的下标keyi方便数据与key进行比较;
设左右两个指针left与right,left为keyi+1,right为最后一个数据的下标;
left从前往后遍历,right从后往前进行遍历,left找比key大的数据,right找比eky小的数据;
当left找到相应的数据同时right也找到相应的数据时,将两个数据进行交换;
交换过后再继续遍历;
当left与right相遇时,则停止遍历,同时将相遇位置的值与key进行交换;

同时,在遍历时若是需要左右指针都在中间位置停止,那必须保证key在左边时右指针先往前遍历,key若是在最右边选数则需要左边指针先进行遍历

在这里插入图片描述

[单次排序示意图]
//单次排序
int PartSort(int* a, int left, int right)
{
	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]);
	return left;
}

当第一次排序过后,将要以前序遍历的方式进行分治;
区间将会以[begin,keyi-1]keyi[keyi+1,end]进行划分;
根据该思维进行递归;

void QuitSort(int* a, int begin, int end)
{
	if (begin>=end) {
		return;
	} 
	int kiey = PartSort(a, begin, end);
	QuitSort(a, begin, kiey-1);
	QuitSort(a, kiey+1, end);
}
递归的结束条件即为该组数据中只存在一个数或是该段区间不存在;

挖坑法

挖坑法的思路与hoare原生版本的思路大体相同:

思路即为在开始前记住最左边的基准值key的值并将其保存,左右指针同时往中间进行遍历
左边找比基准值key大的值,右边找小的值,与hoare相同,key在左边时右指针先进行遍历;

当最左边key值被保存之后,key所在的位置即形成一个坑;
当右指针找到相应的值时则将右指针的值给给这个坑,此时left所在的位置,也就是key的位置的坑被填;
同时right指针所在的位置又新生成一个坑,left指针再从后往前遍历找到对应位置进行填补,如此反复;
当两个指针相遇或者相交时,所在的位置必定是一个坑,此时再把key值填入次坑;

在这里插入图片描述

[单次排序示意图]
//单次排序
int PartSort(int* a, int left, int 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;
	return hole;
}

其递归的思路都一致;


前后指针法

前后指针,顾名思义即为前后两个指针进行遍历;
其思路为:

设指针keyi,指针prev与指针cur,用cur指针进行遍历,当cur所在位置小于基准值key值时,
prev指针先++,而后将cur指针所在的数据与prev所在的数据进行交换;
当cur大于right时,则停止遍历,将此时prev所在位置的值与key值进行交换;
此时key值左边的数小于key,右边则大于key;

在这里插入图片描述

[单次排序示意图]
//单趟排序
int PartSort(int* a, int left, int right)
{

	int keyi = left;
	int prev = left;
	int cur = left + 1;

	while (cur<=right) {
		
		if (a[cur] < a[keyi]) {
			prev++;
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	swap(&a[prev], &a[keyi]);
	return prev;
}

该方法的递归思路与上面两种方式相当;


三数取中优化

快速排序,是基于在综合性能以及综合使用场景情况下速度较快,效率较高才之所以叫做快速排序;
当然在一定情况下,快速排序依旧有短板;

在一般情况下,综合来说,快速排序的时间复杂度为O(N*logN),而不排除一些极端的情况;
即数据本身就为有序的情况;

在这个情况下,由于上述选基准值key值的方式都是为数据中最左边或者最右边的数据,而若是数据本身有序的情况;
快速排序的时间复杂度将会为O(N2);

在这里插入图片描述

由于key值每次都为最小值,导致快速排序遍历速度更慢,最终时间复杂度将会为O(N2);

在这里插入图片描述

针对该种情况,有人设计了一种情况,即为将key值取头尾中间三个数据中,既不是最大也不是最小的值;

int GetMidIndex(int* a, int left, int right) {
	int mid = (left + right) / 2;
	if (a[mid] > a[left]) {
		if (a[right] > a[mid]) {
			return mid;
		}
		else if (a[left] > a[right]) {
			return left;
		}
		else return right;
	}
	else // a[left]>a[mid]
	{
		if (a[right] > a[left]) {
			return left;
		}
		else if (a[mid] > a[right]) {
			return mid;
		}
		else return right;
	}
}

当每次单趟排序时,设置一个指针mid指向中间位置,将该中间值与最左以及最右进行三数取中并返回下标;
再将它与key值进行交换;

//使用hoare原生版本的单趟排序作为演示
int PartSort(int* a, int left, int right)
{
	int mid = GetMidIndex(a,left,right);
	swap(&a[left], &a[mid]);
	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]);
	return left;
}

该方法的优化解决了快速排序中遇到的有序情况;


随机数取key优化

由于三数取中是能解决一部分办法,但是并没有完全解决,有些题目的测试用例对于专门三数取中的优化还是并不能完全解决;
因此在此基础上有出现了一个为随机数取key的优化;
即为将数据中[left,right]这段区间中随机选一个数做key;

//使用hoare原生版本的单趟排序作为演示
int PartSort(int* a, int left, int right)
{
	int randi = left+rand()%(right-left);//在使用rand函数前需先调用srand函数
	swap(&a[left], &a[randi]);
	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]);
	return left;
}

三路划分版

三路划分版本的快速排序针对一些重复率较高的情况:

[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,]

若是遇到该种情况,三数取中与随机数取key的方法将会再次失效,时间复杂度也会再度来到 O(N2)的极端情况;
则在该种情况下,又有人想出了一种对应的方法,这个方法即为三路划分法;
该方法的思想为:

将原本的[left,keyi-1]keyi[keyi+1,right]区间;
划分为[<key][==key][>key]三个区间;

在这里插入图片描述
思路为:

使用三数取中的方式选取一个适和的数据作为key值并将key值进行保存(保存的为数据不为下标),
设指针cur指向left所指数据的后一个位置,即cur = left+1;
当cur指针不大于最右边界的情况进行循环遍历;
当cur所指向的数据小于key值时,则交换left指向的数据与cur指向的数据,并将两个指针都向后走一步;
当cur所指向的数据等于key值时,则不进行交换,cur继续遍历;
当cur指向的数据大于key值时,将cur所指向的数据与right所指向的数据进行交换,同时right–;
此时cur指针保存不懂避免交换过后cur所指向的数据小于key值;

void QuitSort(int* a, int begin, int end) 
{
	if (begin >= end) {
		return;
	}
	int left = begin;
	int right = end;
	int mid = GetMidIndex(a, left, right);
	swap(&a[mid], &a[left]);
	int key = a[left];
	int cur = left+1;


	while (cur <= right) {

	    if (a[cur] < key) {
		swap(&a[left], &a[cur]);
		cur++, left++;
	}
		else if (a[cur] > key) {
			swap(&a[right], &a[cur]);
			right--;
		}
		
		else {
			cur++;
		}
	}
	QuitSort(a,begin, left - 1);
	QuitSort(a, right+1, end);
}

非递归

快速排序除了递归以外同时也存在非递归的写法,
非递归的快速排序依靠为栈或者队列;
基本思想为:
将每次需要处理的区间进行入栈(注意栈的顺序为LIFO),
出栈后将出栈的区间进行处理(处理采用单趟排序处理);
当栈不为空的时候说明栈内仍存在区间需要进行出栈并处理,反复直到区间不存在或是区间内只有一个数时则不进行处理;
在这里插入图片描述
以改图为例,按照顺序先进[6,0];
再出栈[0,6]并将这段区间进行处理;
处理过后返回keyi,即区间又可以划分为[left,keyi-1]keyi[keyi+1,right];
如此循环;

void QuiteSortNonR(int* a, int begin, int end) {
	ST st;
	InitStack(&st);
	/*
	*第一次入栈整段区间
	*并在循环中进行处理(循环将会返回keyi值)
	*/
	StackPush(&st, end);
	StackPush(&st, begin);

	while (!StackEmpty(&st)) {
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		int key = PartSort(a, left, right);
		if (key + 1 < right) {
			StackPush(&st,right);
			StackPush(&st, key+1);
		}
		if (key - 1 > left) {
			StackPush(&st, key - 1);
			StackPush(&st, left);
		}
	}
	Destruc(&st);//销毁栈
}

稳定性

  • 综合上述来说,快速排序并不是一个十分稳定的排序,它在综合情况以及使用场景之中,总体效率都比较不错
    但是若是遇到比较极端的情况下却不能很好解决,如有序的情况,有序的情况下一些排序将会停止排序,而快速排序若是在这种
    情况下用三数取中的方式进行处理,仍会有一定的开销;
    故快速排序不稳定;

归并排序

归并排序,是一种基于归并操作的排序算法,该算法与快速排序算法一样采用了分治法的排序;
基本思想为:
将两个已经有序的子序列进行合并,得到一个完全有序的序列;
要使两个子序列有序,一样要将子序列中的子序列变得有序;将问题化为子问题;

在这里插入图片描述

分治思路示意图

递归

递归的思路即为上述思路,
相应的思路可以参考二叉树中的后序遍历;
具体步骤为:

开出一块与数据内容相应大小的空间temp,这块空间用来进行每两组之间的归并;
以上图为参考,因为每个子区间中的数据不一定都是有序的;
所以要使用后序遍历的思路,最终将数据分为单个数据(单个数据可以看作有序);
使用归并的思路将数据归并至temp中;
最终再每次递归结束时将数据拷贝回原数组;

//递归
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 left1 = begin, right1 = mid;
	int left2 = mid + 1, right2 = end;
	int i = begin;

	//使用归并的思路将数据依次放至tmp数组中
	while (left1 <= right1 && left2 <= right2) {
		if (a[left1] < a[left2]) {
			tmp[i++] = a[left1++];
		}
		else//a[left1]>=a[left2]
			tmp[i++] = a[left2++];
	}
	//但凡两个其中一个结束
	//将剩余数据依次拷贝至tmp数组
	while (left1 <= right1) {
		tmp[i++] = a[left1++];
	}
	
	while (left2 <= right2) {
		tmp[i++] = a[left2++];
	}

	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("MergeSort::malloc fail");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

非递归

相比于递归不同,非递归比递归所需要在意的细节更多;
但是思路上大体相同;
与快排不同,归并排序的非递归并不需要用到栈或者队列;
归并排序的非递归大致的思路为:

设一个gap为间距值,gap一开始为1,且gap在每次归并过一次时*=2;
当gap为1时实现一一归并,
当gap为2时实现两两归并,以此类推;

void MergeSortNonR(int* a, int n/*数据个数*/)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL) {
		perror("MergeSort::malloc fail");
		exit(-1);
	}
	int gap = 1;
	//1  2  4  8
	while (gap < n) {
		for (int j = 0;j<n; j += 2 * gap) {
			int left1 = j, right1 = j + gap - 1;
			int left2 = j + gap, right2 = j + 2 * gap - 1;
			int i = j;
			

			while (left1 <= right1 && left2 <= right2) {

				if (a[left1] < a[left2]) {
					tmp[i++] = a[left1++];
				}

				else//a[left1]>=a[left2]

					tmp[i++] = a[left2++];

			}//单反两个其中一个结束

			while (left1 <= right1) {
				tmp[i++] = a[left1++];
			}

			while (left2 <= right2) {
				tmp[i++] = a[left2++];
			}
		}
		memcpy(a , tmp , sizeof(int) * n);
		gap *= 2;
	}
	free(tmp);

}

该段代码可以完美实现上图中所对应的数据所给的排序;
但是这个方法的缺陷特别明显;

缺陷:

  1. 当数据量不为2n时,将会有越界风险,即gap是一个不是很好控制的因素所以相应的需要进行边界的修正;
  2. 当数据在拷贝时,由于在控制边界之后,有些数据不能直接转入temp空间,导致空间中存在随机值,若是盲目拷贝整块空间将会出现随机数覆盖原有效数组的风险;

根据相应的缺陷需要制定出有效的措施;


调整边界

既然gap不能很好的控制调整,那就在遍历的过程中调整边界以达到不会越界;
从上述情况种可以了解到在非递归种共有四个指针,分别为left1,right1,left2与right2;
而非递归中越界的情况分为3种;

right1,left2与right2都越界

在这里插入图片描述

left2与right2越界

在这里插入图片描述

right2越界

在这里插入图片描述
相应的解法即为修改边界,当right1或者left2越界时,
即表示该段区间后的数据不需要进行归并,若是归并则会越界;
当right2越界时,则将right2的边界改为n-1;

void MergeSortNonR(int* a, int n/*数据个数*/)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL) {
		perror("MergeSort::malloc fail");
		exit(-1);
	}
	int gap = 1;
	//1  2  4  8
	while (gap < n) {
		for (int j = 0;j<n; j += 2 * gap) {
			int left1 = j, right1 = j + gap - 1;
			int left2 = j + gap, right2 = j + 2 * gap - 1;
			int i = j;
			
			//right1 left2 right2 全部越界
			//left2 right2 越界
			//right2
			
			//修正前
			if (right1 >= n || left2 >= n){
				break;
			}

			if (right2 >= n) {
				right2 = n - 1;
			}

			//修正后

			while (left1 <= right1 && left2 <= right2) {

				if (a[left1] < a[left2]) {
					tmp[i++] = a[left1++];
				}

				else//a[left1]>=a[left2]

					tmp[i++] = a[left2++];

			}//两个其中一个结束

			while (left1 <= right1) {
				tmp[i++] = a[left1++];
			}

			while (left2 <= right2) {
				tmp[i++] = a[left2++];
			}
		}
		memcpy(a, tmp, sizeof(int)n);
		gap *= 2;
	}
	free(tmp);


单次归并单次拷贝

第二个缺点的解决方法即为,每次归并结束后,都将这段归并的数据拷贝回原数组,归并一段拷贝一段;
即不会出现随机数覆盖原有效数据的问题;

最终代码:

void MergeSortNonR(int* a, int n/*数据个数*/)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL) {
		perror("MergeSort::malloc fail");
		exit(-1);
	}
	int gap = 1;
	//1  2  4  8
	while (gap < n) {
		for (int j = 0;j<n; j += 2 * gap) {
			int left1 = j, right1 = j + gap - 1;
			int left2 = j + gap, right2 = j + 2 * gap - 1;
			int i = j;
			
			//right1 left2 right2 全部越界
			//left2 right2 越界
			//right2
		

			if (right1 >= n || left2 >= n){
				break;
			}

			if (right2 >= n) {
				right2 = n - 1;
			}


			while (left1 <= right1 && left2 <= right2) {

				if (a[left1] < a[left2]) {
					tmp[i++] = a[left1++];
				}

				else//a[left1]>=a[left2]

					tmp[i++] = a[left2++];

			}

			while (left1 <= right1) {
				tmp[i++] = a[left1++];
			}

			while (left2 <= right2) {
				tmp[i++] = a[left2++];
			}

			memcpy(a + j, tmp + j, sizeof(int) * (right2 - j + 1));
			//     0        0                     1      2
			//     1        1                      3     
		}
		gap *= 2;
	}
	free(tmp);

}

时间复杂度

  • 归并排序的时间复杂度为O(N*logN),但是以综合性能的话,略逊快排一点,但无伤大雅;

空间复杂度

  • 归并排序的缺点之一即为空间复杂度,在归并的过程中的开销并不多,但由于归并排序需要
    开一段大小为N的空间,故空间复杂度为O(N);

稳定性

  • 稳定

总结

排序算是一种较为基本的且需要掌握的算法,优秀的排序算法能在程序中使得效率倍式的提升;
不同的排序算法可以根据不同的使用场景分别进行使用;
排序中较为难的点为一些排序算法中的边界问题,以及递归中分治思路的理解;

在类似快速排序样式的排序中依旧可以再进行小区间优化,
即在递归到一定数据量时使用稳定的且效率还行的非复杂排序对数据进行排序;
小区间优化的目的是为了减少排序过程中递归调用的次数从而达成效率的优化;

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

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

相关文章

Vue 组件开发总结

Vue 组件开发思路 1. 组件划分 首先&#xff0c;你需要明确定义组件的划分。将大型界面划分为小型、可重用的组件是一个关键步骤。这有助于提高代码的可维护性和可复用性。 2. 组件设计 在设计组件时&#xff0c;考虑组件的输入&#xff08;props&#xff09;和输出&#xf…

Redis_注册为服务

Redis注册服务 1、windowsR ---->services.msc 先查看服务中是否存在redis服务 不存在的话就找到redis解压目录 输入redis-server --service-install&#xff0c;展示如下即为成功 查看服务 此时已经注册成功服务。 卸载服务 使用redis-server --service-uninst…

攻防演练篇 | 企业安全运营之攻防演练——以攻促防

随着互联网技术的发展和企业信息化程度的提高&#xff0c;企业面临的网络安全威胁越来越多。**为了保护企业的信息安全&#xff0c;攻防演练已经成为企业安全运营中不可或缺的一部分。**攻击者通常会利用各种方法来破坏企业的安全系统和数据&#xff0c;因此企业需要像攻击者一…

蓝桥杯 题库 简单 每日十题 day9

01 特殊年份 问题描述 今年是2021年&#xff0c;2021这个数字非常特殊&#xff0c;它的千位和十位相等&#xff0c;个位比百位大1&#xff0c;我们称满足这样条件的年份为特殊年份。输入5个年份&#xff0c;请计算这里面有多少个特殊年份。 输入格式 输入5行&#xff0c;每行一…

高效管理体验?试试docker registry连接

Linux 本地 Docker Registry本地镜像仓库远程连接 文章目录 Linux 本地 Docker Registry本地镜像仓库远程连接1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址…

C++之list成员函数应用总结(二百三十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【pdf密码】打开PDF文件之后发现不能编辑,什么原因?

打开PDF文件的时候&#xff0c;没有提示带有密码&#xff0c;但是打开文件之后发现没有办法编辑PDF文件&#xff0c;这个是因为PDF文件设置了限制编辑&#xff0c;我们需要将限制取消才能够编辑文件。 那么&#xff0c;我们应该如何取消密码&#xff0c;编辑文件呢&#xff1f…

redhat 6.1 测试环境安装 yum

redhat 6.1 测试环境安装 yum 记录 1. 新建虚拟机 1.1 自定义建立虚拟机 自定义创建新的虚拟机 选择硬件兼容性 创建空白硬盘&#xff0c;稍后选择 iso 文件创建系统。 选择操作系统类型 为虚拟机命名 选择处理器配置 选择虚拟机内存 选择虚拟机网络类型 选择…

零基础学JavaScript(二)ECMAScript 基础

一、变量 1. 我们JavaScript代码写在 script标签里面 2. 我们定义一个变量名字为name&#xff0c;它的值是“张三” 3. 打开开发者工具的控制台&#xff0c;查看打印结果 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"…

asp.net企业生产管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 企业生产管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语 言开发 二、功能介绍 (1)用户管理&…

java使用正则提取数据

一、正则提取文本指定数据 需要对一个json结构做数据的提取,提取label和value的值&#xff0c;组成新的结构&#xff0c;西瓜:0、苹果:1、草莓:2 原始json字符串如下格式 [{"label": "西瓜","value": 0},{"label": "苹果"…

http协议与tomcat

目录 引言 抓包 fiddler的基本使用及设置 HTTP请求 请求首行请求头空行正文 请求的首行方法URL版本号 ​编辑 响应首行响应头空行正文 响应的首行版本号状态码 URL(网址) url基本格式 urlencode 常见方法 get和post区别 认识请求"报头"(header) Host Content-Len…

Kubernetes的容器批量调度引擎 Volcano

一个用于高性能工作负载场景下基于Kubernetes的容器批量调度引擎 Volcano是在Kubernetes上运行高性能工作负载的容器批量计算引擎。 它提供了Kubernetes目前缺少的一套机制&#xff0c;这些机制通常是许多高性能 工作负载所必需的&#xff0c;包括&#xff1a; - 机器学习/深度…

某金融机构在数据安全及等保合规背景下的网络安全规划项目案例

前言 **近年来网络入侵、信息泄露以及网络病毒等事件频发&#xff0c;国家层面陆续出台多部数据安全相关法律法规&#xff0c;金融行业作为国家强监管的重点行业&#xff0c;参照上层法律法规起草发布了各类相关行业标准和规范。另外&#xff0c;结合笔者所在公司基础架构和信…

国产芯片ZT1826亮相IOTE展 以物联网技术助力行业数字化转型

9月20日&#xff0c;备受物联网行业瞩目的IOTE 2023第20届国际物联网展&#xff0c;在深圳宝安国际会展中心盛大开幕&#xff01;本届展会为期三天&#xff0c;从9月20日到22日&#xff0c;以“IoT构建数字经济底座”为主题&#xff0c;汇聚了全球超600家参展企业&#xff0c;参…

计算机毕业设计 智慧养老中心管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

简单线性回归(Simple Linear Regression)

简单线性回归(Simple Linear Regression) 简单线性回归(Simple Linear Regression)简介理解数据数据处理读取数据数据预览数据探索数据统计信息数据类型查看数据的直方图通过散点图查看数据的相关关系相关系数建立模型创建训练数据和测试数据建立简单线性回归模型查看回归方…

甜型葡萄酒取决的因素有哪些?

许多葡萄酒爱好者对干葡萄酒有明显的偏好&#xff0c;其中一些人甚至不喜欢甜葡萄酒&#xff0c;但这种拒绝意味着他们永远不会知道一些世界上最好的葡萄酒&#xff0c;如果你想了解更多关于甜酒的知识&#xff0c;你来对地方了。来自云仓酒庄雷盛红酒分享&#xff0c;云仓酒庄…

【2023研电赛】商业计划书赛道—总决赛二等奖:高安全性刷掌识别技术产业化

本文为2023年第十八届中国研究生电子设计竞赛商业计划赛道二等奖分享&#xff0c;参加极术社区的【有奖活动】分享2023研电赛作品扩大影响力&#xff0c;更有丰富电子礼品等你来领&#xff01;&#xff0c;分享2023研电赛作品扩大影响力&#xff0c;更有丰富电子礼品等你来领&a…

全球第4大操作系统(鸿蒙)的软件后缀.hap

system exe 2022-12-01 04:38:38 首页 > 操作系统 145|0条评论 鸿蒙OS兼容已有安卓程序&#xff1a;这事不稀奇。 其实一个系统兼容另外系统的可执行程序并非新鲜事&#xff0c;比如linux下的wine和crossover可以兼容许多win系统的.exe程序。 作为回应&#xff0c;Wind…