数据结构(初阶7)---七大排序法(堆排序,快速排序,归并排序,希尔排序,冒泡排序,选择排序,插入排序)(详解)

news2024/11/28 10:53:33

排序

  • 1.插入排序
  • 2.希尔排序
  • 3.冒泡排序
  • 4.选择排序(双头排序优化版)
  • 5.堆排序
  • 6.快速排序
    • 1). 双指针法
    • 2).前后指针法
    • 3).非递归法
  • 7.归并排序
    • 1).递归版本(递归的回退就是归并)
    • 2).非递归版本(迭代版本)

计算机执行的最多的操作之一就有排序,排序是一项极其重要的技能
接下来我们来详细介绍一下,我们遇到最多的排序方法

1.插入排序

首先我们介绍的是最基本的排序方式,插入排序

给定一个数组
[2,3,5,7,6,4,1]
请添加图片描述

代码实现:如何在数组中实现插入呢,
1.先将p+1的值记住。
2.从后向前遍历,找到比它小的前一个数
3.在遍历的时候,只需要将后面的值赋值给前面的值

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

}

这是最基本的排序方式
遍历了n+(n-1)+(n-2)+…+1
共计时间复杂度为O(n^2);

2.希尔排序

希尔排序是插入排序的升级版,是一种强大的排序方式。
我们发现如果我们正常使用选择排序,如果我的极值在数组的两头,我们就需要花费更多的时间将两头的元素一步一步的移到另一端,所以希尔大佬就发明了一种跳跃式移动的方法——希尔排序

给定一个数组:[5,3,2,4,1,2,2,3,4]需要排为升序
请添加图片描述
所以我们可以去迭代gap,让gap从sz/3开始逐渐向1迭代,
这样数列逐渐有序->最终变为有序

void ShellSort(int*a,int n)
{
	int gap=n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int count = 0; count < n - gap; count++)
		{
			int end = count;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}

	}
}

在实际应用中,希尔排序的平均时间复杂度通常被认为是
O(n log n)到O(n^(1.2)) 之间

3.冒泡排序

冒泡排序应该是所有初学者第一次学习的排序算法,也是效率最低的一种算法

这里就不过多赘述
通过一次次的冒泡排序,每一次都将一个数字移到正确的位置

void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void bubblesort(int *p,int sz)
{
	for (int count = 0; count < sz; count++)
	{
		for (int cur = 0; cur < sz - count-1; cur++)
		{
			if (p[cur] > p[cur+1])
			{
				swap(p + cur, p + cur + 1);
			}
		}
	}
}

4.选择排序(双头排序优化版)

选择排序应该也是一种十分常见的排序方式

这里我们就在原有基础上优化升级一下,双头选择排序优化版
从两头分别取数,左边取一个交换为较小,右边取一个交换为较大
给定一个数组:[5,3,2,4,1,2,2,3,4]需要排为升序请添加图片描述
时间复杂度还是O(n^2)但是效率要略高一些。

5.堆排序

堆排序我已经在这篇文章中仔细地阐述了:
可以点击以下链接去访问:

数据结构(初阶5)—堆与堆排序(详解)

6.快速排序

运用最最广泛的一种排序方式,以其独特的解决思想而闻名。
他的变种和相关推论有许多,这边着重将双指针法前后指针法
优化方式为三数取中法

快速排序的本质就是分治的思想,递归的思想
给定一个数组:[5,3,2,4,1,2,2,3,4]需要排为升序
请添加图片描述

1). 双指针法

请添加图片描述
为什么我们必须让p1先走呢,因为p1先走,最后相遇的时候,

相遇的值一定大于key,可以直接将key与相遇值交换
我们也许会想到,如果key的左边全部都大于key,这种极端的情况下,我们的快排就会变得十分的不稳定
所以我们有一个三数取中的优化方法
先将key与中间值与排头比较,最后将第二大的值与key的位置交换

void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int mid_inthree(int* arr, int begin, int end)
{
	if (arr[begin] > arr[end] && arr[begin] < arr[(begin + end) / 2])
		return begin;
	else if (arr[end] > arr[begin] && arr[end] < arr[(begin + end) / 2])
		return end;
	else
		return (end + begin) / 2;
}
void my_qsort(int* arr, int begin, int end)//双指针快排
{
	if (end <= begin)
	{
		return;
	}
	swap(arr+end, arr+mid_inthree(arr, begin, end));
	int left = begin, right = end - 1;
	while (left <= right)
	{
		while (arr[left] <= arr[end]&&left<=right)
		{
			left++;
		}
		while (arr[right] >= arr[end]&&right>=left)
		{
			right--;
		}
		if (left < right)
		{
			swap(arr + left, arr + right);
		}
	}
	swap(arr + end, arr + left);
	my_qsort(arr, begin, left - 1);
	my_qsort(arr, left + 1, end);
}

2).前后指针法

请添加图片描述

void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int mid_inthree(int* arr, int begin, int end)
{
	if (arr[begin] > arr[end] && arr[begin] < arr[(begin + end) / 2])
		return begin;
	else if (arr[end] > arr[begin] && arr[end] < arr[(begin + end) / 2])
		return end;
	else
		return (end + begin) / 2;
}
void my_qsort2(int* arr, int begin, int end)
{
	if (end <= begin)
	{
		return;
	}
	swap(arr + end, arr + mid_inthree(arr, begin, end));
	int cur = begin, prev = begin-1;
	while (cur<end)
	{
		while (cur < end && arr[cur] >= arr[end])
		{
			cur++;
		}
		prev++;
		swap(arr + cur, arr + prev);
	}
	swap(arr + prev+1, arr + end);
	my_qsort(arr, begin, prev - 1);
	my_qsort(arr, prev + 1, end);
}

3).非递归法

递归的短板不言而喻,稍微大一点的数组就会导致栈溢出
所以我们这里可以在堆上面建立一个模拟栈,总所周知栈上空间只有M量级堆上空间却有G量级

大致思路与上面类似,只是多了一个模拟栈,所以选择上面任意一种排序法就行,这里选择双指针快排作为核心
我们的模拟栈的作用是什么:用来存放和调用下标
比如说给定一个数组:[5,3,2,4,1,2,2,3,4]需要排为升序
哪么我的模拟栈中存入 (0,8)这两个区间的脚标
每进行一次快排,去掉内部的两个脚标,再放入四个脚标;
如果区间只有一个数字,就不放入模拟栈
最后栈变为空。
所以代码实现如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 定义栈节点
typedef int STDataType;

typedef struct Stack {
    STDataType _data;
    struct Stack* _next;
} Stack;

// 初始化栈
Stack* StackIni() {
    return NULL; // 空栈直接用 NULL 表示
}

// 销毁栈
void Stackdestory(Stack* pst) {
    while (pst) {
        Stack* tmp = pst;
        pst = pst->_next;
        free(tmp);
    }
}

// 入栈
void StackPush(Stack** ppst, STDataType x) {
    Stack* newNode = (Stack*)malloc(sizeof(Stack));
    newNode->_data = x;
    newNode->_next = *ppst; // 新节点指向当前栈顶
    *ppst = newNode;        // 更新栈顶为新节点
}

// 出栈
void StackPop(Stack** ppst) {
    assert(*ppst); // 确保栈非空
    Stack* tmp = *ppst;
    *ppst = (*ppst)->_next;
    free(tmp);
}

// 判断栈是否为空
int Stackempty(Stack* pst) {
    return pst == NULL; // 空栈返回 1,非空返回 0
}

// 获取栈顶元素
STDataType Stacktop(Stack* pst) {
    assert(!Stackempty(pst)); // 确保栈非空
    return pst->_data;
}

前面是栈的源码操作,和建立
下面实现非递归快排

void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int partsort(int* arr, int begin, int end)//双指针核心
{
	swap(arr + end, arr + mid_inthree(arr, begin, end));
	int left = begin, right = end - 1;
	while (left <= right)
	{
		while (left <= right&&arr[left] <= arr[end])
		{
			left++;
		}
		while (right >= left &&arr[right] >= arr[end] )
		{
			right--;
		}
		if (left < right)
		{
			swap(arr + left, arr + right);
		}
	}
	swap(arr + end, arr + left);
	return left;
}
void my_qsort3(int* arr, int begin, int end)//非递归快排
{
	Stack* tmp=StackIni();
	StackPush(&tmp, end);
	StackPush(&tmp, begin);
	while (!Stackempty(tmp))
	{
		int begin = Stacktop(tmp);
		StackPop(&tmp);
		int end = Stacktop(tmp);
		StackPop(&tmp);
		int div = partsort(arr, begin, end);
		
		if (div + 1 < end)
		{
			StackPush(&tmp, end);
			StackPush(&tmp, div+1);

		}
		if (div - 1 > begin)
		{
			StackPush(&tmp, div - 1);
			StackPush(&tmp, begin);
		}
	}
	Stackdestory(tmp);
}

这种操作可以有效规避栈溢出,但是不会提高性能。
用了三数取中的方法,我们的快排可以近似为O(nlogn)

在这里插入图片描述

7.归并排序

归并排序也是一个十分重要的排序,在许多场景下都有运用,其主要思想是分治

给定一个数组:[5,3,2,4,1,2,2,3]需要排为升序
主要思想如下:
递归到底,再向上归并
请添加图片描述

1).递归版本(递归的回退就是归并)

代码实现如下,在经历了二叉树便利的学习后,归并排序的代码其实并不难理解

void _MergeSort(int* a, int left, int right, int* tmp)//递归回退就是归并
{
	if (left >=right)
	{
		return;
	}
	int mid = (left+right) / 2;
	_MergeSort(a, left, mid,tmp);
	_MergeSort(a, mid + 1, left, tmp);
	int begin_1 = left, end_1 = mid;
	int begin_2 = mid + 1, end_2 = right;
	int begin_tmp = begin_1;
	while (begin_1 <= end_1 && begin_2 <= end_2)
	{
		if (a[begin_1] >= a[begin_2])
		{
			tmp[begin_tmp++] = a[begin_2++];
		}
		else 
		{
			tmp[begin_tmp++] = a[begin_1++];
		}
	}
	while (begin_1 <= end_1)//因为是不等长的,所以我们需要考虑不等长的情况
	{
		tmp[begin_tmp++] = a[begin_1++];
	}
	while (begin_2 <= end_2)
	{
		tmp[begin_tmp++] = a[begin_2++];
	}
	while (left <= end_2)//复制过去
	{
		a[left] = tmp[left];
		left++;
	}
}
void MergeSort(int* arr, int n)
{
	assert(arr);
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(arr,0,n-1,tmp);
}

2).非递归版本(迭代版本)

这个版本有点抽象,这种递归改非递归的题都很让人摸不着头脑qwq

创建数组int arr[ ] = {1,3,5,7,9,2,4,6,8,0};
我们创造一个gap,gap从1开始,为 2^(n-1)+1,这样代表1,3,7,15…刚好表示
请添加图片描述
代码实现:(切记要控制边界,且要处理特殊情况!!!)

void MergeSortpart(int* a, int left,int mid,int right, int* tmp)
{
	int begin_1 = left, end_1 = mid;
	int begin_2 = mid + 1, end_2 = right;
	int begin_tmp = begin_1;
	while (begin_1 <= end_1 && begin_2 <= end_2)
	{
		if (a[begin_1] >= a[begin_2])
		{
			tmp[begin_tmp++] = a[begin_2++];
		}
		else
		{
			tmp[begin_tmp++] = a[begin_1++];
		}
	}
	while (begin_1 <= end_1)//因为是不等长的,所以我们需要考虑不等长的情况
	{
		tmp[begin_tmp++] = a[begin_1++];
	}
	while (begin_2 <= end_2)
	{
		tmp[begin_tmp++] = a[begin_2++];
	}
	while (left <= right)//复制过去
	{
		a[left] = tmp[left];
		left++;
		//printf("%d ", a[left]);
	}
}
void MergeSort(int* arr, int n)
{
	assert(arr);
	int* tmp = (int*)malloc(sizeof(int) * n);
	//_MergeSort(arr,0,n,tmp);
	int gap = 1;
	for (gap = 1; gap <= n-1; gap=gap*2+1)
	{
		for (int x = 0; x+gap<=n; x += gap+1)
		{
			int begin_1 = x, end_1 = x + gap;
			int mid = (begin_1 + end_1) / 2;
			MergeSortpart(arr, begin_1,mid,end_1,tmp);
		}
		for (int i = 0; i < n + 1; i++)
		{
			printf(" %d", arr[i]);
		}
		printf("\n");
	}

	if (gap/2 != n-1)//处理后面多出来的一小块
	{
		MergeSortpart(arr, 0, gap/2, n,tmp);
	}
	//_MergeSort(arr,0,n-1,tmp);
}

(*´∀`)~♥

创作不易,恳请留一个赞吧qwq,有留言必回 (*´∀`)~♥
在这里插入图片描述

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

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

相关文章

【JavaEE初阶 — 网络原理】初识网络原理

目录 1. 网络发展史 1.1 独立模式 1.2 网络互连 1.2.1 网络互联的背景 1.2.2 网络互联的定义 1.3 局域网LAN 1.4 广域网WAN 2. 网络通信基础 2.1 IP地址 2.2 端口号 2.3 认识协议 2.4 五元组 2.5 协议分层 2.5.1 分…

【C++习题】15.滑动窗口_串联所有单词的子串

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 30. 串联所有单词的子串 题目描述&#xff1a; 解法 滑动窗口哈希表 这题和第14题不同的是&#xff1a; 哈希表不同&#xff1a;hash<string,int>left与right指…

【学术讲座】视觉计算中的深度学习方法 AIGC图像视频生成模型的推理加速

视觉计算中的深度学习方法 发展历程 backbone 强化学习、LLM等&#xff1a;有监督 && 无监督的结合 目标检测 图像分割 网络结构搜索 搜索方法 1&#xff1a;强化学习 2&#xff1a;强化学习 3&#xff1a;梯度算法 结构选择的作用 1&#xff1a;开放环境感知网络…

【VLANPWN】一款针对VLAN的安全研究和渗透测试工具

关于VLANPWN VLANPWN是一款针对VLAN的安全研究和渗透测试工具&#xff0c;该工具可以帮助广大研究人员通过对VLAN执行渗透测试&#xff0c;来研究和分析目标VLAN的安全状况。该工具专为红队研究人员和安全学习爱好者设计&#xff0c;旨在训练网络工程师提升网络的安全性能&…

机器学习之数据预处理理论——基于表格数据分析

一、机器学习中数据预处理的作用与目的 对于机器学习而言&#xff0c;数据预处理是指在数据挖掘、数据分析、模型构建训练等过程中&#xff0c;对原始数据进行一系列的处理&#xff0c;以提高数据质量、减少噪声、提取有用信息等。数据预处理的主要目的是将原始数据转换为有用的…

如何写出好证明(支持思想的深入数学写作)

不断的修改和精炼是写作过程中的重要环节&#xff0c;数学写作最终目的是提供对问题的深刻洞察而非仅仅陈述细节。 根据harvey mudd college Francis Su教授的《GUIDELINES FOR GOOD MATHEMATICAL WRITING》讲稿&#xff0c;总结出撰写好的数学证明需要注意以下几个要点&#x…

中英双语介绍DeepSpeed 的 ZeRO 优化

DeepSpeed 的 ZeRO 优化&#xff1a;通俗易懂的原理与实践指南 引言 在深度学习的大规模模型训练中&#xff0c;显存瓶颈是常见的挑战。DeepSpeed 提供了革命性的 ZeRO (Zero Redundancy Optimizer) 优化技术&#xff0c;为大模型训练节省显存、提高效率提供了强有力的工具。…

如何将 GitHub 私有仓库(private)转换为公共仓库(public)

文章目录 如何将 GitHub 私有仓库转换为公共仓库步骤 1: 登录 GitHub步骤 2: 导航到目标仓库步骤 3: 访问仓库设置步骤 4: 更改仓库可见性步骤 5: 确认更改步骤 6: 验证更改注意事项 如何将 GitHub 私有仓库转换为公共仓库 在软件开发领域&#xff0c;GitHub 是一个广受欢迎的…

【webrtc】 mediasoup中m77的IntervalBudget及其在AlrDetector的应用

IntervalBudget 用于带宽控制和流量整形 mediasoup中m77 代码的IntervalBudget ,版本比较老IntervalBudget 在特定时间间隔内的比特预算管理,从而实现带宽控制和流量整形。 一。 pacedsender 执行周期: 下一次执行的时间的动态可变的 int64_t PacedSender::TimeUntilNextPr…

Z2400023基于Java+Servlet+jsp+mysql的酒店管理系统的设计与实现 源码 调试 文档

酒店管理系统的设计与实现 1.摘要2.主要功能3. 项目技术栈运行环境 4.系统界面截图5.源码获取 1.摘要 本文介绍了一个基于Java的酒店管理系统&#xff0c;该系统采用Servlet、JSP、JDBC以及c3p0等技术构建&#xff0c;为酒店提供了一个全面的管理平台。该系统不仅适合酒店进行…

《操作系统 - 清华大学》5 -5:缺页异常

文章目录 1. 缺页异常的处理流程2.在何处保存未被映射的页&#xff1f;3. 虚拟内存性能 1. 缺页异常的处理流程 缺页中断的处理过程: CPU读内存单元&#xff0c;在TLB中根据其虚拟地址匹配物理地址&#xff0c;未命中&#xff0c;读页表; 由于页表项的存在位为0&#xff0c;CP…

C++:多态的原理

目录 一、多态的原理 1.虚函数表 2.多态的原理 二、单继承和多继承的虚函数表 1、单继承中的虚函数表 2、多继承中的虚函数表 一、多态的原理 1.虚函数表 首先我们创建一个使用了多态的类&#xff0c;创建一个对象来看其内部的内容&#xff1a; #include<iostre…

Ubuntu 硬盘分区并挂载

一、什么是挂载 1.挂载的定义 在 Ubuntu&#xff08;或其他 Linux 系统&#xff09;中&#xff0c;挂载&#xff08;Mount&#xff09; 是将一个存储设备或分区连接到系统的文件系统层次结构中的过程。挂载后&#xff0c;你可以通过某个目录&#xff08;挂载点&#xff09;访问…

python-docx -- 读取word页眉、页脚

文章目录 sections介绍访问section添加section页眉、页脚综合案例:sections介绍 word支持section的概念,即一个文档的划分部分,不同的部分均包含相同的页面布局设置,如相同的边距、页面方向等;在每个section中可以定义页眉、页脚来应用于该section下的所有页面;大部分wor…

开源加密库mbedtls及其Windows编译库

目录 1 项目简介 2 功能特性 3 性能优势 4 平台兼容性 5 应用场景 6 特点 7 Windows编译 8 编译静态库及其测试示例下载 1 项目简介 Mbed TLS是一个由ARM Maintained的开源项目&#xff0c;它提供了一个轻量级的加密库&#xff0c;适用于嵌入式系统和物联网设备。这个项…

《生成式 AI》课程 第7講:大型語言模型修練史 — 第二階段: 名師指點,發揮潛力 (兼談對 ChatGPT 做逆向工程與 LLaMA 時代的開始)

资料来自李宏毅老师《生成式 AI》课程&#xff0c;如有侵权请通知下线 Introduction to Generative AI 2024 Springhttps://speech.ee.ntu.edu.tw/~hylee/genai/2024-spring.php 摘要 这一系列的作业是为 2024 年春季的《生成式 AI》课程设计的&#xff0c;共包含十个作业。…

公司金融期末考试题目

公司金融期末考试题 选择题 1.现金折扣和信用条件&#xff08;教材P253&#xff09; 题目类似&#xff1a; 下列不属于信用条件的是&#xff08;&#xff09;。 现金折扣 数量折扣信用期限 折扣期限 给定的信用条件为"1/10&#xff0c;n/40"&#xff0c;则其含义…

【前端】JavaScript中的字面量概念与应用详解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;字面量1. 数字字面量2. 字符串字面量3. 布尔字面量4. 空值字面量&#xff08;null&#xff09;5. 对象字面量6. 数组字面量7. 正则表达式字面量8. 特殊值字面量9. 函数字…

Kotlin DSL Gradle 指南

本文是关于 Kotlin DSL Gradle 的指南&#xff08;上篇&#xff09;&#xff0c;介绍了 Gradle 作为 Android 开发构建工具的作用及优势&#xff0c;包括初始配置、生命周期、依赖管理、Task 相关内容。如 Task 的创建、自定义、各种方法和属性&#xff0c;以及文件操作等&…

Web开发:使用stackexchange.redis库对redis进行增删改查

一、安装第三方库 二、官网 StackExchange.Redis |通用型 redis 客户端 三、连接示例 private static string redisConnectionString "localhost:6379,passwordyourpassword,defaultDatabase0,allowAdmintrue,asyncTimeout10000";private static string redisConn…