数据结构 | 超详细讲解七大排序(C语言实现,含动图,多方法!)

news2024/11/6 3:50:02

目录

​编辑

排序的概念

常见排序算法

​编辑

1.冒泡排序

🍹图解

🥳代码实现

🤔时间复杂度

2.插入排序

🍹图解

🌴深度剖析

🍎代码思路

🥳代码实现

🤔时间复杂度

3.希尔排序

🌴深度剖析

🍎代码思路

🍋思考:关于gap的取值问题

🥳代码实现

🤔时间复杂度

4.堆排序

⛱️请看我的另一篇文章:详解堆排序

5.选择排序

🍹图解

🌴深度剖析

🥳代码实现

🤔时间复杂度

6.快速排序

🍹图解1:霍尔法

🌴深度剖析

🍎代码思路

🍎优化1:改变选key策略,采用三数选中法

🍎优化2:小区间优化,采用其它排序方法,减少递归次数

🍋思考:如何保证相遇位置比key小?

🥳代码实现

🍹图解2:前后指针法

 🌴深度剖析

🍎代码思路

🍎优化:避免自己和自己交换

🥳代码实现

🍹非递归实现

🍎代码思路

🥳代码实现

7.归并排序

🍹图解

🌴深度剖析

🍹递归版

🍎代码思路

🥳代码实现

🍹非递归版

🍎代码思路

🍋思考:上面的思路只能解决数组大小为的数组,其余数组则会存在越界问题,如何解决?

🥳代码实现

🤔时间复杂度


排序的概念

排序 :所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性 :假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次
序保持不变,即在原序列中, r[i]=r[j] ,且 r[i] r[j] 之前,而在排序后的序列中, r[i] 仍在 r[j] 之前,则称这种排
序算法是稳定的;否则称为不稳定的。
内部排序 :数据元素全部放在内存中的排序。
外部排序 :数据元素太多不能同时放在内存中,根据排序过程的要求不断地在内外存之间移动数据的排序。

常见排序算法

下文将会细细介绍上图中七种排序,观看前,可以点一个免费的赞与收藏支持作者~希望本篇博客能帮助到你!

1.冒泡排序

相邻两个数进行比较,大的数向后移,每次循环都能冒出一个大的数到数组最后,直至最后全部冒出。

🍹图解

🥳代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

void bubble_sort(int arr[],int sz)
{
	int flag = 1;//优化
	int i = 0;
	for (i = 0; i < sz - 1; i++) {
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) {
				flag = 0;
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		if (flag == 1) {
			break;
		}
	}
}


int main() {
	int n = 0;
	int arr[] = {2,6,9,3,6,9,1};
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	for (n = 0; n < sz - 1; n++) {
		printf("%d", arr[n]);
	}

	return 0;
}

🤔时间复杂度

O(N^2)

2.插入排序

🍹图解

🌴深度剖析

🍎代码思路

上图为基本的插入排序,可对其进行优化:依次遍历找出最大值和最小值索引。

代码思路:1.设变量mini,maxi分别为最小值和最大值索引

                     设begin,end分别无序部分的首尾索引。

                  2.遍历无序部分,找出最小值和最大值的索引mini,maxi

                  3.将a[begin]和a[mini]进行交换,将a[end]和a[maxi]进行交换

                     注意:两次交换的中间需要进行依次判断,判断maxi是否仍然等于begin,因为经过第一个交换后原begin位置的值已经交换到mini位置去了,如果判断成立,maxi也应该跟随原begin的值的移动移动到mini位置。

                  4.此时begin、end处已经属于有序部分,begin++,end--,,更新无序部分的范围。

                 5.对剩余无序部分重复上述步骤,直到begin==end。

🥳代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void selectsort(int* a, int n) {
	int begin = 0;
	int end = n - 1;
	while (begin < end) {
		int mini = begin;
		int 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[mini], &a[begin]);
		if (maxi == begin)//begin此时被mini换走了
			maxi = mini;
		swap(&a[maxi], &a[end]);
		begin++;
		end--;
	}
	

}




int main() {
	
	int a1[8] = { 9,1,2,5,7,4,6,3};
	selectsort(a1,8);
	//int a2[5] = { 3,2,5,6,7 };
	//selectsort(a2, 5);
	for (int i = 0; i < 8; i++) {
		printf("%d ", a1[i]);
	}
	return 0;
}

🤔时间复杂度

O(N^2)

3.希尔排序

🌴深度剖析

🍎代码思路

希尔排序是在插入排序的基础上进行优化的。

1.预排序:将数组分为gap组,进行预排序(让数组接近有序)

2.插入排序(此时,数组近乎有序,使用插入排序效率极高

即蓝色的为一组,红色的为一组,绿色为一组,对每组进行插入排序

以下是预排序的代码:

//预排序过程,假设gap=3
//第一种写法:依次对三组进行预排序
for(int j=0;j<gap;j++){   //依次对三组进行预排序
for (int i = gap; i < n; i+=gap) {  //对一组进行预排序的过程,由于一组内部元素之间相距gap,所以应该是i+=gap

				int end = i;

				int tmp = a[end];

				while (end >= gap) {

					if (tmp < a[end - gap]) {

						a[end] = a[end - gap];

						a[end - gap] = tmp;

						end -= gap;

					}

					else {

						break;

					}


                   } 
				}
//第二种写法:同时进行三组的预排序(但效率相较上一种写法其实没有改变)
for (int i = gap; i < n; i++) {//先对蓝组排1下,再对红组排1下,再对绿组排1下,如此循环

				int end = i;

				int tmp = a[end];

				while (end >= gap) {

					if (tmp < a[end - gap]) {

						a[end] = a[end - gap];

						a[end - gap] = tmp;

						end -= gap;

					}

					else {

						break;

					}



				}

🍋思考:关于gap的取值问题

gap越大:大的数字越快跳到后面,小的数字越快跳到前面,结果越不接近有序。

gap越小:跳得越慢,但结果越接近有序(gap==1时,相当于插入排序)

解决方案:走多组gap,gap>1时就是预排序

                                      gap==1时就是插入排序

gap我们通常设置为gap=gap/3+1

这里+1是为了保证gap>=1

🥳代码实现

//1.预排序,分成gap组进行

//2.插入排序

void ShellSort(int* a, int n) {

	int gap = n;

	while (gap>1) {

		 gap = gap / 3 + 1;//保证gap>=1

			for (int i = gap; i < n; i++) {

				int end = i;

				int tmp = a[end];

				while (end >= gap) {

					if (tmp < a[end - gap]) {

						a[end] = a[end - gap];

						a[end - gap] = tmp;

						end -= gap;

					}

					else {

						break;

					}



				}

			}

 

	}



}

🤔时间复杂度

4.堆排序

⛱️

请看我的另一篇文章:详解堆排序

5.选择排序

🍹图解

🌴深度剖析

基本思路:遍历一遍,选择最小的插到最左边

优化思路:遍历一遍,选出最小的最大的分别插到最左最右

一个小坑:将最大值放到最后后,可能破坏了原本的

🥳代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void selectsort(int* a, int n) {
	int begin = 0;
	int end = n-1 ;
	
	while (begin < end) {
		int mini = a[begin];
		int maxi = a[end];
		for (int i = begin; i <= end; i++) {
			if (a[i] < mini) {
				mini = a[i];
				swap(&a[i], &a[begin]);

			}
			if(a[i]>maxi) {
				maxi = a[i];
				swap(&a[i], &a[end]);
			}
			//验证
			if (a[i] <= mini) {
				mini = a[i];
				swap(&a[i], &a[begin]);

			}
		}
		begin++;
		end--;
	}

}



int main() {
	
	int a1[8] = { 9,1,2,5,7,4,6,3};
	selectsort(a1,8);
	//int a2[5] = { 3,2,5,6,7 };
	//selectsort(a2, 5);
	for (int i = 0; i < 8; i++) {
		printf("%d ", a1[i]);
	}
	return 0;
}

🤔时间复杂度

O(N^2)

6.快速排序

🍹图解1:霍尔法

🌴深度剖析

🍎代码思路

我们先考虑单趟
    end,begin相遇前end向前走找比key小的值begin向后走找比key大的值,都找到后两者进行交换
    end,begin相遇时:循环停止,交换begin和key

  单趟过后的结果:

key所在位置一定是正确的位置[left,key-1]中的值一定小于key,[key+1,right]中的值一定大于key

//单趟(一定先走end,再走begin)
int begin = left;
	int end = right;
	int key = left;
	while (begin < end) {
		while (a[end] >= a[key] && begin < end) {
			end--;
		}
		while(a[begin] <= a[key]&&begin<end) {
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[key]);
key = begin;

此时整个数组可以划分为三个部分:[left,key-1],key,   [key+1,right]

递归:接着就用递归思想对[left,key-1][key+1,right]中的值进行排序
  递归结束条件数组不存在或者只有1个元素

🍎优化1:改变选key策略,采用三数选中法

当数组有序排列时,且数据量较大时,基础版快排可能出现栈溢出问题

解决办法:改变选key的策略,采用三数选中的方法,使key不要老是为最小值,而尽量趋于中值。

即确定出三个索引,如left,right, (right-left)/2,选出三个索引对应的值为中值的索引。

//优化1:改变选key的策略,采用三数选中法
int FindMid(int* a, int left, int right) {
	int mid = (left + right) / 2;
	if (a[left] < a[right]) {
		if (a[left] < a[mid]) {
			if (a[mid] < a[right]) {//a[left]<a[mid]<a[right]
				return mid;
			}
			else {//a[left]<a[right]<a[mid]
				return right;
			}
		}
		else {//a[mid]<a[left]<a[right]
			return left;
		}
	}
	else {//a[left] > a[right]
		if (a[mid] > a[right]) {
			if (a[mid] > a[left]) {//a[mid] > a[left]> a[right]
				return left;
			}
			else {
				return mid;
			}

		}
		else {//a[left] > a[right]>a[mid]
			return right;
		}


	}
}

🍎优化2:小区间优化,采用其它排序方法,减少递归次数

冒泡排序、选择排序效率太一般,希尔排序更适合处理数据量更大的数据,此时的数据已经较为接近有序,此处采用插入排序。

	//优化:小数区间,采取选择排序
	if (left + 5 >= right) {
		InsertSort(a+left, right - left + 1);
		return;
	}
	

🍋思考:如何保证相遇位置比key小?

左边做key,右边先走,可以保证相遇位置比key小。

相遇场景分析:

begin遇end:end先走,停下来,一定是因为遇到了比key小的值。

                      begin再走,begin没有找到大的遇到end就停下了。

end遇begin:end走,end没有找到小的遇到begin就停下了。

                      而begin的位置此时还是上一轮交换的位置,而上一轮交换,把比key小的值换到了begin的位置。

🥳代码实现

1.基础版(递归)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//递归版
void Swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void QuickSort(int* a, int left, int right) {
	//先考虑单趟
	//end找比key小,begin找比key大,都找到后交换
	//end,begin相遇时停止,交换begin和key
	//单趟过后的结果:key所在位置一定是正确的位置,a[left,key-1]一定小于key,a[key+1,right]一定大于key
	//接着就用递归思想处理:a[left,key-1],a[key+1,right]
	//递归结束条件:数组不存在或者只有1个元素
	if (left >= right) {
		return;
	}
	
	int begin = left;
	int end = right;
	int key = left;
	while (begin < end) {
		while (a[end] >= a[key] && begin < end) {
			end--;
		}
		while(a[begin] <= a[key]&&begin<end) {
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[key]);
	key = begin;//此时key需要改变
	QuickSort(a, left, key-1);
	QuickSort(a, key + 1,  right);
}




int main() {
	int a[10]={ 6, 1, 2, 7, 9, 3, 4, 5, 10,8 };
	QuickSort(a, 0, 9);
	for (int i = 0; i < 10; i++) {
		printf("%d ", a[i]);
	}
	return 0;
}

2.优化版(递归)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//递归版
void Swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//优化1:改变选key的策略,采用三数选中法
int FindMid(int* a, int left, int right) {
	int mid = (left + right) / 2;
	if (a[left] < a[right]) {
		if (a[left] < a[mid]) {
			if (a[mid] < a[right]) {//a[left]<a[mid]<a[right]
				return mid;
			}
			else {//a[left]<a[right]<a[mid]
				return right;
			}
		}
		else {//a[mid]<a[left]<a[right]
			return left;
		}
	}
	else {//a[left] > a[right]
		if (a[mid] > a[right]) {
			if (a[mid] > a[left]) {//a[mid] > a[left]> a[right]
				return left;
			}
			else {
				return mid;
			}

		}
		else {//a[left] > a[right]>a[mid]
			return right;
		}


	}
}


void InsertSort(int* a, int n) {
	for (int i = 1; i < n ; i++) {
		int end = i;
		int tmp = a[end];
		while (end > 0) {
			if (tmp < a[end - 1]) {
				a[end] = a[end - 1];
				a[end - 1] = tmp;
				end--;
			}
			else {
				break;
			}
		}
	}
}

void QuickSort(int* a, int left, int right){
	//先考虑单趟
	//end找比key小,begin找比key大,都找到后交换
	//end,begin相遇时停止,交换begin和key
	//单趟过后的结果:key所在位置一定是正确的位置,a[left,key-1]一定小于key,a[key+1,right]一定大于key
	//接着就用递归思想处理:a[left,key-1],a[key+1,right]
	//递归结束条件:数组不存在或者只有1个元素
	
	/*if (left >= right) {
		return;
	}*/
	//优化:小数区间,采取选择排序
	if (left + 5 >= right) {
		InsertSort(a+left, right - left + 1);
		return;
	}
	
	int begin = left;
	int end = right;
	//优化:改变选key的策略,采用三数选中法
	int key = FindMid(a+left, left, right);
	Swap(&a[begin], &a[key]);
	key = begin;
	//int key = begin;
	while (begin < end) {
		while (a[end] >= a[key] && begin < end) {
			end--;
		}
		while(a[begin] <= a[key]&&begin<end) {
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[key]);
	key = begin;//此时key需要改变
	QuickSort(a, left, key-1);
	QuickSort(a, key + 1,  right);
}


int main(){
	int a[10]={ 6, 1, 2, 7, 9, 3, 4, 5, 10,8 };
	QuickSort(a, 0, 9);
	for (int i = 0; i < 10; i++) {
		printf("%d ", a[i]);
	}
	return 0;
}

🍹图解2:前后指针法

 🌴深度剖析

🍎代码思路

仍然从单趟开始分析:

1.

2.判断cur指针指向的数据是否小于key:

  小于——prev后移一位,交换cur和prev指向的内容,cur指针后移一位

大于——cur后移一位,效果:使得prev和cur之间的值全是大于key的值

3.当cur越界,将prev指向的内容与key进行呼唤

效果:key左边的数据都比key小,key右边的数据都比key大

由于快慢指针法单趟后的效果和霍尔法其实是一致的,后续步骤就和霍尔法的步骤一模一样。

🍎优化:避免自己和自己交换

if (a[cur] < a[key] && ++prev != cur) {//优化:当++prev与cur重叠时,就不进行交换
			Swap(&a[cur], &a[prev]);
		}

🥳代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void Swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//优化1:改变选key的策略,采用三数选中法
int FindMid(int* a, int left, int right) {
	int mid = (left + right) / 2;
	if (a[left] < a[right]) {
		if (a[left] < a[mid]) {
			if (a[mid] < a[right]) {//a[left]<a[mid]<a[right]
				return mid;
			}
			else {//a[left]<a[right]<a[mid]
				return right;
			}
		}
		else {//a[mid]<a[left]<a[right]
			return left;
		}
	}
	else {//a[left] > a[right]
		if (a[mid] > a[right]) {
			if (a[mid] > a[left]) {//a[mid] > a[left]> a[right]
				return left;
			}
			else {
				return mid;
			}

		}
		else {//a[left] > a[right]>a[mid]
			return right;
		}


	}
}


void InsertSort(int* a, int n) {
	for (int i = 1; i < n; i++) {
		int end = i;
		int tmp = a[end];
		while (end > 0) {
			if (tmp < a[end - 1]) {
				a[end] = a[end - 1];
				a[end - 1] = tmp;
				end--;
			}
			else {
				break;
			}
		}
	}
}




void QuickSort2(int* a, int left, int right) {
	if (left + 5 >= right) {
		InsertSort(a + left, right - left + 1);
		return;
	}


	//优化:改变选key的策略,采用三数选中法
	int key = FindMid(a + left, left, right);
	Swap(&a[left], &a[key]);
	key = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right) {
		if (a[cur] < a[key] && ++prev != cur) {//优化:当++prev与cur重叠时,就不进行交换
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[key], &a[prev]);
	key = prev;//此时key需要改变
	QuickSort2(a, left, key - 1);
	QuickSort2(a, key + 1, right);
}




int main(){
	int a[10] = { 6, 1, 2, 7, 9, 3, 4, 5, 10,8 };
	QuickSort2(a, 0, 9);
	for (int i = 0; i < 10; i++) {
		printf("%d ", a[i]);
	}
	return 0;
}

🍹非递归实现

🍎代码思路

利用栈来模拟递归的过程,假设每次key值都刚好二分。

1.初始化一个栈,将right和left压入栈中

1.对数组进行单趟快速排序,得到[left,key-1],key,[key+1,right]

2.设begin1=left,end1=key-1

   设begin2=key+1,end2=right

3.若begin2<end2,将end2,begin2压入栈中

若begin1<end1,将end1,begin1压入栈中

4.取并删除栈顶元素两次,得到begin1,end1,对[begin1,end1]数组进行单趟快速排序

5.重复步骤2,3,4,栈为空时循环结束

.

🥳代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "stack.h"

void Swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}


void QuickSort(int* a, int left, int right) {
	Stack st;
	StackInit(&st);

	StackPush(&st, right);
	StackPush(&st, left);
	while (!StackEmpty(&st)) {
		left = StackTop(&st);
		StackPop(&st);
		right = StackTop(&st);
		StackPop(&st);
		int begin = left;
		int end = right;
		int key = begin;
		while (begin < end) {
			while (a[end] >= a[key] && begin < end) {
				end--;
			}
			while (a[begin] <= a[key] && begin < end) {
				begin++;
			}
			Swap(&a[begin], &a[end]);
		}
		Swap(&a[begin], &a[key]);
		key = begin;//此时key需要改变

		int begin1 = left;
		int end1 = key - 1;
		int begin2 = key + 1;
		int end2 = right;
		if (begin2 < end2) {
			StackPush(&st, end2);
			StackPush(&st, begin2);
		}
		if (begin1 <end1) {
			StackPush(&st, end1);
			StackPush(&st, begin1);
		}
	}
	StackDestroy(&st);
	return;
}


int main() {
	int a[10] = { 6, 1, 2, 7, 9, 3, 4, 5, 10,8 };
	QuickSort(a, 0, 9);
	for (int i = 0; i < 10; i++) {
		printf("%d ", a[i]);
	}
	return 0;
}


7.归并排序

🍹图解

🌴深度剖析

基本思想:
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法
已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序核心步骤:

🍹递归版

🍎代码思路

假设数组可以被拆分成两个子数组:

比较begin1和begin2位置的值,

取小尾插到tmp,

被取指针向前移动

未被取指针,不动

但问题是:数组往往不能直接被拆成两个有序数组

因此,考虑继续细分数组直到有序(比如只有1个数时必定有序)

然后将有序子数组一层层的合并回去,每次合并完将结果拷贝回原数组。

这个过程有点类似后序遍历。

🥳代码实现

void MergeSort1(int* a, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL) {
		perror("tmp malloc fail!");
		return;
	}
	if (n <= 1) {//当数组被拆分成单个数,或无法继续拆分,返回
		return;
	}
	int mid = (n+1)/ 2;//假设数组3个数,mid==2,
	MergeSort1(a, mid);//[0,mid-1]有序
	MergeSort1(a+mid, n-mid);//[mid,n-1]有序
	//合并两个有序数组
	int begin1 = 0;
	int end1 = mid - 1;
	int begin2 = mid;
	int end2 = n - 1;
	int tmp1 = 0;
	while (begin1 <=end1 && begin2 <= end2) {
		if (a[begin1] < a[begin2]) {//begin1的数比begin2小,尾插begin1
			tmp[tmp1] = a[begin1];
			begin1++;
			tmp1++;
		}
		else {
			tmp[tmp1] = a[begin2];
			begin2++;
			tmp1++;
		}
	}
	//循环结束,说明有一方指针已经走完
	//将另一方未走完指针走完
	while (begin1 <=end1) {
		tmp[tmp1] = a[begin1];
		begin1++;
		tmp1++;
	}

	while (begin2 <=end2) {
		tmp[tmp1] = a[begin2];
		begin2++;
		tmp1++;
	}
	//此时的tmp数组为有序数组
	//拷贝回原数组
	memcpy(a, tmp, sizeof(int) * n);
}

🍹非递归版

🍎代码思路

1.首先设一个变量gap代表每组需要归并的个数。

2.划分两个大小为gap的子数组[begin1,end1],[begin2,end2],这两个数组应当是有序的,对其进行归并,归并完后,继续向后划分两个大小为gap的子数组,继续归并,直到整个数组被遍历完。

3.遍历完一次数组意味着以gap*2为大小的子数组已经有序,因此gap*=2,以新gap数,继续完成新一轮对数组的归并遍历。直到gap>=2,结束。

🍋思考:上面的思路只能解决数组大小为2^n的数组,其余数组则会存在越界问题,如何解决?

这是一个数据个数为10的数组:

打印每轮的合并情况,可发现,有些位置发生了越界:

将图示越界的位置抽象出来,即为:

解决方案:

判断begin2是否存在,若不存在,则结束归并,若存在则修正end2,使其不越界。

🥳代码实现

void MergeSort2(int* a, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL) {
		perror("tmp malloc fail!");
		return;
	}
	if (n <= 1) {//当数组被拆分成单个数,或无法继续拆分,返回
		return;
	}
	//后序遍历
	//合并两个有序数组
	int gap = 1;//每组需要归并的个数
	while(gap<n){//层序遍历
		for(int i=0;i<n;i+=2*gap){
			int begin1 = i;
			int end1 = i+gap-1;
			int begin2 = i+gap;
			int end2 = i+2*gap-1;
			int j = i;
			if (begin2 >= n) {
				break;
			}
			if (end2 >= n) {
				end2 = n - 1;
			}
				while (begin1 <= end1 && begin2 <= end2) {
					if (a[begin1] < a[begin2]) {//begin1的数比begin2小,尾插begin1
						tmp[j++] = a[begin1];
						begin1++;
						
					}
					else {
						tmp[j++] = a[begin2];
						begin2++;
						
					}
				}
			//循环结束,说明有一方指针已经走完
			//将另一方未走完指针走完
			while (begin1 <= end1) {
				tmp[j++] = a[begin1];
				begin1++;
			}

			while (begin2 <= end2) {
				tmp[j++] = a[begin2];
				begin2++;
			}
			//此时的tmp数组为有序数组
			//拷贝回原数组
			memcpy(a+i, tmp+i, sizeof(int) * (end2-i+1));//易错点:(end2-begin1+1)是错的,因为begin1这个时候已经不再是子数组起点位置
		}
		gap *= 2;
	}
	
}




int main() {
	int a1[8] = { 10,6,7,1,3,9,4,2 };
	int a2[16] = { 16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1 };
	int a3[12] = { 12,11,10,9,8,7,6,5,4,3,2,1 };
	MergeSort2(a1, 8);
	for (int i = 0; i < 8; i++) {
		printf("%d ", a1[i]);
	}
	printf("\n");
	MergeSort2(a2, 16);
	for (int i = 0; i < 16; i++) {
		printf("%d ", a2[i]);
	}
	MergeSort2(a3, 12);
	printf("\n");
	for (int i = 0; i < 12; i++) {
		printf("%d ", a3[i]);
	}

}

🤔时间复杂度

O(N*logN)

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

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

相关文章

【深度学习-第6篇】使用python快速实现CNN多变量回归预测(使用pytorch框架)

上一篇我们讲了使用CNN进行分类的python代码&#xff1a; Mr.看海&#xff1a;【深度学习-第5篇】使用Python快速实现CNN分类&#xff08;模式识别&#xff09;任务&#xff0c;含一维、二维、三维数据演示案例&#xff08;使用pytorch框架&#xff09; 这一篇我们讲CNN的多变…

【Linux】磁盘文件和软硬链接

上篇博客我们说了内存级文件&#xff0c;就是文件加载到内存中它的一些操作。那么不可能所有文件文件都要加载到内存中&#xff0c;大部分文件都要存在与一种可以永久性存储数据的硬件中&#xff0c;就是我们要说的磁盘。现在的笔记本电脑用的都是硬盘&#xff0c;你可以理解为…

C语言 io-文件拷贝

#include <stdio.h> int main(int argc, const char *argv[]) {//1文件拷贝到2文件FILE* fileAfopen(argv[1],"r");FILE* fileBfopen(argv[2],"w");if(NULLfileA){perror("fopen");return -1;}if(NULLfileB){perror("fopen");re…

【Vue】scoped解决样式冲突

默认情况下写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。 全局样式: 默认组件中的样式会作用到全局&#xff0c;任何一个组件中都会受到此样式的影响 局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件 一、代码示例 BaseOne…

MYSQL ORDER BY

在MySQL中&#xff0c;默认情况下&#xff0c;升序排序会将NULL值放在前面&#xff0c;因为在排序过程中&#xff0c;NULL会被视为最小值。然而&#xff0c;有时会要求在升序排序中需要将NULL值放在最后。 例如根据日期升序时就会出现这种问题 方案一&#xff1a; SELECT sor…

Docker成功启动Rabbitmq却访问不了管理页面问题解决

目录 启动步骤&#xff1a; 无法访问问题总结&#xff1a; 启动步骤&#xff1a; 拉取镜像&#xff1a; docker pull rabbitmq 运行&#xff1a; docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq进入容器&#xff1a; docker exec -it 容器id /bin/…

2024.6.9周报

目录 摘要 ABSTRACT 一、文献阅读 1、相关信息 2、摘要 3、文献解读 1、Introduction 2、文章主要贡献 3、模型架构 4、实验 4、结论 二、代码实现 总结 摘要 本周我阅读了一篇题目为《Unlocking the Potential of Transformers in Time Series Forecasting with …

流水线建构apk、abb实战(二)

gradlew 命令生成apk、aab包 其实构建应用程序包就几个命令&#xff1a; ### 生成AAB&#xff1a; gradlew bundleRelease #输出到[project]/build/outputs/bundle/release/下 gradlew bundleDebug### 生成APK&#xff1a; gradlew assembleRelease gradlew assembleDebug###…

Linux系统之fc命令的基本使用

Linux系统之fc命令的基本使用 一、fc命令介绍1.1 fc命令简介1.2 fc命令用途 二、fc命令的帮助信息2.1 fc的man帮助2.2 fc命令的使用帮助2.3 fc命令与history命令区别 三、fc命令的基本使用3.1 显示最近执行的命令3.2 指定序号查询历史命令3.3 使用vim编辑第n条历史命令3.4 替换…

openh264 自适应量化功能源码分析

openh264 OpenH264是一个开源的H.264/AVC视频编解码器&#xff0c;由Cisco公司发起并贡献了最初的代码基础。它提供了一个用于视频编码和解码的库&#xff0c;支持H.264视频压缩标准&#xff0c;广泛应用于视频会议、流媒体和视频存储等领域。OpenH264是实现H.264编解码功能的…

关于vue2 antd 碰到的问题总结下

1.关于vue2 antd 视图更新问题 1.一种强制更新 Vue2是通过用Object…defineProperty来设置数据的getter和setter实现对数据和以及视图改变的监听的。对于数组和对象这种引用类型来说&#xff0c;getter和setter无法检测到它们内部的变化。用这种 this.$set(this.form, "…

T-Rex2: Towards Generic Object Detection via Text-Visual Prompt Synergy论文解读

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、引言二、文献综述1. Text-prompted Object Detection2. Visual-prompted Object Detection3. Interactive Object Detection 三、模型方法1. Visual-Text P…

在vscode 中使用npm的问题

当我装了 npm和nodejs后 跑项目在 文件中cmd的话可以直接运行但是在 vscode 中运行的时候就会报一下错误 解决方法就是在 vscode 中吧 power shell换成cmd 来运行就行了

JVM相关:Java内存区域

Java 虚拟机&#xff08;JVM)在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。 Java运行时数据区域是指Java虚拟机&#xff08;JVM&#xff09;在执行Java程序时&#xff0c;为了管理内存而划分的几个不同作用域。这些区域各自承担特定的任务&#xff0c…

知攻善防应急

知攻善防应急靶场一 小李在值守的过程中&#xff0c;发现有 CPU 占用飙升&#xff0c;出于胆子小&#xff0c;就立刻将服务器关机&#xff0c;并找你帮他分析&#xff0c;这是他的服务器系统&#xff0c;请你找出以下内容&#xff0c;并作为通关条件&#xff1a; 1.攻击者的 …

今日增长工具精选| 8个SaaS出海必备运营工具

一、SurveyMonkey 是一个灵活、方便、经济实惠的在线调查工具&#xff0c;可以通过自行设计定制化问卷&#xff0c;开展消费者调研&#xff0c;收集第一手数据&#xff0c;获取用户反馈。 客户涵盖财富100强公司以及其他不同规模和类型的组织&#xff0c;如公司、学术研究机构…

第二十六章CSS3续~

3.CSS3渐变属性 CSS3渐变(gradients)可以在两个或多个指定的颜色之间显示平稳的过渡。 以前&#xff0c;我们必须使用图像来实现这些效果。但是&#xff0c;通过使用CSS3渐变(gradients)&#xff0c;可以减少下载的事件和宽带的使用。由于渐变(gradient)是由浏览器生成的&…

首个文字生成手语模型来了!SignLLM通过文字描述来生成手语视频,目前已经支持八国手语!

SignLLM 是目前第一个通过文字描述生成手语视频的多语言手语模型。 该项目引入了首个多语言手语数据集 Prompt2Sign&#xff0c;它使用工具自动采集和处理网络上的手语视频&#xff0c;能够不断更新&#xff0c;且具有轻量化特点。 该模型当前支持 8 种手语类型。包括美国手语…

软件管理、rpm安装、yum安装、源码编译安装

目录 一、Windows安装/卸载 二、软件的卸载&#xff1a; 三、Linux的软件安装和卸载 3.1rpm安装 第一步&#xff1a;挂在光盘 第二步&#xff1a;查看/mnt 第三步&#xff1a;切换到/mnt/Packages 第四步&#xff1a;安装 3.2yum安装&#xff08;使用关盘作为yum源&…

29 - 买下所有产品的客户(高频 SQL 50 题基础版)

29 - 买下所有产品的客户 selectc.customer_id fromCustomer c group byc.customer_id havingcount(c.product_key)(select count(distinct product_key) from Product);