数据结构--八大排序算法

1. 直接插入排序

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

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

// 直接插入排序--时间复杂度略小于O(n^2)
// 希尔排序 gap == 1 就是直接插入排序
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			// 排升序--大的往后放
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				--end;
			}
			else
			{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}

直接插入排序的特性总结

1. 元素集合越接近有序,直接插入排序算法的时间效率越高;

2. 时间复杂度:O(N^2);

3. 空间复杂度:O(1);

2. 希尔排序

        希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定⼀个整数(通常是            gap = n/3 + 1),把待排序文件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每一组内的记录进行排序,然后 gap = gap/3 + 1 得到下一个整数,再将数组分成各组,进行插入排序,当 gap = 1 时,就相当于直接插入排序。

        它是在直接插入排序算法的基础上进行改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的。

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

// 希尔排序--O(n^1.3)
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

希尔排序的特性总结

1. 希尔排序是对直接插入排序的优化;

2. 当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果;

3. 时间复杂度大约是O(n^1.3);

3. 直接选择排序

1. 在元素集合 array[i]~array[n-1] 中选择关键码最大(小)的数据元素;

2. 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后⼀个(第⼀个)元素

交换;

3. 在剩余的 array[i]~array[n-2](array[i+1]~array[n-1]) 集合中,重复上述步骤,直到集合剩余 1 个元素;

// 直接选择排序--O(n^2)
void SelectSort(int* arr, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin; i <= end; ++i)
		{
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
		}
		// mini == begin 和 maxi == end情况
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&arr[mini], &arr[begin]);
		Swap(&arr[maxi], &arr[end]);

		++begin;
		--end;
	}
}

4. 堆排序

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

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

// 向下调整
void AdjustDown(int* arr, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 先找最大的孩子--排升序--建大堆
		if (child + 1 < n && arr[child + 1] > arr[child])
		{
			++child;
		}
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

// 堆排序--O(nlogn)
void HeapSort(int* arr, int n)
{
	// 向下调整
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)	// O(n)
	{
		AdjustDown(arr, i, n);	// O(logn)
	}

	// 排升序--建大堆
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, 0, end);
		--end;
	}
}

5. 冒泡排序

        冒泡排序是⼀种最基础的交换排序,之所以叫做冒泡排序,因为每⼀个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

// 冒泡排序--O(n^2)
void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n; ++i)
	{
		int change = 1;
		for (int j = 0; j < n - i - 1; ++j)
		{
			// 排升序--大的往后放
			if (arr[j] > arr[j + 1])
			{
				change = 0;
				Swap(&arr[j], &arr[j + 1]);
			}
		}
		if (change == 1)
			break;
	}
}

6. 快速排序

        快速排序是Hoare于1962年提出的⼀种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序实现主框架:

// 快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;

	// 找基准值
	int key = _QuickSort(arr, left, right);
	// 二分思想--二叉树前序遍历思想
	// [left, key-1] key [key+1, right]
	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}

将区间中的元素进行划分的 _QuickSort 方法主要有以下几种实现方式:


6.1 hoare版本

算法思路:

1)创建左右指针,确定基准值;

2)从左向右找出比基准值大的数据从右向左找出比基准值小的数据,左右指针数据交换,进入下次循环;

问题1:为什么跳出循环后right位置的值一定不大于key?

        当 left > right 时,即 right 走到 left 的左侧,而 left 扫描过的数据均不大于 key,因此 right 此时指向的数据⼀定不大于key。

问题2:为什么 left 和 right 指定的数据和 key 值相等时也要交换?

        相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据大量重复时,无法进行有效的分割排序。

// 左右指针法
int _QuickSort1(int* arr, int left, int right)
{
	int key = left;	// 基准值
	++left;
	while (left <= right)
	{
		// right:从右往左找比基准值要小的数据
		// 比基准值大right就--
		while (left <= right && arr[right] > arr[key])
		{
			--right;
		}
		// left:从左往右找比基准值要大的数据
		// 比基准值小left就++
		while (left <= right && arr[left] < arr[key])
		{
			++left;
		}
		// left和right交换,left、right与基准值相等也要交换
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		}
	}
	// 基准值key与right交换
	Swap(&arr[key], &arr[right]);

	// 返回基准值
	return right;
}

6.2 挖坑法

思路:

        创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的"坑",然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

// 挖坑法
int _QuickSort2(int* arr, int left, int right)
{
	// 起始坑位--left
	int hole = left;
	// 保存起始坑位的内容
	int key = arr[hole];
	//left++;	// err--left这里不能++,++后填坑的位置会有问题

	while (left < right)
	{
		// right:找比坑位的值小
		// 比坑位的值大或者相等就不填坑,且right--
		while (left < right && arr[right] >= key)
		{
			--right;
		}
		arr[hole] = arr[right];	// right下标的值去填坑
		hole = right;	// right下标的位置作为新的坑
		// left:找比坑位的值大
		// 比坑位的值小或者相等就不填坑,且left++
		while (left < right && arr[left] <= key)
		{
			++left;
		}
		arr[hole] = arr[left];	// left下标的值去填坑
		hole = left;	// left下标的位置作为新的坑
	}
	arr[hole] = key;	// 把起始坑位保存的数据去填坑

	return hole;
}

6.3 lomuto前后指针

思路:创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

// 双指针法--前后指针法
int _QuickSort3(int* arr, int left, int right)
{
	int prev = left, cur = prev + 1;
	int key = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[key] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		++cur;
	}
	// cur已经越界--key基准值根prev交换
	Swap(&arr[prev], &arr[key]);

	return prev;
}

6.4 非递归版本

借助数据结构栈:数据结构--栈-CSDN博客

// 快排--非递归版本--借助数据结构--栈
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	StackInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);
	while (!StackEmpty(&st))
	{
		// 取栈顶元素两次
		int begin = StackTop(&st);
		StackPop(&st);

		int end = StackTop(&st);
		StackPop(&st);

		// 对区间[begin, end]找基准值--双指针法--前后指针
		int prev = begin, cur = prev + 1;
		int key = begin;
		while (cur <= right)
		{
			if (arr[cur] < arr[key] && ++prev != cur)
			{
				Swap(&arr[cur], &arr[prev]);
			}
			++cur;
		}
		// cur已经越界--key基准值根prev交换
		Swap(&arr[prev], &arr[key]);
		key = prev;

		// 此时基准值的下标:key
		// 划分左右序列:[begin, key-1] [key+1, end]
		if (key + 1 < end)	// 右区间
		{
			StackPush(&st, end);
			StackPush(&st, key + 1);
		}
		if (key - 1 > begin)	// 左区间
		{
			StackPush(&st, key - 1);
			StackPush(&st, begin);
		}
	}

	StackDestroy(&st);
}

快速排序特性总结:

1. 时间复杂度:O(nlogn) 

2. 空间复杂度:O(logn)

7. 归并排序

归并排序算法思想:

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

7.1 递归版本

// 归并排序子程序
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)
		return;

	int mid = (left + right) / 2;
	// 根据mid划分成两个序列:[left, mid] [mid+1, right]
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);
	
	// 合并[left, mid] [mid+1, right]
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
		{
			tmp[index++] = arr[begin1++];
		}
		else
		{
			tmp[index++] = arr[begin2++];
		}
	}
	// 可能存在第一个序列中的数据没有全部放到tmp中
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	// 可能存在第二个序列中的数据没有全部放到tmp中
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}

	// 将tmp中数据挪回arr数组中
	for (int i = left; i <= right; ++i)
	{
		arr[i] = tmp[i];
	}
}

// 归并排序--O(nlogn)
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail!\n");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

7.2 非递归版本

// 归并排序--非递归版本
void MergeSortNonR(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail!\n");
		exit(-1);
	}
	int gap = 1;
	while (gap < n)
	{
		// 根据gap划分组,两两一合并
		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;

			// 第二组都越界不存在,这一组就不需要归并
			if (begin2 >= n)
			{
				break;
			}

			// 第二的组begin2没越界,end2越界了,需要修正一下,继续归并
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			int index = begin1;
			// 两个有序序列进行合并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
				{
					tmp[index++] = arr[begin1++];
				}
				else
				{
					tmp[index++] = arr[begin2++];
				}
			}
			
			// 第一组还有数据
			while (begin1 <= end1)
			{
				tmp[index++] = arr[begin1++];
			}

			// 第二组还有数据
			while (begin2 <= end2)
			{
				tmp[index++] = arr[begin2++];
			}

			// 导入到原数组中
			memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

归并排序特性总结:

1. 时间复杂度:O(nlogn) 

2. 空间复杂度:O(n) 

8. 计数排序

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

操作步骤:

1)统计相同元素出现次数;

2)根据统计的结果将序列回收到原来的序列中;

// 计数排序--O(n+range)
void CountSort(int* arr, int n)
{
	int min = arr[0], max = arr[0];
	for (int i = 1; i < n; ++i)
	{
		// 找最大值
		if (arr[i] > max)
		{
			max = arr[i];
		}
		// 找最小值
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}

	// max min
	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("calloc fail!\n");
		exit(-1);
	}

	for (int i = 0; i < n; ++i)
	{
		count[arr[i] - min]++;
	}

	// 将count中出现的次数还原到原数组中
	int index = 0;
	for (int i = 0; i < range; ++i)
	{
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
	}
}

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

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

相关文章

mapbox V3 新特性,添加下雪效果

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;mapbox 从入门到精通 文章目录 一、&#x1f340;前言1.1 ☘️mapboxgl.Map 地图对象…

STM32 GPIO误触发问题全解析:从噪声干扰到电路设计优化

问题描述 在STM32项目中&#xff0c;配置某GPIO为内部上拉输入模式&#xff0c;并外接了一个上拉电阻。该引脚通过1米长的线束连接至电机控制模块&#xff0c;但出现以下异常&#xff1a; 弯折线束或手指触碰线束时&#xff0c;电机误触发&#xff08;MCU检测到低电平&#x…

pyqt自制简单浏览器(python)

确保已安装 PyQt5 和 PyQtWebEngine 库。 import sys from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QApplication, QMainWindow, QToolBar, QLineEdit, QAction, QListWidget, QVBoxLayout, QDialog, QMessageBox, QInputDialog, QTabWidget from PyQt5.QtWebE…

【人工智能】如何选择合适的大语言模型,是能否提高工作效率的关键!!!

DeepSeek R1入门指南 导读一、提示语差异1.1 指令侧重点不同1.2 语言风格差异1.3 知识运用引导不同 二、挑选原则2.1 模型选择2.2 提示语设计2.3 避免误区 结语 导读 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 在前面的内容中&#xff0c…

​矩阵元素的“鞍点”​

题意&#xff1a; 一个矩阵元素的“鞍点”是指该位置上的元素值在该行上最大、在该列上最小。 本题要求编写程序&#xff0c;求一个给定的n阶方阵的鞍点。 输入格式&#xff1a; 输入第一行给出一个正整数n&#xff08;1≤n≤6&#xff09;。随后n行&#xff0c;每行给出n个整数…

ChatGPT搜索免费开放:AI搜索引擎挑战谷歌霸主地位全面分析

引言 2025年2月6日&#xff0c;OpenAI宣布ChatGPT搜索功能向所有用户免费开放&#xff0c;且无需注册登录。这一重大举措在搜索引擎行业引发巨大反响&#xff0c;有观点认为"谷歌搜索时代即将结束"。本文将深入分析ChatGPT生成式AI搜索对谷歌搜索业务及全球搜索市场…

NO.18十六届蓝桥杯备战|循环嵌套|乘法表|斐波那契|质数|水仙花数|(C++)

循环嵌套 循环嵌套的使⽤ while &#xff0c; do while &#xff0c; for &#xff0c;这三种循环往往会嵌套在⼀起才能更好的解决问题&#xff0c;就是我们所说的&#xff1a;循环嵌套。这三种循环都可以任意嵌套使⽤ ⽐如&#xff1a; 写⼀个代码&#xff0c;打印⼀个乘法⼝…

国际互联网安全日|Web3 世界的安全挑战与防护指南

2025 年 2 月 11 日是全球 “国际互联网安全日”&#xff08;Safer Internet Day&#xff09;。当我们跨越 Web2 迈入 Web3 时代&#xff0c;互联网安全的内涵也在悄然改变。在 Web2 时代&#xff0c;我们主要关注社交媒体隐私泄露、账号密码被盗、网络诈骗等传统安全问题。而在…

DeepseeK自动写作,自动将回答导出文档

在使用 DeepseeK 进行内容生成时&#xff0c;您是否也遇到了答案导出的困扰&#xff1f;无论是内容创作、数据分析还是项目报告&#xff0c;快速、高效地将生成的答案导出是提升工作效率的关键。本文将为您提供简单易行的解决方案&#xff0c;助您轻松实现 DeepseeK 答案的导出…

deep seek

1.介绍:DeepSeek是一款由国内人工智能公司研发的大型语言模型&#xff0c;拥有强大的自然语言处理能力&#xff0c;能够理解并回答问题&#xff0c;还能辅助写代码、整理资料和解决复杂的数学问题。免费开源&#xff0c;媲美ChatGPT 最近最火爆的AI对话程序。 www.deepseek.com…

数据结构中的邻接矩阵

一、概念 邻接矩阵&#xff08;Adjacency Matrix&#xff09;是图&#xff08;Graph&#xff09;的一种表示方法&#xff0c;用于描述图中顶点之间的连接关系。它是一种常见的数据结构&#xff0c;特别适用于表示稠密图&#xff08;即边数接近于顶点数平方的图&#xff09;。 …

微软AutoGen高级功能——Selector Group Chat

介绍 大家好&#xff0c;这次给大家分享的内容是微软AutoGen框架的高级功能Selector Group Chat(选择器群聊)&#xff0c;"选择器群聊"我在给大家分享的这篇博文的代码中有所体现微软AutoGen介绍——Custom Agents创建自己的Agents-CSDN博客&#xff0c;但是并没有详…

web前端开发中vscode常用的快捷键

1.快速复制一行 快捷键&#xff1a; shiftalt 下箭头(上箭头) 或者 ctrlc 然后 ctrlv 2.选定多个相同的单词 快捷键&#xff1a; ctrl d 先双击选定一个单词&#xff0c;然后按下 ctrl d 可以往下依次选择相同的单词。 这样同时修改相同的单词 3.全局替换某单词 当我们一个…

网络安全要学python 、爬虫吗

网络安全其实并不复杂&#xff0c;只是比普通开发岗位要学习的内容多一点。无论是有过编程基础还是零基础的都可以学习的。网络安全目前可就业的岗位从技术上可分为两部分&#xff1a;web安全和二进制逆向安全。web安全是网络安全的入门方向&#xff0c;内容简单&#xff0c;就…

深入解析哈希表:原理、实现与应用

目录 一、哈希表是什么&#xff1f; 1.1 基本概念 1.2 哈希表的工作原理 二、哈希表的使用方法 2.1 C 标准库中的哈希表 示例&#xff1a;std::unordered_map 的基本用法 2.2 自定义哈希函数 示例&#xff1a;自定义哈希函数 三、什么时候使用哈希表&#xff1f; 3.1…

CentOS-Stream 9更换RT实时内核

文章目录 1 安装环境1.1 Centos9原生内核示意 2 下载实时内核3 CentOS更换阿里YUM源3.1 更换源3.2 添加文件内容3.2.1 centos.repo3.2.2 centos-addons.repo 3.3 yum源生效 4 安装epel-release5 安装必要库和软件6 配置内核6.1 更改内核文件权限6.2 使用tar命令解压内核源码文件…

微软AutoGen高级功能——Magentic-One

介绍 大家好&#xff0c;博主又来给大家分享知识了&#xff0c;这次给大家分享的内容是微软AutoGen框架的高级功能Magentic-One。那么它是用来做什么的或它又是什么功能呢&#xff0c;我们直接进入正题。 Magentic-One Magnetic-One是一个通用型多智能体系统&#xff0c;用于…

国产编辑器EverEdit - 极简追梦人的福音:迷你查找

1 迷你查找 1.1 应用场景 某些场景下&#xff0c;用户不希望调出复杂的查找对话框&#xff0c;此时可以使用迷你查找窗口。 1.2 使用方法 选择主菜单查找 -> 迷你查找&#xff0c;或使用快捷键Ctrl Alt F&#xff0c;会在右上角弹出迷你查找窗口&#xff0c;如下图所示…

【第4章:循环神经网络(RNN)与长短时记忆网络(LSTM)— 4.5 序列标注与命名实体识别】

一、引言 嘿,各位技术小伙伴们!今天咱们要来深入聊聊循环神经网络(RNN)和长短时记忆网络(LSTM),这俩在序列标注和命名实体识别领域那可是相当厉害的角色。咱就从最基础的概念开始,一步步揭开它们神秘的面纱,看看它们到底是怎么在实际应用中发挥巨大作用的。 二、序列…

AIGC与AICG的区别解析

目录 一、AIGC&#xff08;人工智能生成内容&#xff09; &#xff08;一&#xff09;定义与内涵 &#xff08;二&#xff09;核心技术与应用场景 &#xff08;三&#xff09;优势与挑战 二、AICG&#xff08;计算机图形学中的人工智能&#xff09; &#xff08;一&#x…