数据结构-C语言-排序(3)

news2024/11/24 2:46:05

        代码位置:test-c-2024: 对C语言习题代码的练习 (gitee.com)

一、前言:

1.1-排序定义:

        排序就是将一组杂乱无章的数据按照一定的规律(升序或降序)组织起来。(注:我们这里的排序采用的都为升序)

1.2-排序分类:

常见的排序算法:
  • 插入排序
    a. 直接插入排序
    b. 希尔排序

  • 选择排序
    a. 选择排序
    b. 堆排序
  • 交换排序
    a. 冒泡排序
    b. 快速排序
  • 归并排序
    a. 归并排序
  • 非比较排序
    a.计数排序
    b.基数排序

1.3-算法比较:

1.4-目的:

        今天,我们这里要实现的是快速排序

1.5-快速排序定义:

        通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序。

二、快速排序-key的选择:

2.1-直接在left和right中选择:

        这种选择方法具有局限性如果排序的序列已经为升序的情况下,根据快速排序的定义,我们可知,快速排序的话,会确定一个值的位置也就是key,这个值的作用就是把数据分割成独立的两部分,一部分是比他大,一部分是比他小,而如果是升序的情况下直接选择left,选出的left的值是最小的,也就是说右面的部分是N-1个数据,而如果是用递归的方式实现的快排,那么就需要递归N次,也就是建立N个栈才能实现最终排序的操作,如果数据量大就很有可能出现栈溢出的情况。

该情况下递归的图片如图所示:

2.2-随机选择key:

        随机选择key,也就是说,在数组下标范围内,随机生成一个下标,采用这个下标位置的数据值作为key这样的情况下,我们就大大降低了选出的key是最小值的情况。能有效地减少栈溢出的情况。

2.3-三数取中:

        三数取中就是在left 、midi((right+left)/2) 、right三个下标位置上的数据之间选择出这三个数据中的中间数。这样就避免了key为最小值的情况。

2.4-代码:

void Swap(int* p, int* q)				//交换函数
{
	int tem = *p;
	*p = *q;
	*q = tem;
}
//直接选取法
int keyi = left;

//随机选keyi
int randi = left + (rand() % (right - left));
Swap(&a[randi], &a[left]);
int keyi = left;

//三数取中
int midi = GetMidNumi(a, left, right);
if (midi != left)
	Swap(&a[midi], &a[left]);
int keyi = left;
int GetMidNumi(int* a, int left, int right)			//三数取中法
{
	int mid = (left + right) / 2;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	if (a[left] < a[mid])
	{
		if (a[left] > a[right])
		{
			return left;
		}
		if (a[right] > a[mid])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}
}

三、快速排序-Hoare:

3.1-思路:

Hoare快速排序的思路就是:

        如果key定义的是left,那么就首先从右边找小,找到以后再从左边出发找大,找完以后后将他们俩的数据调换,然后接着进行下一次先右后左找小再找大,直到left小于right为止。同理若定义key在right处那么就先左后右,处理方式与left类似。        

        这样排完一趟后,我们能将大于key的数分布在右边,小于key的数分布在左边,最后,我们只需要按照这个思路递归下去,就实现快速排序啦。

        注意:在递归时,如果出现left>=right的情况下,我们就需要返回,否则就会死循环。

左边做key为什么相遇位置一定比key小?

3.2-过程图:

3.3-代码:

//Hoare
void QuickSort1(int* a, int left,int right)			//快速排序---时间复杂度(O(N^logN))
{
	if (left >= right)
		return;

	int begin = left;
	int end = right;
	//直接选取法
	//int keyi = left;

	//随机选keyi
	//int randi = left + (rand() % (right - left));
	//Swap(&a[randi], &a[left]);
	//int keyi = left;

	//三数取中
	int midi = GetMidNumi(a, left, right);
	if(midi!=left)
	Swap(&a[midi], &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[keyi], &a[left]);
	keyi = left;
	//递归---小区间优化--小区间直接使用插入排序
	if (end - begin+1 > 10)
	{
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

四、快速排序-挖坑法:

4.1-思路:

        快速排序,挖坑法的思路就是:

        先将第一个数据存放在临时变量k中,形成一个坑位,然后再数组的右边出发,向左寻找小于key的数,然后将这个数填在以前的坑位上,然后在该处重新生成坑位,接着在从左边向右寻找,找大于ker的数,然后将这个数填在生成的坑位上,然后在该处重新生成坑位,就这样一直循环实现上述操作,直到left与right相遇为止,此时,该坑位就应该填key。。

        这样排完一趟后,我们能将大于key的数分布在右边,小于key的数分布在左边,最后,我们只需要按照这个思路递归下去,就实现快速排序啦。

        注意:在递归时,如果出现left>=right的情况下,我们就需要返回,否则就会死循环。

4.2-过程图:

        

4.3-代码:

//挖坑法
void QuickSort2(int* a, int left, int right)			//快速排序---时间复杂度(O(N^logN))
{
	if (left >= right)
		return;

	int begin = left;
	int end = right;
	//直接选取法
	//int keyi = left;

	//随机选keyi
	//int randi = left + (rand() % (right - left));
	//Swap(&a[randi], &a[left]);
	//int keyi = left;

	//三数取中
	int midi = GetMidNumi(a, left, right);
	if(midi!=left)
	Swap(&a[midi], &a[left]);
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right &&a[right] >= key)
		{
			--right;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right&&a[left] <= key)
		{
			++left;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	
	//递归---小区间优化--小区间直接使用插入排序
	if (end - begin + 1 > 10)
	{
		QuickSort2(a, begin, hole - 1);
		QuickSort2(a, hole + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

五、快速排序-前后指针法:

5.1-思路:

快速排序,前后指针法的思路就是:

首先,定义一个prev=left ; cur=left+1。这里我们实现的操作:

        1.cur找到比key小的值,++prove, cur和prev位置的数调换,然后++cur。

        2.cur找到比key大的值,++cur。

说明:

        1.prev要么紧跟着cur(prev下一个位置就是cur)。

        2.prev跟cur中间隔着一段比key大的值。

         就这样,按上述思想进入循环知道,直到cur走到数组末端的下一个位置为止,接下来我们要实行的操作就是将keyi位置的值(key)与prev位置的值交换。这样排完一趟后,我们能将大于key的数分布在右边,小于key的数分布在左边,最后,我们只需要按照这个思路递归下去,就实现快速排序啦。

        注意:在递归时,如果出现left>=right的情况下,我们就需要返回,否则就会死循环。

5.2-过程图:

5.3-代码:

//前后指针法
void QuickSort3(int* a, int left, int right)			//快速排序---时间复杂度(O(N^logN))
{
	if (left>=right)
		return;

	int begin = left;
	int end = right;
	//直接选取法
	//int keyi = left;

	//随机选keyi
	//int randi = left + (rand() % (right - left));
	//Swap(&a[randi], &a[left]);
	//int keyi = left;

	//三数取中
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
		Swap(&a[midi], &a[left]);
	int keyi = left;
	int prev= left;
	int cur = left + 1;
	while (cur<=right)
	{
		if(a[cur]<a[keyi]&&++prev!=cur)
		Swap(&a[cur], &a[prev]);

		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	keyi = prev;
	//递归---小区间优化--小区间直接使用插入排序
	if (end - begin + 1 > 10)
	{
		QuickSort3(a, begin, keyi - 1);
		QuickSort3(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}

}

六、递归的问题与优化:

6.1-递归的问题:

        1.效率问题(略有影响)。

        2.深度太深时会栈溢出。

递归过程图:

6.2-小区间优化:

小区间优化的思想就是:

        将递归的最后几层,也就是基本有序的小区间,采用直接插入排序的方法,不再采用递归的方式,这样能够减少递归时所开辟栈。

        因为,递归栈的开辟相当于二叉树,而二叉树的最后一层相当于总数的一半,如果把最后一层省掉,也就是省去了递归所需开辟栈的大概50%的空间。

        所以,我们可以通过小区间优化来减少栈的开辟,在不影响时间复杂度的情况下,也减小了深度太深时会栈溢出的问题。

6.3-小区间优化代码:

//递归---小区间优化--小区间直接使用插入排序
if (end - begin + 1 > 10)
{
	QuickSort3(a, begin, keyi - 1);
	QuickSort3(a, keyi + 1, end);
}
else
{
	InsertSort(a + begin, end - begin + 1);
}

七、递归改非递归:

        由上述可知,通过递归实现快排,具有一定的弊端,也就是栈溢出,所以这里我们可以采取将递归改成非递归的方式来实现快速排序

7.1-方式:

        1.直接改成循环。

        2.使用栈辅助改成循环。

        通过上述代码可观察发现,递归改非递归的第一种方式,我们是实现不了的,所以我们这里需要借助栈来辅助将递归改成循环。

7.2-思路:

        这里的思路就是将递归时的左右区间,存入栈中。然后在循环的过程中,我们只需将区间值出站即可。

        注意:栈的原理是后进先出。

7.3-代码:

#include "Stack.h"
//递归改非递归
void QuickSortNonR(int* a, int left, int right)					//快速排序---时间复杂度(O(N^logN))
{
	ST ps;
	STInit(&ps);
	STpush(&ps, right);	//入栈
	STpush(&ps, left);	//入栈
	
	while (!STEmpty(&ps))
	{
		int begin= STTop(&ps);		//取栈顶元素
		STPop(&ps);		//出栈
		int end = STTop(&ps);		//取栈顶元素
		STPop(&ps);		//出栈

		int midi = GetMidNumi(a, begin, end);
		if (midi != begin)
			Swap(&a[midi], &a[begin]);
		int keyi = begin;
		int prev = begin;
		int cur = begin + 1;
		while (cur <= end)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
				Swap(&a[cur], &a[prev]);

			cur++;
		}
		Swap(&a[keyi], &a[prev]);
		keyi = prev;
		if (keyi + 1 < end)
		{
			STpush(&ps, end);	//入栈
			STpush(&ps, keyi + 1);	//入栈
		}
		if (keyi - 1 > begin)
		{
			STpush(&ps, keyi - 1);	//入栈
			STpush(&ps, begin);	//入栈
		}
	}
	for (int i = 0; i <=right; i++)
	{
		printf("%d  ", a[i]);
	}
	printf("\n");
	STDestory(&ps);
}

7.4-栈的代码:

#pragma once

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


typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* ps);		//初始化
void STDestory(ST* ps);	//释放销毁

void STpush(ST* ps, STDataType x);	//入栈
void STPop(ST* ps);		//出栈
int STSize(ST* ps);		//栈中元素个数
bool STEmpty(ST* ps);		//判断栈空
STDataType STTop(ST* ps);		//栈顶元素

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"

void STInit(ST* ps)		//初始化
{
	assert(ps);

	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc");
		return;
	}

	ps->capacity = 4;
	ps->top = 0;				//top是栈顶元素的下一个位置

	//ps->top=-1				//top是栈顶元素位置
}

void STDestory(ST* ps)		//释放销毁
{
	assert(ps);

	ps->top = 0;
	ps->capacity = 0;
	free(ps->a);
	ps->a = NULL;
}


void STpush(ST* ps, STDataType x)		//入栈
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		STDataType* tem = (STDataType*)realloc(ps->a, sizeof(STDataType) * ps->capacity * 2);
		if (tem == NULL)
		{
			perror("realloc");
			return;
		}

		ps->a = tem;
		ps->capacity *= 2;
	}

	ps->a[ps->top] = x;
	ps->top++;
}

void STPop(ST* ps)		//出栈
{
	assert(ps);
	assert(!STEmpty(ps));

	ps->top--;
}

int STSize(ST* ps)			//栈中元素个数
{
	assert(ps);

	return ps->top;
}

bool STEmpty(ST* ps)		//判断栈空
{
	assert(ps);

	return ps->top == 0;
}

STDataType STTop(ST* ps)		//返回栈顶元素
{
	assert(ps);
	assert(!STEmpty(ps));

	return ps->a[ps->top - 1];
}

八、结语:

        上述内容,即是我个人对数据结构排序中快速排序的个人见解以及自我实现。若有大佬发现哪里有问题可以私信或评论指教一下我这个小萌新。非常感谢各位友友们的点赞,关注,收藏与支持,我会更加努力的学习编程语言,还望各位多多关照,让我们一起进步吧!

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

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

相关文章

从汇编层看64位程序运行——栈保护

大纲 栈保护延伸阅读参考资料 在《从汇编层看64位程序运行——ROP攻击以控制程序执行流程》中&#xff0c;我们看到可以通过“微操”栈空间控制程序执行流程。现实中&#xff0c;黑客一般会利用栈溢出改写Next RIP地址&#xff0c;这就会修改连续的栈空间。而编译器针对这种场景…

集合媒体管理、分类、搜索于一体的开源利器:Stash

Stash&#xff1a;强大的媒体管理工具&#xff0c;让您的影音生活井井有条- 精选真开源&#xff0c;释放新价值。 概览 Stash是一个专为个人媒体管理而设计的开源工具&#xff0c;基于 Go 编写&#xff0c;支持自部署。它以用户友好的界面和强大的功能&#xff0c;满足了现代用…

16_网络IPC2-寻址

进程标识 字节序 采用大小模式对数据进行存放的主要区别在于在存放的字节顺序&#xff0c;大端方式将高位存放在低地址&#xff0c;小端方式将高位存放在高地址。 采用大端方式进行数据存放符合人类的正常思维&#xff0c;而采用小端方式进行数据存放利于计算机处理。到目前…

IDEA快速生成项目树形结构图

下图用的IDEA工具&#xff0c;但我觉得WebStorm 应该也可以 文章目录 进入项目根目录下&#xff0c;进入cmd输入如下指令&#xff1a; 只有文件夹 tree . > list.txt 包括文件夹和文件 tree /f . > list.txt 还可以为相关包路径加上注释

系统架构师考点--软件工程(下)

大家好。今天继续总结软件工程的知识点。 一、处理流程设计 业务流程重组BPR BPR是对企业的业务流程进行根本性的再思考和彻底性的再设计&#xff0c;从而获得可以用诸如成本、质量、服务和速度等方面的业绩来衡量的显著性的成就。BPR设计原则、系统规划和步骤如下图所示&am…

从 Pandas 到 Polars 十八:数据科学 2025,对未来几年内数据科学领域发展的预测或展望

我在2021年底开始使用Polars和DuckDB。我立刻意识到这些库很快就会成为数据科学生态系统的核心。自那时起&#xff0c;这些库的受欢迎程度呈指数级增长。 在这篇文章中&#xff0c;我做出了一些关于未来几年数据科学领域的发展方向和原因的预测。 这篇文章旨在检验我的预测能力…

日志的编写与线程池的结合

目录 一、认识日志 二、时间的等级划分 三、日志的输出端 3.1 保存至文件 四、日志的部分信息 4.1 日志等级 4.2 日志时间 五、加载日志 六、日志的宏编写 七、ThreadPool Log 一、认识日志 记录事件&#xff1a; 日志用于记录系统运行过程中发生的各种事件&…

word 设置多级混合标题自动更新

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 有没有体会过多级标题&#xff0c;怎么设置都不听使唤的情况&#xff1f; 我想要的格式是&#xff1a; 二、原因分析 多级标题中发现&#xff0c;输入编号格式这里有个数字没有底纹,是了&#xff0…

解析 Mira :基于 Web3,让先进的 AI 技术易于访问和使用

“Mira 平台正在以 Web3 的方式解决当前 AI 开发面临的复杂性问题&#xff0c;同时保护 AI 贡献者的权益&#xff0c;让他们可以自主拥有并货币化自己的模型、数据和应用&#xff0c;以使先进的 AI 技术更加易于访问和使用。” AI 代表着一种先进的生产力&#xff0c;它通过深…

nginx代理缓存

在服务器架构中&#xff0c;反向代理服务器除了能够起到反向代理的作用之外&#xff0c;还可以缓存一些资源&#xff0c;加速客户端访问&#xff0c;nginx的ngx_http_proxy_module模块不仅包含了反向代理的功能还包含了缓存功能。 1、定义代理缓存规则 参数详解&#xff1a; p…

万字长文之分库分表里如何优化分页查询?【后端面试题 | 中间件 | 数据库 | MySQL | 分库分表 | 分页查询】

分库分表的一般做法 一般会使用三种算法&#xff1a; 哈希分库分表&#xff1a;根据分库分表键算出一个哈希值&#xff0c;根据这个哈希值选择一个数据库。最常见的就是数字类型的字段作为分库分表键&#xff0c;然后取余。比如在订单表里&#xff0c;可以按照买家的ID除以8的…

开发实战经验分享:互联网医院系统源码与在线问诊APP搭建

作为一名软件开发者&#xff0c;笔者有幸参与了多个互联网医院系统的开发项目&#xff0c;并在此过程中积累了丰富的实战经验。本文将结合我的开发经验&#xff0c;分享互联网医院系统源码的设计与在线问诊APP的搭建过程。 一、需求分析 在开发任何系统之前&#xff0c;首先要…

UPFC统一潮流控制器的simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 UPFC统一潮流控制器的simulink建模与仿真。能够在不增加输电线路物理容量的情况下&#xff0c;显著提高电力系统的传输能力和稳定性。UPFC能够同时控制输电线路的有功功率、无…

技术速递|Let’s Learn .NET Aspire – 开始您的云原生之旅!

作者&#xff1a;James Montemagno 排版&#xff1a;Alan Wang Let’s Learn .NET 是我们全球性的直播学习活动。在过去 3 年里&#xff0c;来自世界各地的开发人员与团队成员一起学习最新的 .NET 技术&#xff0c;并参加现场研讨会学习如何使用它&#xff01;最重要的是&#…

微软研究人员为电子表格应用开发了专用人工智能LLM

微软的 Copilot 生成式人工智能助手现已成为该公司许多软件应用程序的一部分。其中包括 Excel 电子表格应用程序&#xff0c;用户可以在其中输入文本提示来帮助处理某些选项。微软的一组研究人员一直在研究一种新的人工智能大型语言模型&#xff0c;这种模型是专门为 Excel、Go…

在设计电气系统时,电气工程师需要考虑哪些关键因素?

在设计电气系统时&#xff0c;电气工程师需要考虑多个关键因素&#xff0c;以确保系统的安全性、可靠性、效率和经济性。我收集归类了一份plc学习包&#xff0c;对于新手而言简直不要太棒&#xff0c;里面包括了新手各个时期的学习方向编程教学、问题视频讲解、毕设800套和语言…

【Neural signal processing and analysis zero to hero】- 1

The basics of neural signal processing course from youtube: 传送地址 Possible preprocessing steps Signal artifacts (not) to worry about doing visual based artifact rejection so that means that before you start analyzing, you can identify those data epic…

《学会 SpringBoot · 定制 SpringMVC》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;如需交流&#xff…

Pytorch学习笔记day1—— 安装教程

这里写自定义目录标题 Pytorch安装方式 工作需要&#xff0c;最近开始搞一点AI的事情。但是这个国产的AI框架&#xff0c;实话说对初学者不太友好 https://www.mindspore.cn/ 比如说它不支持win下的CUDA&#xff0c;可是我手里只有3070Ti和4060也不太可能自己去买昇腾就有点绷不…

C语言 | Leetcode C语言题解之第239题滑动窗口最大值

题目&#xff1a; 题解&#xff1a; int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize) {int prefixMax[numsSize], suffixMax[numsSize];for (int i 0; i < numsSize; i) {if (i % k 0) {prefixMax[i] nums[i];} else {prefixMax[i] fmax(pref…