【数据结构】排序---C语言版

news2024/11/29 20:36:33

七大排序算法

  • 一、对于排序的分类:
  • 二、插入排序
    • 1、直接插入排序
      • (1)基本思想:
      • (2)直接插入排序:
      • (3)代码实现:
      • (4)总结:
    • 2、希尔排序
      • (1)基本思想:
      • (2)希尔:
      • (3)代码实现:
      • (4)总结:
  • 二、选择排序
    • 1、直接选择排序
      • (1)基本思想:
      • (2)代码实现:
      • (3)总结:
    • 2、堆排序
      • (1)基本思想:
      • (2)代码实现:
      • (3)总结:
  • 三、交换排序
    • 1、冒泡排序
      • (1)基本思想:
      • (2)代码实现:
    • 2、快速排序
      • (1)基本思想
      • (2)霍尔原版
      • (3)三数取中的加持
      • (4)挖坑法
      • (5)前后指针法
      • (6)快排---非递归
      • (7)总结:
  • 四、归并排序
    • 1.归并---递归版
      • (1)基本思想:
      • (2)代码实现:
      • (3)总结:
    • 2、归并---非递归版
      • (1)基本思想:
      • (2)代码实现:
  • 五、计数排序(非比较排序)
    • (1)基本思想
    • (2)代码实现
    • (3)总结
  • 六、排序总结
    • 稳定性的概念

一、对于排序的分类:

在这里插入图片描述

二、插入排序

1、直接插入排序

(1)基本思想:

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。(打扑克牌就是类似的思想
在这里插入图片描述

(2)直接插入排序:

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与
array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移
动图如下:
在这里插入图片描述

(3)代码实现:

1.头文件:

#pragma once

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

void Swap(int* p1, int* p2);

void Insert_sort(int* arr, int n);

2.源文件:

void InsertSort(int* arr, int n)
{
	// 1.for循环,外层循环的作用:是不断让end往后走,这样就保持前面一段序列[0,end]的之间是有序的!
	for (int i = 0; i < n - 1; i++)//2.这里的循环控制条件为什么i<n-1,因为:如果i<n的话,那么最终end会走到最后一个元素的下标!
	//但是进入循环里面还有一步,你别忘了就是把arr[end+1]赋值给tmp,那end+1不就是越界了吗?
		//所以说为了防止end+1越界,那么end只能走到倒数第2个位置!
	{
		//我们要定义一个下标来控制:[0,end]是有序的
		int end=i;
		//我们先用一个临时变量tmp来暂时保存,要插入这个数据,要插入这个数据就是:下标为end+1
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (tmp < arr[end])
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}

		arr[end + 1] = tmp;
	}
}

3.测试文件:

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

(4)总结:

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

2、希尔排序

(1)基本思想:

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

(2)希尔:

1.希尔排序包括:(1)预排序(2)直接插入排序
2.gap是间隔的意思,gap=X,整个数组就有X组!
在这里插入图片描述

(3)代码实现:

1.头文件:

#pragma once
#include<stdio.h>
#include <stdlib.h>
#include<assert.h>

//希尔排序
void ShellSort(int* arr, int n);

2.源文件:

//希尔排序

void ShellSort(int* arr, int n)
{
	int gap = n;
	//3.这个while循环gap最后一次一定要==1
	while (gap>1)
	//(1)gap>1是预排序!(2)gap==1就是插入排序!
	{
		gap = gap / 3 + 1;
		//2.一组一组地插入排序
		for (int i = 0; i < gap; i++)
		{
			//这是以gap为间隔的一组数据已经排完了
			for (int i = 0; i < n - gap; i += gap)
			{
				//1.单趟
				int end = i;
				//tmp/arr[end+1]就是要插入的数据
				int tmp = arr[end + gap];
				while (end >= 0)
				{
					if (tmp < arr[end])
					{
						arr[end + gap] = arr[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				arr[end + gap] = tmp;
			}
		}
	}
}

3.测试文件:

#include <stdio.h>
#include "Sort.h"

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

(4)总结:

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定,实验和基础上推断时间复杂度为O(N^1.3)。
  4. ① 最坏的情况是逆序时,gap很大时,while循环的时间复杂度为O(N)
    ② 当gap很小时,本来应该是O(N*N) ,但是经过预排序后,数组已经接近有序,所以这里还是O(N)

二、选择排序

1、直接选择排序

(1)基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
在这里插入图片描述

(2)代码实现:

1.头文件:

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

//选择排序
void SelectSort(int* arr, int n);

2.源文件:

//选择排序
void SelectSort(int* arr, int n)
{
	//begin和end作为没有排好序的数组区间的,begin:(首)和end:(尾)
	int begin = 0;
	int end = n - 1;
	//i为遍历数组下标的移动变量
	//mini,maxi分别作为遍历过程中的最小值和最大值(下标)
	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin+1; i <= end; i++)
		{
			if (arr[i] < arr[mini])
			{
				mini = i;
			}

			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
		}

		Swap(&arr[mini], &arr[begin]);
		//坑:如果最大值的位置就是开头的位置,最小值要放到begin的位置左边没毛病,但是开头正好是最大值,交换后最大值的数值虽然过去了,
		//但是最大值的下标仍然停留在begin的位置(也就是交换后mini的下标)所以说要把此刻mini的下标赋值给maxi下标,下标也一起带走。
		if (arr[maxi] == arr[begin])
		{
			maxi = mini;
		}
		Swap(&arr[maxi], &arr[end]);

		begin++;
		end--;
	}
}

3.测试文件:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>

int main()
{
	int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	//BubbleSort(arr, sz);
	SelectSort(arr, sz);
	//HeapSort(arr, sz);
	//QuickSort3(arr, 0, sz - 1);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

(3)总结:

直接选择排序的特性总结:

  • 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  • 时间复杂度:O(N^2)无论最好还是最坏都是: O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

2、堆排序

(1)基本思想:

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是:排升序要建大堆,排降序建小堆。

  • 建堆:利用向下调整算法(向上也可以)
  • 创建一个end的变量,用来控制数组的尾
  • 首尾交换后,最大值max就到了组尾,然后不把max看作堆成员!
  • 在对end的之前个数字进行向下调整算法,重新构建堆

(2)代码实现:

1.头文件:

//堆排序
void HeapSort(int* arr, int n);

//向上调整
void AdjustUp(int* arr, int child);

//向下调整
void AdjustDown(int* arr, int size, int parent);

2.源文件:

//向上调整
void AdjustUp(int* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
	
}

//向下调整
void AdjustDown(int* arr, int size, int parent)
{
	int child = parent * 2 + 1;
	//建大堆:假设左孩子最大,如果不行再调换。
	while (child < size)
	{
		if (child+1 < size && arr[child] < arr[child + 1])
		{
			child++;
		}
		//运行到此处,child就是左右子树中最大的
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//堆排序
void HeapSort(int* arr, int n)
{
	//向上调整建大堆:
	for (int i = 0; i < n; i++)
	{
		AdjustUp(arr, i);
	}
	//开始堆排序
	int end = n - 1;//end是数组中最后一个元素的下标!!!
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);//此处不要管最后一个元素end的大小,这里传的end就是size!
		end--;
	}
}

3.测试文件:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>

int main()
{
	int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	//BubbleSort(arr, sz);
	//SelectSort(arr, sz);
	HeapSort(arr, sz);
	//QuickSort3(arr, 0, sz - 1);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

(3)总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

三、交换排序

1、冒泡排序

(1)基本思想:

依次比较相邻的两个数,将较小数放在前面,较大数放在后面,如此继续,直到比较到最后的两个数,将小数放在前面,大数放在后面,重复步骤,直至全部排序完成

  • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
  • 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

在这里插入图片描述

(2)代码实现:

1.头文件:

#pragma once

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
void Bubble_sort(int* arr, int n);

2.源文件:

//2.冒泡排序
//时间复杂度:最坏:O(N^2),最好:O(N)

void Bubble_sort(int* arr, int n)
{
	//1.外层循环是:趟数!(假设有10个元素需要进行9趟,所以说有n个元素就进行n-1趟。)
	for (int i = 0; i < n - 1; i++)
	{
		//标记
		bool flag = true;
		//2.内层循环是:每一趟所比较的对数!(外层循环进行一次,说明有一个元素已经对号入座了,每趟所比较的对数都会依次减小,所以说与i挂钩,要-i。)
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(&arr[j], &arr[j + 1]);
				flag = false;
			}
		}
		if (flag == true)
		{
			printf("数组原本就是有序的!");
			break;
		}
	}
}

3.测试文件:

int main()
{
	int arr[] = { 3,5,6,2,9,10,1,7,4,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	Bubble_sort(arr, sz);
	

	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

2、快速排序

(1)基本思想

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

(2)霍尔原版

// 快速排序hoare版本
void QuickSort1(int* arr, int begin, int end)
{
	//递归的最小区间!!!
	if (begin >= end)
		return;

	// Begin和end是控制未排好序的数字区间的首和尾
	// Left和right是遍历数组的移动下标
	int left = begin, right = end;
	int keyi = begin;
	while (left < right)
	{
		//右边找小
		while (left < right && arr[right] >= arr[keyi])
			right--;
		//左边找大
		while (left < right && arr[left] <= arr[keyi])
			left++;
		Swap(&arr[left], &arr[right]);
	}

	Swap(&arr[keyi], &arr[left]);
 
	//keyi要更新!!!
 
	keyi = left;//假设此时有10个数,相遇位置/最中间的数,的下标是5,即:(left),我把:下标5,赋值给了keyi!接下来我就可以:分段递归了!
	//下面的递归分为三部分:[begin,keyi-1],keyi,[keyi+1,end]
	QuickSort1(arr, begin, keyi - 1);
	QuickSort1(arr, keyi + 1, end);
}

1.原版霍尔所需要注意的细节
在这里插入图片描述
2.思考:当选取数组中最左边的数据为key,为什么相遇位置的值一定比key小?
答:因为是右边先走
相遇有两种情况:

  1. R遇到L:R没有找到比key小的,一直走直到遇到了L,相遇位置的值就是L,L所停留的位置一定比k小,因为L找大
  2. L遇到R:R先走,找到小的就停了下来。L找大的没有找到,一直走,直到遇到R就停下来了,相遇位置是R一定比key小

(3)三数取中的加持

//三数取中:返回的是下标!!!
int GetMid(int* arr, int begin, int end)
{
	int midi = (begin + end) / 2;
	//在begin midi end中取中位数
	if (arr[begin] < arr[midi])
	{
		if (arr[midi] < arr[end])
			return midi;
		
		else if (arr[begin] > arr[end])
			return begin;
	
		else
			return end;
	}
	else //arr[begin]>arr[midi]
	{
		if (arr[midi] > arr[end])
			return midi;
		else if (arr[begin] < arr[end])
		
			return begin;
		else
			return end;
	}
}

三数取中取的是一组数据的中位数,直接找到这组数据的中位数,把多把它当做key,然后交换到最开始的第1个位置,这样就会很方便!

(4)挖坑法

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

//2.挖坑法
int QuickSort2(int* a, int begin, int end)
{
	
	int midi = GetMidi(a, begin, end);
	Swap(&a[begin], &a[midi]);
	int key = a[begin];//(1)挖:把开头的begin对应的下标的值挖出去赋给:key
	int holei = begin;//(2)设:key的位置为第一个坑位!

	while (begin < end)
	{
		//右边找小,放入坑位
		while (begin<end && a[end]>=key)
		{
			end--;
		}
		a[holei] = a[end];
		holei = end;

		//左边找大,放入坑位
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		a[holei] = a[begin];
		holei = begin;
	}
	a[holei] = key;
	
	return holei;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	//int keyi = QuickSort1(a, begin, end);

	int keyi = QuickSort2(a, begin, end);
	//int keyi = QuickSort3(a, begin, end);

	//[begin,keyi-1],keyi,[keyi+1,end]

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

}

(5)前后指针法

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

//3.前后指针法
int QuickSort3(int* a, int begin, int end)
{
	
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

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

	while (cur <= end)
	{
		if (a[cur] <= a[keyi])
		{
			prev++;
			Swap(&a[prev], &a[cur]);
			cur++;
		}
		else//cur和prev之间的数据永远都大于key!!!
		{
			cur++;
		}
	}
	Swap(&a[prev], &a[keyi]);
	
	return prev;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	//int keyi = QuickSort1(a, begin, end);

	//int keyi = QuickSort2(a, begin, end);
	int keyi = QuickSort3(a, begin, end);

	//[begin,keyi-1],keyi,[keyi+1,end]

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

}

注意:挖坑法和前后指针法,只是理解上更容易理解,但是性能上跟原版的霍尔是一样的,没有变化。

(6)快排—非递归

1.递归—>非递归
(1)循环
(2)借助栈
2.思路:
整个区间入栈—>出栈—>单趟排序(得到key,分割成了2个子区间)—>子区间入栈(注意:后进先出)—>出栈(其中一个子区间)…
整个循环:(入栈)—>(出栈)—>(单趟排序)

//非递归快排:(入栈)->(出栈)->(单趟排序)->(入栈)......
void QuickSortNonR(int* a, int begin, int end)
{
	ST s;
	STInit(&s);

	//1.先将整个区间(入栈),再进行循环判断。
	STPush(&s, end);
	STPush(&s, begin);

	//如果栈为空直接结束排序
	while (!STEmpty(&s))
	{
		//2.(出栈)
		int left = STTop(&s);
		STPop(&s);
		int right = STTop(&s);
		STPop(&s);

		//3.(单趟排序)进行单趟排序之后就会分为两个区间
		int keyi=QuickSort3(a, left, right);
		//[left,keyi-1],keyi,[keyi+1,right]
		
		//!!!入栈前对小区间进行判断,如果没必要入栈就直接结束
		if (left < keyi - 1)//(=):一个值,(>):为NULL
		{
			STPush(&s, keyi - 1);
			STPush(&s, left);
		}
		if (keyi + 1 < right)
		{
			STPush(&s, right);
			STPush(&s, keyi+1);
		}
	}
	STDestroy(&s);
}

(7)总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

四、归并排序

1.归并—递归版

(1)基本思想:

在这里插入图片描述

归并排序:实质上就是:后续排序
基本思想:

  • 先将所有的分割成最小单位(最小单位就是一个节点,一个节点就当做有序)
  • 然后再归并合到一起形成有序
    思路:(原数组·:a,开辟的数组:tmp)
  • 首先动态开辟一个tmp数组空间
  • 数组a分解后,tmp从a数组中取出(11)归(22)归(44)归,每归并好一小段就直接拷贝回去

(2)代码实现:

1.头文件:

#pragma once


#include <stdio.h>
#include <string.h>
#include <stdlib.h>



void MergeSort(int* a, int n);

void _MergeSort(int* a, int begin, int end,int* tmp);

2.源文件:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>
#include "sort.h"


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 begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;

	int i = begin;
	//合并两个有序数组,取小的尾插
	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++];
	}

	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("malloc fail");
		return;
	}

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

	free(tmp);
}

思考细节
MergeSort为啥要设置一个子函数:_MergeSort?
1.因为在递归的时候必须是一段区间,而函数MergeSort没有写区间,在子函数中写区间begin和end的控制区间!
2.每次递归调用自己的时候,不可能每次都开辟空间吧,因为开辟空间这个过程是在原函数MergeSort完成的。
3. malloc tmp 之后记得free(tmp);

(3)总结:

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

2、归并—非递归版

(1)基本思想:

gap:归并每组的数据个数
在这里插入图片描述

非递归可以控制每次的gap成倍增长来达到归并的目的,但gap成倍增长也可能会带来这样的问题

(1)第一个区间的元素个数=gap,第二个区间的元素个数<gap

(2)第一个区间的元素个数<=gap,第二个区间的元素不存在

以上这两个问题需要用边界处理来解决

(2)代码实现:

1.头文件:

#pragma once


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void MergeSortNonR(int* a, int n);

2.源文件:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>
#include "sort.h"

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	//gap:每次归并 每组的数据个数
	int gap = 1;

	while (gap < n)//n是元素个数!!!
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;//第一组
			int begin2 = i + gap, end2 = i + 2 * gap - 1;//第二组

			//归并前:边界处理!!!
			//因为数组的大小不可能正好是2^n倍

			if (end1 >= n || begin2 >= n)
			{
				break;
			}

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

			int j = begin1;

			//归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2-i + 1));
		}
		gap *= 2;
	}
	free(tmp);
}

3.测试文件:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>
#include "sort.h"

int main()
{

	int a[] = { 2,4,5,6,1,7,3,2,0,6 };
	int sz = sizeof(a) / sizeof(a[0]);
	MergeSortNonR(a, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

五、计数排序(非比较排序)

(1)基本思想

原理:用另一个数组统计原数组中相同元素出现的次数,再根据统计次数反向填充到原数组中
在这里插入图片描述
如果相对集中的数据还可以很好写,但是如果分散间距很大的数据,用这种绝对映射是不行的!
所以说我们要引出一个相对映射的概念
比如说我有1000,1999,1888…等等100个数据,如果我开2000个空间,有些地方都没有用到,就是浪费!所以说我们要用相对映射的概念
相对映射的第1步:就是先求出一组数据的最小值(min)和最大值(max)

(2)代码实现

//计数排序

void CountSort(int* a, int n)
{
	//1.利用相对路径:需要先求出min和max
	int min = a[0];
	int max = a[0];

	for (int i = 0; i < n; i++)
	{
		if (a[i] < min)
			min = a[i];
		if (a[i] > max)
			max = a[i];
	}
	//2.创建一个count数组且初始化均为0
	int range = max - min + 1;//相对路径

	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("calloc fail");
		return;
	}

	//3.统计各个数据出现的次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	int i = 0;	//i:遍历a数组的下标
				//j:遍历count数组的下标
	//4.排序
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)//注意判断条件:count[j](出现的次数)!!!如果出现零次,我都没必要进行排序,就不会进入这个循环。
			//对于1个数字出现多次,所以要count[j]--
		{
			a[i++] = j + min;
		}
	}

}

在这里插入图片描述

(3)总结

适用场景:待排序数组的范围比较集中,效率就高,否则Count数组长度很长,浪费空间;且只适合整数,浮点数和字符串不适用。

时间复杂度:O(n+range)
空间复杂度:O(n)

六、排序总结

稳定性的概念

如果数组中有多个相同的值排序后还能够保证还能这几个相同的值其相对顺序不变,那么该排序算法具有稳定性
在这里插入图片描述


好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!
在这里插入图片描述

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

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

相关文章

Postgresql体系结构

client连接PostgreSQL过程&#xff1a; 1、客户端发起请求 2、主服务postmaster进程负责服务器是否接受客户端的host通信认证&#xff0c;服务器对客户端进行身份鉴别 3、主服务进程为该客户端单独fork一个客户端工作进程postgres 4、客户端与postgres进程建立通信连接&#xf…

【小白开服日记】幻兽帕鲁服务器如何搭建?

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

二分查找第二弹

目录 力扣852.山脉数组的峰顶索引 力扣162.寻找峰值 力扣153.寻找旋转排序数组中的最小值 力扣剑指Offer53.0-n-1缺失的数字 力扣852.山脉数组的峰顶索引 峰顶之前的全部比他小&#xff0c;峰顶之后的也比他小&#xff0c;把小于等于和大于分成两段 class Solution {publi…

java仓库进销存商品库存管理系统springboot+vue

库存管理信息系统研究的内容涉及库存管理的全过程&#xff0c;包括入库、出库、退 货、订货、库存统计查询等等。 根据上述工作流程&#xff0c;库存管理系统将包含以下内容 1&#xff09;登录信息的输入&#xff0c;密码的修改。 2&#xff09;基本信息的输入&#xff0c;包括…

一篇文章搞懂CNN(卷积神经网络)及其所含概念

目录 1. 什么是卷积神经网络&#xff1a;2. 应用领域&#xff1a;3. 架构&#xff1a;4. 卷积层的参数和名词参数&#xff1a;名词&#xff1a; 5. 注意&#xff1a;6. 经典网络&#xff1a;小结&#xff1a; 当下&#xff0c;计算机视觉在人工智能领域中扮演着至关重要的角色。…

【数据分享】1929-2023年全球站点的逐日降雪深度数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 之前我们分享过1929-2023年全球气象站点的逐日平均气温数据、逐日最高气温数据…

基于OpenCV灰度图像转GCode的双向扫描实现

基于OpenCV灰度图像转GCode的双向扫描实现 引言激光雕刻简介OpenCV简介实现步骤 1.导入必要的库2. 读取灰度图像3. 图像预处理4. 生成GCode 1. 简化版的双向扫描2. 优化版的双向扫描 5. 保存生成的GCode6. 灰度图像双向扫描代码示例 总结 系列文章 ⭐深入理解G0和G1指令&…

freeswitch对接FunASR实时语音听写

1、镜像启动 通过下述命令拉取并启动FunASR软件包的docker镜像&#xff1a; sudo docker pull \registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.7 mkdir -p ./funasr-runtime-resources/models sudo docker run -p 10096:10095 -i…

Eclipse 安装使用ABAPGit

Eclipse->Help->Install New software 添加地址 https://eclipse.abapgit.org/updatesite/ 安装完成打开 选择abapGit repositories,先添加仓库 点下图添加自己仓库 如图添加仓库地址 添加完仓库后&#xff0c;点击我的仓库 右键选中行&#xff0c;可以进行push和pu…

WPS Office18.7软件日常更新

【应用名称】&#xff1a;WPS Office 【适用平台】&#xff1a;#Android 【软件标签】&#xff1a;#WPS 【应用版本】&#xff1a;18.6.1➡18.7 【应用大小】&#xff1a;160MB 【软件说明】&#xff1a;软件日常更新。WPS Office是使用人数最多的移动办公软件。独有手机阅读模…

打榜,一个新人是如何做到一天推荐小报童7人的?

这是第一次参加打榜活动&#xff0c;有点小激动&#xff0c;虽然GMV不高&#xff0c;和那些动辄几千上万的大佬没法比&#xff0c;但是其实内在玩法还是相通的 下面就记录一下个人在此次打榜活动中的一些感悟&#xff0c;与君共勉吧~ 先来看下当时的成绩 最后时刻被反超 本…

Python算法100例-1.3 牛顿迭代法求方程根

完整源代码项目地址&#xff0c;关注博主私信’源代码’后可获取 1&#xff0e;问题描述 编写用牛顿迭代法求方程根的函数。方程为 a x 3 b x 2 c x d 0 ax^3bx^2cxd0 ax3bx2cxd0&#xff0c;系数a、b、c、d由主函数输入&#xff0c;求x在1附近的一个实根。求出根后&…

C++STL之容器

STL的概述 STL(Standard Template Library,标准模板库) STL的6大组件&#xff1a;容器、算法、迭代器、适配器、仿函数、空间配置 容器&#xff1a;存放数据 算法&#xff1a;操作数据 迭代器&#xff1a;算法 通过迭代器 操作 容器 适配器&#xff1a;为算法 提供更多的接口 …

电脑/机顶盒/ps3/4/连接老电视(只有AV、S-Video接口)解决方案之HDMI转AV/S-Video转换器HAV

HDMI转AV/S-Video转换器功能 01、将HDMI高清信号经过视频处理转换成AV、S-VIDEO(PAL/NTSC)的视频信号输出 02、将HDMI数字音频&#xff0c;经过DAC数模芯片处理转成模拟立体声输出 03、采用先进的视频处理技术&#xff0c;对图像的亮度&#xff0c;对比度及色彩进行增强处理 04…

AI重构千行百业!超声波俱乐部大湾区内部分享会圆满落幕

1月27日、28日&#xff0c;超声波俱乐部内部分享会第十六期、第十七期分别在深圳、广州召开&#xff0c;创梦天地、元气森林、新浪智库、百准、A2空间对活动进行了特别支持&#xff0c;六十余名AI领域的创始人和行业嘉宾出席分享会。 出席活动的嘉宾有&#xff1a; 超声波创始…

使用java -jar命令运行jar包提示“错误:找不到或无法加载主类“的问题分析

用maven把普通java项目打包成可运行的jar后&#xff0c;打开cmd用java -jar运行此jar包时报错&#xff1a; 用idea运行该项目则没有问题 。 其实原因很简单&#xff0c;我们忽略了2个细节。 java指令默认在寻找class文件的地址是通过CLASSPATH环境变量中指定的目录中寻找的。我…

数据中心机房建设的痛点:投资与运维之间的博弈

在数字化浪潮的推动下&#xff0c;数据中心机房建设成为企业发展不可或缺的一环。然而&#xff0c;这一过程中存在一系列的痛点&#xff0c;其中投资与运维之间的博弈成为机房建设的重要议题。本文将深入探讨机房系统建设中的投资及运行维护痛点&#xff0c;并提出相关观点和建…

Vulnhub靶机:hacksudo2 (HackDudo)

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;hacksudo2 (HackDudo)&#xff08;10.0.2.44&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://download.vulnh…

最新酒桌小游戏喝酒骰子小程序源码/带流量主

2023最新酒桌小游戏喝酒小程序源码-带流量主&#xff0c;喝酒神器3.6修改增加了广告位&#xff0c;直接上传源码到开发者端即&#xff0c;可通过后改广告代码&#xff0c;然后关闭广告展示提交&#xff0c;通过后打开即可。 流量主ID替换插屏广告位 adunit-29629a7b54a41a8b视频…

假期day3,三种进程间通信代码实现(2024/2/4)

消息队列 #include<myhead.h> struct msgbuf{long mstype;char text[1024]; };#define SIZE (sizeof(struct msgbuf)-sizeof(long))int main(int argc, const char *argv[]) {int pid;//创建key值key_t keyftok("/",a);if(key -1){perror("creat key&quo…