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

七大排序算法

  • 一、对于排序的分类:
  • 二、插入排序
    • 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)

六、排序总结

稳定性的概念

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


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024机械工程师面试题

1.常用的机械画图软件有哪些 SolidWorks、Pro/e、CATIA、UG、Creo、CAD、inventor。CAXA电子图板. 2.第一视角是___&#xff0c;第三视角是___&#xff1b; 只要区别是&#xff1a;物体所处的位置不同。一般中国都使用第一视角的。 3.气缸属于_____执行元件&#xff0c;电磁…

AIGC技术讲解以及应用的落地

简介 近期&#xff0c;火爆的“AI绘画”、图片转AI图&#xff0c;智能聊天软件ChatGPT&#xff0c;引起了人们广泛关注。人工智能潜力再次被证明&#xff0c;而这三个概念均来自同一个领域&#xff1a;AIGC。AIGC到底是什么&#xff1f;为什么如此引人关注&#xff1f;AIGC能产…

关系型数据库的介绍与历史(History of DataBase)

昨晚和大家聊到 数据库&#xff08;DataBase 简称DB&#xff09;简单概述 &#xff0c;今天简单和大家聊聊 关系型数据库&#xff08;关系数据库&#xff09; 的历史&#xff0c;它是以关系模型&#xff08;Relational Model&#xff09;来构建的数据存储系统。 关系数据库有个…

C/C++内存管理的底层调用逻辑

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

10_机械臂运动学_机械臂C++逆解——2023

就是算&#xff01; 遨博机械臂改进DH参数表&#xff1a; 机械臂正运动学连杆变换通式&#xff1a; 其中si代表sin(θi),ci代表cos(θi) sij代表sin(θi-θj),cij代表cos(θi-θj) sijk代表sin(θi-θjθk),cijk代表cos(θi-θj-θk)&#xff0c;用两角和差公式直接展开即可. 每…

如何获取气象数据

问题 如何获取气象数据 详细问题 笔者使用NOAA获取部分气象数据&#xff0c;但是涉及字段有限&#xff0c;如何获取更丰富的气象数据&#xff0c;譬如包括太阳辐射、温度、湿度、云覆盖等信息呢&#xff1f; 解决方案 步骤1、访问https://power.larc.nasa.gov/data-access…

财务数据处理问题及解决方案分享

一、平台介绍 财务自营计费主要承接京东自营数据在整个供应链中由C端转B端的功能实现&#xff0c;在整个供应链中属于靠后的阶段了&#xff0c;系统主要功能是计费和向B端的汇总。 二、问题描述 近年来自营计费数据量大增&#xff0c;有百亿的数据量&#xff0c;一天中汇总占…

【Linux】信号-下

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb;信号递达&#xff0c;信号未决&#x…

软考20-上午题-串及其模式匹配

串&#xff08;字符串&#xff09;是一种特殊的线性表&#xff0c;其数据元素为字符。如&#xff1a;"abc"。 一、串的定义 由字符构成的有限序列&#xff0c;是一种线性表。 串的比较&#xff1a;以字符的ASCII值作为依据。比较操作从两个字符串的第一个字符开始&a…

Openresty+Lua+Redis实现高性能缓存

一、背景 当我们的程序需要提供较高的并发访问时&#xff0c;往往需要在程序中引入缓存技术&#xff0c;通常都是使用Redis作为缓存&#xff0c;但是要再更进一步提升性能的话&#xff0c;就需要尽可能的减少请求的链路长度&#xff0c;比如可以将访问Redis缓存从Tomcat服务器…

4. 树(二叉树、二叉查找树/二叉排序树/二叉搜索树、平衡二叉树、平衡二叉B树/红黑树)

树 1. 二叉树1.1 概述1.2 特点1.3 二叉树遍历方式1.3.1 前序遍历(先序遍历)1.3.2 中序遍历1.3.3 后序遍历1.3.4 层序遍历 2. 二叉查找树&#xff08;二叉排序树、二叉搜索树&#xff09;2.1 概述2.2 特点 3. 平衡二叉树3.1 概述3.2 特点3.3 旋转3.3.1 左旋3.3.2 右旋 3.4 平衡二…

Quartus IP 之mif与hex文件创建与使用

一、mif与hex概述 ROM IP的数据需要满足断电不丢失的要求&#xff0c;ROM IP数据的文件格式一般有三种文件格式&#xff1a;.mif、.hex、.coe&#xff0c;Xilinx与Intel Altera支持的ROM IP数据文件格式如下&#xff1a; Xilinx与Altera支持的ROM文件格式 Alterahex、mifAM&am…

JS第二天、原型、原型链、正则

☆☆☆☆ 什么是原型&#xff1f; 构造函数的prototype 就是原型 专门保存所有子对象共有属性和方法的对象一个对象的原型就是它的构造函数的prototype属性的值。prototype是哪来的&#xff1f;所有的函数都有一个prototype属性当函数被创建的时候&#xff0c;prototype属性…

项目02《游戏-08-开发》Unity3D

基于 项目02《游戏-07-开发》Unity3D &#xff0c; 本次任务做物品相互与详情的功能&#xff0c; 首先要做 点击相应&#xff0c; 接下来用接口实现点击相应事件&#xff0c;具体到代码中&#xff0c;我们找到需要响应鼠标事件的对象&#xff0c; 双击PackageCell…

食堂预约系统

文章目录 前言​部分沟通内容技术点小程序功能部分代码段功能图 商家管理系统功能说明功能图登录页面商家管理页面 食品管理订单管理其他功能 结束语 前言​ 最近&#xff0c;接了个小项目——学校食堂预约取餐系统。 具体需求如下图&#xff1a; 部分沟通内容 技术点 系统…

C++:模板初阶

泛型编程 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础。 函数模板 函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实参类型产生函数的特定类型版本。…

百面嵌入式专栏(面试题)网络编程面试题

沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将介绍网络编程面试题 。 1、什么是IO多路复用 I/O多路复用的本质是使用select,poll或者epoll函数,挂起进程,当一个或者多个I/O事件发生之后,将控制返回给用户进程。以服务器编程为例,传统的多进程(多线程…

antv/x6 边添加鼠标悬浮高亮和删除功能

antv/x6 边添加鼠标悬浮高亮和删除功能 效果添加悬浮效果和删除工具取消悬浮效果边删除后的回调函数 效果 添加悬浮效果和删除工具 this.graph.on(edge:mouseenter, ({ cell }) > {let cellId cell.store.data.source.celllet sourceCell _this.graph.getCellById(cellId…

绝地求生:盘点游戏内七款真人脸模,你最喜欢哪款?

从27.1版本更新后&#xff0c;游戏内上线了荣都地图代言人吴彦祖和李政宰的真人脸模&#xff0c;从此闲游盒的各位盒友灵魂搭配的资源库里又多了两位英俊脸庞&#xff0c;那么今天闲游盒来盘点一下游戏内上线的七款真人脸模&#xff0c;看看大家更喜欢哪款呢? 吴彦祖和李政宰 …

CSS-IN-JS

CSS-IN-JS 为什么会有CSS-IN-JS CSS-IN-JS是web项目中将CSS代码捆绑在JavaScript代码中的解决方案。 这种方案旨在解决CSS的局限性&#xff0c;例如缺乏动态功能&#xff0c;作用域和可移植性。 CSS-IN-JS介绍 1&#xff1a;CSS-IN-JS方案的优点&#xff1a; 让css代码拥…