搞定常见八大排序

news2024/11/18 17:25:55

文章目录

    • 注意事项
    • 插入排序
      • 插入排序
      • 希尔排序
    • 分组预排序
    • 选择排序
      • 堆排序
      • 直接选择排序(最拉胯的排序)
    • 交换排序
      • 冒泡排序
      • 快速排序
        • 1. hoare版本
        • 如何解决快排缺陷?
        • 2.挖坑法版本
        • 3.双指针法版本(建议)
        • 快排算法优化
        • 实现非递归快排
    • 归并排序
      • 归并排序
    • 非比较排序
      • 计数排序
    • 外排序

注意事项

  • 下面排序一律使用升序实现

插入排序

插入排序

核心: 不断地往有序队列里插入值

在这里插入图片描述

// 时间复杂度:O(N^2)
// 最好:O(N)   -- 顺序有序或者接近有序
// 最坏:O(N^2) -- 逆序
// 空间复杂度:O(1)
void InsertSort(int* a, int n)
{
	assert(a);

	for (int i = 0; i < n - 1; ++i)
	{
		// 将x插入[0, end]有序区间
		int end = i;
		int x = a[end+1];
		while (end >= 0)
		{
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = x;
	}
}

希尔排序

核心:

    1. 分组预排序 – 让数组接近有序
    1. 直接插入排序

分组预排序

第一步:

gap是一个整数, 如下图gap为3, 紫色,红色和蓝色,这三组,就是gap组;

紫色组:9,5,8,5
红色组:1,7,6
蓝色组:2,4,3

每相同组元素之间间隔gap个元素.
在这里插入图片描述
第二步:

每一组进行插入排序

如下图:

紫色组,从第一个元素9开始插入排序.
红色组蓝色组也是一样.

在这里插入图片描述
第三步(了解):

如图 对逆序数组预排之后,接近有序
在这里插入图片描述
第四步:

我们感觉到预排消耗挺大的,后面我们与其他排序进行对比分析.

第五步:

实现预排操作

基础版:

时间复杂度:
最好: O(N)
最坏: F(N,gap) = (1+2+3+… N/gap) * gap – gap个组里,每一组时间复杂度 (1+2+…N/gap)

通过上述时间复杂度, gap越大,预排越快 , gap越小时间复杂度不断接近O(N^2)

void ShellSort(int* a, int n)
{
	// 按gap分组数据进行预排序
	int gap = 3;

	for (int j = 0; j < gap; ++j)
	{
	// 下面一组一组来插入排序
		for (int i = j; i < n - gap; i += gap)
		{
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > x)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}

			a[end + gap] = x;
		}
	}
}

改造版(多组一窝顿):

void ShellSort(int* a, int n)
{
	// 按gap分组数据进行预排序
	int gap = 3;

	// 下面一组一组来插入排序
		for (int i = j; i < n - gap; i ++)
		{
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > x)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}

			a[end + gap] = x;
		}
	
}

第六步:

如何通过gap提高效率? 多大的gap值最合适?

我们分析
第一点 :

gap越大,预排越快,预排后越不接近有序
gap越小,预排越慢,预排后越接近有序

第二点:

多次预排序 (gap > 1)
直接插入 (gap == 1)

第三点:

结合第二点,我们列出gap的倍数数, 其范围 [ while{ gap = gap/3 +1 } ],这些gap倍数数去进行预排序.
即gap从大到小进行作为预排值,由于,gap虽然不断的在变小,由于前面预排的操作让数据不断的接近有序,并且前期的预排很快,数据越有序那么预排越快,即插入排序越快.

第四点:

实际书本上希尔排序时间复杂度为 logN^1.3 ;
我们自己大概估算
希尔排序时间复杂度为 O( N*log(N) )
每次最好预排的时间大概为O(N) – 最快的情况是O(N^2),由于前面的预排,所以最快的情况被优化成接近O(N)
所以每次预排的时间大概为O(N).
代码实现:

int gap = n;
	while (gap > 1)
	{
		//gap = gap / 2;
		gap = gap / 3 + 1;
		
		// 多组一锅炖
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > x)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}

			a[end + gap] = x;
		}
	}	

选择排序

堆排序

利用堆的性质, 让堆帮我们选择

void HeapSort(int* a, int n)
{
	// 直接把a构建成堆,直接控制a数组
	int root = n;
	while (root >0)
	{
		AdjustDown(a, root);
		root--;
	}
}

// 堆排序 -- O(N*logN)
void HeapSort(int* a, int n)
{
	// O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	// O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

直接选择排序(最拉胯的排序)

基础版:
每次选最值,往数组左边堆

代码省略

优化版的:
每次在未选范围内选择最小值和最大值, 时间复杂度 O(N^2) = N + N-2 + N-4+…N-2*n

注意 : 该代码存在小bug, 已修改

// 选择排序
// 时间复杂度:O(N^2)
// 最好:O(N^2)
// 整体而言最差的排序,因为无论什么情况都是O(N^2)
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	
	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin; i <= end; ++i)
		{
			if (a[i] < a[mini])
				mini = i;

			if (a[i] > a[maxi])
				maxi = i;
		}
//bug
		Swap(&a[begin], &a[mini]);
		// begin == maxi时,最大被换走了,修正一下maxi的位置
		if (begin == maxi)
			maxi = mini;

		Swap(&a[end], &a[maxi]);

		++begin;
		--end;
	}
}

交换排序

冒泡排序

核心:循环n论,每一论让交换操作让一个最值的元素移到最右侧,跟选择排序差不多,不过这是交换两个数据

时间复杂度:O(N^2)
最好:O(N)
在这里插入图片描述


void BubbleSort(int* a, int n)
{
// 方法一:
	for (int j = 0; j < n; j++)
	{
		// 该循环让一个最值数不断的通过交换数据,移到最右侧
		for (int i = 1; i < n-j; ++i)
		{
			if (a[i - 1] > a[i])
			{
			Swap(&a[i - 1], &a[i]);
			}
	}
}
// 方法二:与方法一差不多
// 用end来代替n-j
// flag变量是用来优化程序的,当循环一轮后发现没有交换,说明已经有序了,结束后续操作
	int end = n;
	while (end > 0)
	{
		int flag= 0;
		for (int i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				flag= 1;
				Swap(&a[i - 1], &a[i]);
			}
		}
		--end;

		if (flag== 0)
		{
			break;
		}
	}
		

	
}

快速排序

核心:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

1. hoare版本

第一步
取key值

在这里插入图片描述第二步

L:选比key大的元素。
R:选比key小的元素。
然后
L与R进行交换
重复如上步骤

在这里插入图片描述

第三步:

L和R相遇,相遇的位置与key进行交换

在这里插入图片描述

第四步:

我们考虑到第三步的交换时,可能会出现相遇的值不是预期的值,即相遇的值可能比key大或者小,不确定。

如何解决?

选最左边的值做key,右边先走 -》》》 左右相遇比key小
选最右边的值做key,左边先走 -》》》 左右相遇比key大

我们分析选最左边的值做key,

L和R在不考虑谁先走的情况,L和R有4种情况
a.L找到了 ,R找到了 , 它们交换
b.L找到了,R没找到,R与L相遇,此时交换就出错了 erro
c.L没找到,L与R相遇 ,它们交换
d.L和R没找到,不会出现此情况

解决方法:如果我们让R先走,那么就能解决b情况。

第五步:

实现快排

// O(N*logN)
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
		
		int keyi = Partion1(a, left, right);
		// [left, keyi-1] keyi [keyi+1, right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}	
}
// hoare版本
// [left, right]
// O(N)
int Partion1(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[left], &a[keyi]);

	return left;
}

如何解决快排缺陷?

1. 快排的缺陷是什么? – 数据逆序时,时间复杂度O(N^2),递归深度深,栈溢出

最坏情况如图:

数据逆序时,时间复杂度O(N^2)
在这里插入图片描述
最好情况如图:

每次能取到中位数作为基准值,就是一颗二叉树的形式了
在这里插入图片描述

2.如何解决快排缺陷?

三数取中是让数据的最左,最右和中间的值,取一个中间数。

让中间数作为基准值。

// 三数取中
int GetMidIndex(int* a, int left, int right)
{
	//int mid = (left + right) / 2;
	int mid = left + ((right - left) >> 1);
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

// hoare版本
// [left, right]
// O(N)
int Partion1(int* a, int left, int right)
{
	// 三数取中 -- 面对有序最坏情况,变成选中位数做key,变成最好情况
	int mini = GetMidIndex(a, left, right);
	Swap(&a[mini], &a[left]);

	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]);

	return left;
}

2.挖坑法版本

hoare版本的变形,我们不需要考虑谁先走的问题了。

算法步骤:

a. 为占坑位的找值,然后填坑,自己作为坑位
b. 最后相遇以后,将key值放到坑里

算法实现:

// 挖坑法
int Partion2(int* a, int left, int right)
{
	// 三数取中 -- 面对有序最坏情况,变成选中位数做key,变成最好情况
	int mini = GetMidIndex(a, left, right);
	Swap(&a[mini], &a[left]);

	int key = a[left];
	int pivot = left;
	while (left < right)
	{
		// 右边找小, 放到左边的坑里面
		while (left < right && a[right] >= key)
		{
			--right;
		}

		a[pivot] = a[right];
		pivot = right;

		// 左边找大,放到右边的坑里面
		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[pivot] = a[left];
		pivot = left;
	}

	a[pivot] = key;
	return pivot;
}

在这里插入图片描述

3.双指针法版本(建议)

// 推荐掌握这个 -- 思想三种大家都要掌握
int Partion3(int* a, int left, int right)
{
	// 三数取中 -- 面对有序最坏情况,变成选中位数做key,变成最好情况
	int mini = GetMidIndex(a, left, right);
	Swap(&a[mini], &a[left]);

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

		++cur;
	}

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

快排算法优化

我们发现快排在递归时,最后几层递归次数占总体的大部分,因此我们对最后几层的递归改用插入排序,进行分割排序。

// O(N*logN)
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	// 小区间优化,当分割到小区间时,不再用递归分割思路让这段子区间有序
	// 对于递归快排,减少递归次数
	// 常数10是不确定的,因为这个优化并不是决定性的
	if (right - left + 1 < 10)
	{
	// InsertSort()为插入排序
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		int keyi = Partion3(a, left, right);
		// [left, keyi-1] keyi [keyi+1, right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}	
}

实现非递归快排

递归深度太深会栈溢出,我们可以改用非递归实现。

// 递归深度太深的程序,只能考虑改非递归
void QuickSortNonR(int* a, int left, int right)
{
	stack<int> st;

	st.push(left);
	st.push(right);

	while (!st.empty())
	{
		int end = st.top();
		st.pop();

		int begin = st.top();;
		st.pop();

		int keyi = Partion3(a, begin, end);
		// [begin, keyi-1] keyi [keyi+1, end]
		if (keyi + 1 < end)
		{
			st.push(keyi + 1);
			st.push(end);
		}

		if (begin < keyi - 1)
		{
			st.push(begin);
			st.push(keyi - 1);
		}
	}

}

归并排序

归并排序

总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

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

归并排序核心步骤:

将a数组进行排序,划分成了两个子序列,假设两个子序列已经排好序了,接下来归并到tmp数组里,使两个子序列合并成有序的,然后将tmp数组拷贝到原数组里。
在这里插入图片描述
在这里插入图片描述
递归实现

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}

	int mid = (left + right) / 2;
	// [left, mid] [mid+1, right] 有序
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	int begin1 = left, end1 = mid;
	int begin2 = mid+1, end2 = right;
	int i = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	// tmp 数组拷贝回a
	for (int j = left; j <= right; ++j)
	{
		a[j] = tmp[j];
	}
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}

非递归实现:

第一步

【框架搭好】

使用用循环来实现归并排序,按照层序的方式归并,第一层是1 个元素的两个序列进行归并,第二层是2个元素的两个有序序列进行归并,以此类推。

注意:这个代码存在边界问题,下面进行处理

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
// gap控制子序列长度
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [i,i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

		

			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}

			// 把归并小区间拷贝回原数组
			for (int j = i; j <= end2; ++j)
			{
				a[j] = tmp[j];
			}
		}

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

第二步:

【边界控制】

核心思想:end1、begin2、end2都有可能越界,通过调整这三个值来控制边界,让其不会越界错误

版本一

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [i,i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i+gap, end2 = i + 2*gap-1;
			//printf("[%d,%d][%d,%d]", begin1, end1, begin2, end2);

			// end1 越界,[begin2,end2]不存在
			if (end1 >= n)
			{
				end1 = n - 1;
			}

			//[begin1,end1]存在 [begin2,end2]不存在
			if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}

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

			//printf("[%d,%d][%d,%d]", begin1, end1, begin2, end2);

			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				//printf(" %d ", index);

				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				//printf(" %d ", index);

				tmp[index++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				//printf(" %d ", index);

				tmp[index++] = a[begin2++];
			}
		}
		printf("\n");
		
		// 把归并数据拷贝回原数组
		for (int i = 0; i < n; ++i)
		{
			a[i] = tmp[i];
		}

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

改进版本一:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [i,i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			// 核心思想:end1、begin2、end2都有可能越界
			// end1越界 或者 begin2 越界都不需要归并
			// 原数组 【begin1,n-1】是有序的,现在不合法归并条件,我们不进行归并处理就可以了
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			
			// end2 越界,需要归并,修正end2
			if (end2 >= n)
			{
				end2 = n- 1;
			}

			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}

			// 把归并小区间拷贝回原数组
			for (int j = i; j <= end2; ++j)
			{
				a[j] = tmp[j];
			}
		}

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

非比较排序

计数排序

核心:

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

第一步:

统计数据出现的次数,开辟一个count数组统计A数组的所有元素的个数。
如下图:
在这里插入图片描述
第二步:

根据统计的次数count数组,进行排序
在这里插入图片描述

第三步:

计数排序的空间优化,如下这组数据 【1000,1200,1001,1500,1300,1301】,
min:1000,max=1500,我们需要开辟1500个元素空间进行统计。

优化步骤:

a.相对映射
b.我们只需要开辟max-min个元素空间
c.映射位置=x-min , x为元素

如图:

在这里插入图片描述
实现代码:

// 时间复杂度:O(Max(N, Range))
// 空间复杂度:O(range)
// 适合范围比较集中的整数数组
// 范围较大,或者是浮点数等等都不适合排序了
void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 1; i < n; ++i)
	{
		if (a[i] > max)
		{
			max = a[i];
		}

		if (a[i] < min)
		{
			min = a[i];
		}
	}

	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int)*range);
	memset(count, 0, sizeof(int)*range);
	if (count == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	// 统计次数
	for (int i = 0; i < n; ++i)
	{
		count[a[i] - min]++;
	}

	// 根据次数,进行排序
	int j = 0;
	for (int i = 0; i < range; ++i)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
}

外排序

假设文件上有10亿个数据要进行排序,内存中只能存放1000w的数据,如何排序?

解题步骤:

  • 将10亿个数据切分成1000个小文件。
  • 将小文件加载到内存进行排序,写入回小文件里。
  • 如图1,file1和file2归并mfile文件里。
  • 如图2,file1=mfile,mfile=123,file2=3,将file1和file2归并到mfile文件里。
  • 如此类推,
  • 注意:处理上述方法还有其他方法可以实现,相对于其他方法这个比较简单,但效率差。

图1:

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

在这里插入图片描述

代码实现:

void _MergeFile(const char* file1, const char* file2, const char* mfile)
{
	FILE* fout1 = fopen(file1, "r");
	if (fout1 == NULL)
	{
		printf("打开文件失败\n");
		exit(-1);
	}

	FILE* fout2 = fopen(file2, "r");
	if (fout2 == NULL)
	{
		printf("打开文件失败\n");
		exit(-1);
	}

	FILE* fin = fopen(mfile, "w");
	if (fin == NULL)
	{
		printf("打开文件失败\n");
		exit(-1);
	}

	int num1, num2;
	int ret1 = fscanf(fout1, "%d\n", &num1);
	int ret2 = fscanf(fout2, "%d\n", &num2);
	while (ret1 != EOF && ret2 != EOF)
	{
		if (num1 < num2)
		{
			fprintf(fin, "%d\n", num1);
			ret1 = fscanf(fout1, "%d\n", &num1);
		}
		else
		{
			fprintf(fin, "%d\n", num2);
			ret2 = fscanf(fout2, "%d\n", &num2);
		}
	}

	while (ret1 != EOF)
	{
		fprintf(fin, "%d\n", num1);
		ret1 = fscanf(fout1, "%d\n", &num1);
	}

	while (ret2 != EOF)
	{
		fprintf(fin, "%d\n", num2);
		ret2 = fscanf(fout2, "%d\n", &num2);
	}

	fclose(fout1);
	fclose(fout2);
	fclose(fin);
}

void MergeSortFile(const char* file)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		printf("打开文件失败\n");
		exit(-1);
	}

	// 分割成一段一段数据,内存排序后写到,小文件,
	int n = 10;
	int a[10];
	int i = 0;
	int num = 0;
	char subfile[20];
	int filei = 1;

	memset(a, 0, sizeof(int)*n);
	while (fscanf(fout, "%d\n", &num) != EOF)
	{
		if (i < n - 1)
		{
			a[i++] = num;
		}
		else
		{
			a[i] = num;
			QuickSort(a, 0, n - 1);
			sprintf(subfile, "%d", filei++);
			FILE* fin = fopen(subfile, "w");
			if (fin == NULL)
			{
				printf("打开文件失败\n");
				exit(-1);
			}
			for (int j = 0; j < n; j++)
			{
				fprintf(fin, "%d\n", a[j]);
			}
			fclose(fin);

			i = 0;
			memset(a, 0, sizeof(int)*n);
		}
	}

	// 利用互相归并到文件,实现整体有序
	char mfile[100] = "12";
	char file1[100] = "1";
	char file2[100] = "2";
	for (int i = 2; i <= n; ++i)
	{
		// 读取file1和file2,进行归并出mfile
		_MergeFile(file1, file2, mfile);

		strcpy(file1, mfile);
		sprintf(file2, "%d", i + 1);
		sprintf(mfile, "%s%d", mfile, i + 1);
	}

	printf("%s文件排序成功\n", file);
	fclose(fout);
}

int main()
{
	MergeSortFile("SortData.txt");

	return 0;
}

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

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

相关文章

Rainbond 结合 Jpom 实现云原生 本地一体化项目管理

Jpom 是一个简而轻的低侵入式在线构建、自动部署、日常运维、项目运维监控软件。提供了&#xff1a; 节点管理&#xff1a;集群节点&#xff0c;统一管理多节点的项目&#xff0c;实现快速一键分发项目文件项目管理&#xff1a;创建、启动、停止、实时查看项目控制台日志&…

CentOS系统设置中文输入法,并切换输入法

1.点击Application—>System Tools—>Settings&#xff0c;选择Region&Language 2.在Input Sources中&#xff0c;选择左下角的“”&#xff0c;找到Chinese(Intelligent Pinyin)&#xff0c;选中后点击右上角的“add”即可 3.选择好后&#xff0c;就可以切换中英…

可口可乐如何管理其全球供应链

目录 &#xff08;一&#xff09;可口可乐供应链管理的核心组成部分 &#xff08;二&#xff09;可口可乐管理全球供应链的挑战 &#xff08;三&#xff09;可口可乐利用技术简化其供应链 转载自供应链星球 可口可乐在200多个国家和地区拥有约225家装瓶合作伙伴&#xff0c;并…

【李沐—AutoGluon背后的技术】

1.资料来源 AutoGluon背后的技术_哔哩哔哩_bilibili 也是一种Automl框架【在尽量不需要人的帮助下&#xff0c;对输入进行特征提取&#xff0c;选取适合的机器学习模型对它进行训练】。大部分基于超参数搜索技术【从数十或者数百个参数中选取一个合适的参数&#xff0c;媲美人…

vue插槽的使用

文章目录 前言默认插槽具名插槽作用域插槽 前言 插槽总共分为3类&#xff1a;默认插槽&#xff0c;具名插槽&#xff0c;作用域插槽 默认插槽 默认插槽只需要在子组件的新增一个slot标签&#xff0c;父组件的子组件标签的内容就是要插入的内容 父组件 <template v-bind…

mysql “order by”是怎么工作的?

开发应用的时候&#xff0c;一定会经常碰到需要根据指定的字段排序来显示结果的需求。还是以我们 前面举例用过的市民表为例&#xff0c;假设你要查询城市是“杭州”的所有人名字&#xff0c;并且按照姓名排序返回 前1000个人的姓名、年龄。 假设这个表的部分定义是这样的&…

【linux】基于单例模式实现线程池

文章目录 一、线程池1.1 池化的概念1.2 线程池的实现1.3 线程池的使用场景 二、单例模式2.1 单例模式概念2.2 单例模式的线程池2.3 防过度优化 三、源码 一、线程池 1.1 池化的概念 当我们处理任务的时候&#xff0c;一般就是来一个任务我们就创建一个线程来处理这个任务。这…

类实例化对象的存储与内存对齐,this指针与调用成员函数传参的本原面目(两种调用方式)

类的实例化对象的内存存储方式与内存对齐 对于类当中定义的成员函数&#xff0c;是放在公共代码区的&#xff0c;在公共代码区有类成员函数表。对于不同的实例化对象而言&#xff0c;它里面的各个成员变量都是不一样的&#xff0c;但是如果他们分别调用相同名字的成员函数&…

cmd切换壁纸 适用windows10

文章目录 代码代码讲解参考文章菜鸟的目录结构注意 昨天菜鸟上班但是真的没活干&#xff0c;闲着无聊&#xff0c;突然发现自己壁纸好久都是一个&#xff0c;看着真的烦了&#xff0c;但是下载一个壁纸软件又感觉实际用处不大还占着内存&#xff0c;所以菜鸟就想&#xff0c;要…

Java Type接口出现的原因以及它和泛型的关系

Java泛型很多人都用过&#xff0c;但是对于其中的原理可能很多人可能都不太清楚。 首先给出一个结论&#xff1a;Java的泛型是伪泛型&#xff0c;因为JVM在编译以后会将所有泛型参数擦除掉&#xff0c;这个就叫类型擦除。 下面用一段代码来证明&#xff0c;毕竟千言万语BB不如…

【软考数据库】第三章 数据结构与算法

目录 3.1 数据结构 3.1.1 线性结构 3.1.2 数组 3.1.3 矩阵 3.1.4 树与二叉树 3.1.5 图 3.2 查找 3.2.1 顺序查找 3.2.2 折半查找 3.2.3 哈希表 3.3 排序 3.3.1 直接插入排序 3.3.2 希尔排序 …

Win10任务栏卡死怎么办?这3个方法快收藏!

案例&#xff1a;win10任务栏卡死 【姐妹们&#xff0c;我的win10任务栏一直卡着&#xff0c;我完全没法使用计算机了&#xff0c;遇到这种情况&#xff0c;我应该怎么做呢&#xff1f;求大家给我支支招&#xff01;感谢感谢&#xff01;】 我们使用电脑的过程中&#xff0c;…

MyBatis的添加和简单使用

什么是MyBatis mybatis是一个方便我们更简单的操作数据库的框架&#xff0c;让我们不用再使用JDBC操作数据库。 MyBatis的创建 老项目添加mybatis&#xff0c;首先要安装好editstarters插件&#xff0c;然后在pom.xml中右键generate选择edit插件&#xff0c;注意不仅要添加m…

多维时序 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据多变量时间序列预测

多维时序 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据多变量时间序列预测 目录 多维时序 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据多变量时间序列预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 基于贝叶斯优化卷…

C/C++|物联网开发入门+项目实战|宏定义|数据声明|位操作|类型修饰符|访问固定内存位置|嵌入式C语言高级|常见面试题目讲解-学习笔记(13)

文章目录 常见面试题目讲解宏定义数据声明类型修饰符的使用总结位操作访问固定内存位置 参考&#xff1a; 麦子学院-嵌入式C语言高级-C语言函数的使用-常见面试题目讲解 参考&#xff1a; 嵌入式程序员应该知道的0x10个基本问题 常见面试题目讲解 宏定义 1 .用预处理指令#d…

ERD Online 4.1.0对接ChatGPT,实现AI建模、SQL自由

ERD Online 是全球第一个开源、免费在线数据建模、元数据管理平台。提供简单易用的元数据设计、关系图设计、SQL查询等功能&#xff0c;辅以版本、导入、导出、数据源、SQL解析、审计、团队协作等功能、方便我们快速、安全的管理数据库中的元数据。 4.1.0 ❝ :memo: fix(erd): …

CARIS11.3使用一段时间后的经验和总结

虽然CARIS11.4存在一些小bug&#xff0c;但CARIS11.3使用没有什么问题&#xff0c;相对于CARIS9而言&#xff0c;在导入数据和程序界面有些改进。用过CARIS9的同学都知道其建立项目和导入数据的步骤比较繁琐。而CARIS11.3导入数据的过程比较简洁&#xff0c;基本步骤如下&#…

把阿里大鸟花3个月时间整理的软件测试面经偷偷给室友,差点被他开除了···

写在前面 “这份软件测试面经看起来不错&#xff0c;等会一起发给他吧”&#xff0c;我看着面前的面试笔记自言自语道。 就在这时&#xff0c;背后传来了leder“阴森森”的声音&#xff1a;“不错吧&#xff0c;我可是足足花了三个月整理的” 始末 刚入职阿里的我收到了大学…

牛客网Verilog刷题——VL2

牛客网Verilog刷题——VL2 题目答案 题目 要求用verilog实现两个串联的异步复位的T触发器的逻辑&#xff0c;如下图所示。   模块的输入输出信号如下表&#xff0c;需要注意的是&#xff1a;这里rst是低电平复位&#xff0c;且采用异步复位的方式复位。 信号类型输入/输出c…

2023年淮阴工学院五年一贯制专转本大学语文考试大纲

2023年淮阴工学院五年一贯制专转本大学语文考试大纲 一、考试目标 淮阴工学院五年一贯制高职专转本入学考试秘书学专业《大学语文》考试是我校为招收五年一贯制高职专转本学生设置的具有选拔性质的考试科目。其目的是科学、公平、有效地测试考生是否具备攻读秘书学本科学位所…