[数据结构]排序

news2024/11/26 8:43:17

本篇主要是对常见排序的分类和一些排序的详解

一、插入排序

1.直接插入排序

// 直接插入排序函数
void insertionSort(int arr[], int n) {
    int i, key, j;
    for (i = 1; i < n; i++) {
        key = arr[i]; // 取出待排序的元素
        j = i - 1;
        // 将大于key的元素向后移动一位
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j = j - 1;
        }
        arr[j + 1] = key; // 将key插入到正确的位置
    }
}

这个排序的时间复杂度为O(n^2)空间复杂度为O(1)

2.希尔排序

#include <stdio.h>
// 希尔排序函数,接受一个整数数组和数组长度作为参数
void shell_sort(int arr[], int n) 
{
    // 外层循环控制不同的间隔值gap
    for (int gap = n / 3 + 1; gap > 0; gap /= 3) 
    {
        // 内层循环遍历数组中的元素,从索引gap开始到数组末尾
        for (int i = gap; i < n; i++) 
        {
            int temp = arr[i]; // 保存当前元素的值
            int j;
            // 比较当前元素与间隔为gap的前一个元素的大小,如果前一个元素较大,则交换它们的位置
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) 
            {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp; // 将保存的当前元素值赋给正确的位置
        }
    }
}

这个排序上回我们讲过了,空间复杂度为O(1),时间复杂度为O(n^1.3)。

二、选择排序

1.选择排序

// 选择排序函数
void selectionSort(int arr[], int n) {
    int i, j, minIndex, temp;
    for (i = 0; i < n - 1; i++) {
        minIndex = i; // 记录当前最小值的索引
        for (j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j; // 更新最小值索引
            }
        }
        // 交换当前元素与最小值元素
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

这个排序的思路也很简单,时间复杂度为O(N^2),空间复杂度是O(1)

2.堆排序

// 调整堆
void adjustHeap(int arr[], int i, int length) {
    int temp = arr[i]; // 取出当前元素
    for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
        if (k + 1 < length && arr[k] < arr[k + 1]) { // 如果右子节点大于左子节点,k指向右子节点
            k++;
        }
        if (arr[k] > temp) { // 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
            arr[i] = arr[k];
            i = k;
        } else {
            break;
        }
    }
    arr[i] = temp; // 将temp值放到最终的位置
}

// 堆排序
void heapSort(int arr[], int length) {
    // 1.构建大顶堆
    for (int i = length / 2 - 1; i >= 0; i--) {
        adjustHeap(arr, i, length);
    }
    // 2.调整堆结构+交换堆顶元素与末尾元素
    for (int j = length - 1; j > 0; j--) {
        // 交换堆顶元素和末尾元素
        int temp = arr[j];
        arr[j] = arr[0];
        arr[0] = temp;
        // 重新对堆进行调整
        adjustHeap(arr, 0, j);
    }
}

堆排序的时间复杂度O(N*logN),空间复杂度O(1)

三、交换排序

1,冒泡排序

// 冒泡排序函数
void bubble_sort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) { // 外层循环控制排序趟数
        for (int j = 0; j < n - 1 - i; j++) { // 内层循环控制每趟比较的次数
            if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,交换它们的位置
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

这个排序大家肯定都非常熟悉了,时间复杂度是O(n^2),空间复杂度是O(1)

2.快速排序

这个排序我们没讲过这里我们来讲一讲。

快速排序的整体思路就是保证基准值的左边的数小于基准值,右边大于基准值。

首先我们拥有如下数组

我们先选定一个基准值,(这里我们讲最基础的)我们选择第一个数作为基准值。

然后我们先从右往左走找小于基准值的值

找到后我们从左往右找大于基准值的

然后交换两个值

接着我们再重复之前的操作,直到相遇

然后将相遇点和基准值交换

然后我们可以得到基准值的下标,然后最左端不变,以基准值的下标-1为最右端,以基准值的下标为最左端,最右端不变把原来的数据一分为二:

然后再分别进行快速排序,以此类推,直到所有递归都返回.

代码示例:

void QuickSort(int* a, int left, int right)
{
	// 区间只有一个值或者不存在就是最小子问题
	if (left >= right)
		return;

	int begin = left, end = right;

	int keyi = left;
	while (left < right)
	{
		// right先走,找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

		// left再走,找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);
	keyi = left;

	// [begin, keyi-1]keyi[keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

快速排序的空间复杂度O(logN)时间复杂度是O(N*logN)。有意思的是其实快速排序的时间复杂度不是最坏情况,最坏情况应该是O(N^2),但是出现的概率很小。其次我们可以通过各种方法来改进

举几个例子,就不展开讲了:

//三数取中
int GetMidi(int* a, int left, int right)
{
	int mid = (left + right) / 2;


	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

我们通过三数取中得到key

在快排开头加上这个,就可以很好的减少我们出现最坏情况

int midi = GetMidi(a, left, right);
		Swap(&a[left], &a[midi]);

如果觉得三数取中比较复杂,我们可以去随机数作为key,虽然效率不高,但是写起来方便,也能减少出现最坏情况

int randi = rand() % (right - left + 1);
randi += left;
Swap(&a[left], &a[randi]);

快速排序其实分好几种,这里我再展示一种前后指针法,个人感觉写起来来简单一点

void QuickSort2(int* a, int left, int right)
{
	if (left >= right)
		return;

	int keyi = left;
	int prev = left;
	int cur = left+1;
	
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[prev], &a[cur]);

		++cur;
	}

	Swap(&a[keyi], &a[prev]);
	keyi = prev;

	QuickSort2(a, left, keyi - 1);
	QuickSort2(a, keyi + 1, right);
}

这里再展示一下非递归的快速排序,感兴趣的可以自己看下(注意要结合栈)

void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (StackEmpty(&st) != 0)
{
 right = StackTop(&st);
 StackPop(&st);
 left = StackTop(&st);
 StackPop(&st);
 
 if(right - left <= 1)
 continue;
 int div = PartSort1(a, left, right);
 // 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)
 StackPush(&st, div+1);
 StackPush(&st, right);
 
 StackPush(&st, left);
 StackPush(&st, div);
}
 
 StackDestroy(&s);
}

四、归并排序

基本思想:
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 ,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
我将会把递归和非递归的写法展示给大家
void _MergeSort(int* a, int begin, int end, int* tmp)
{
    if (begin == end)
        return;

    int mid = (begin + end) / 2;
    _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;
    // 依次比较,取小的尾插tmp数组
    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));
}

这部分代码实现了归并排序的递归版本。函数_MergeSort接受一个整数数组a、起始索引begin、结束索引end和一个临时数组tmp作为参数。首先判断是否只有一个元素,如果是则直接返回。然后计算中间索引mid,将数组分为两部分进行递归排序。接下来进行归并操作,将两个有序的子数组合并成一个有序的数组,并将结果存储在tmp数组中。最后将tmp数组的内容复制回原数组a

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);
    tmp = NULL;
}

这部分代码是归并排序的入口函数,调用了递归版本的_MergeSort函数。首先分配一个临时数组tmp,如果分配失败则输出错误信息并返回。然后调用_MergeSort函数进行排序,最后释放临时数组的内存。

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

    int gap = 1;

    while (gap < n)
    {
        for (int j = 0; j < n; j += 2 * gap)
        {
            int begin1 = j, end1 = begin1 + gap - 1;
            int begin2 = begin1 + gap, end2 = begin2 + gap - 1;

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

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

            int i = j;
            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 + j, tmp + j, sizeof(int) * (end2-j+1));
        }

        gap *= 2;
    }

    free(tmp);
    tmp = NULL;
}

这部分代码实现了归并排序的迭代版本。函数MergeSortNonR接受一个整数数组a和数组长度n作为参数。首先分配一个临时数组tmp,如果分配失败则输出错误信息并返回。然后使用迭代的方式逐步增大间隔gap,对数组进行分组归并排序。最后释放临时数组的内存


本篇不出意料,是初阶数据结构的最后一篇,下一篇我们要开始C++了。

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

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

相关文章

6.5 Batch Normalization

在训练神经网络时&#xff0c;往往需要标准化&#xff08;normalization&#xff09;输入数据&#xff0c;使得网络的训练更加快速和有效。 然而SGD&#xff08;随机梯度下降&#xff09;等学习算法会在训练中不断改变网络的参数&#xff0c;隐藏层的激活值的分布会因此发生变…

MySQL故障排查与生产环境优化

一、MySQL单实例常见故障 1.逻辑架构图 MySQL逻辑架构图客户端和连接服务核心服务功能存储引擎层数据存储层 2.故障一 故障现象 ERROR 2002 (HY000): Cant connect to local MySQL server through socket/data/mysql/mysql.sock(2) 问题分析 数据库未启动或者数据库端口…

淘宝优惠券去哪里领取隐藏内部券?

淘宝优惠券去哪里领取隐藏内部券&#xff1f; 1、手机安装「草柴」APP&#xff0c;打开手机淘宝挑选要购买的商品&#xff0c;并点击分享复制链接&#xff1b; 2、将复制的商品链接&#xff0c;粘贴到草柴APP&#xff0c;并点击立即查询该商品的优惠券和约返利&#xff1b; 3、…

java题目15:从键盘输入n个数,求这n个数中的最大数与最小数并输出(MaxAndMin15)

每日小语 你是否有资格摆脱身上的枷锁呢&#xff1f;有许多人一旦获得解放&#xff0c;他的最后一点价值也就会跟着丧失。 ——尼采 自己敲写 它不按我想的来。。。 //从键盘输入n个数&#xff0c;求这n个数中的最大数与最小数并输出 import java.util.Scanner; public clas…

软件测评师教程之软件测试基础

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

Mysql数据库:故障分析与配置优化

目录 前言 一、Mysql逻辑架构图 二、Mysql单实例常见故障 1、无法通过套接字连接到本地MySQL服务器 2、用户rootlocalhost访问被拒绝 3、远程连接数据库时连接很慢 4、无法打开以MYI结尾的索引文件 5、超出最大连接错误数量限制 6、连接过多 7、配置文件/etc/my.cnf权…

全栈开发与测试定向培养班

Python全栈开发与测试 什么是软件测试&#xff1f; 对于测试行业来说&#xff0c;行业普遍会把职位分为测试工程师和测试开发工程师两个岗位。软件测试工程师就是常规意义上了解到的功能测试岗位&#xff0c;以功能测试为主,会有少量的自动化测试。测试能力要求&#xff1a;熟…

键盘输入与屏幕输出——单个字符的输入和输出

目录 字符常量 字符型变量 单个字符的输入输出 两种输入输出方法的比较 字符常量 字符常量是用单引号括起来的一个符号 *’3’表示一个数字字符&#xff0c;而3则表示一个整数数值 转义字符&#xff08;Escape Character&#xff09; *一些特殊字符&#xff08;无法从键盘…

Dimitra:基于区块链、AI 等前沿技术重塑传统农业

根据 2023 年联合国粮食及农业组织&#xff08;FAO&#xff09;、国际农业发展基金&#xff08;IFAD&#xff09;等组织联合发布的《世界粮食安全和营养状况》报告显示&#xff0c;目前全球约有 7.35 亿饥饿人口&#xff0c;远高于 2019 年的 6.13 亿&#xff0c;这意味着农业仍…

【Linux C | 多线程编程】线程的连接、分离,资源销毁情况

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a;2024-04-01 1…

讲解pwngdb的用法,以csapp的bomb lab phase_1为例

参考资料 Guide to Faster, Less Frustrating Debugging 什么情况下会使用gbd 需要逆向ELF文件时(掌握gdb的使用&#xff0c;是二进制安全的基本功)开发程序时&#xff0c;程序执行结果不符合预期 动态调试ELF文件可以使用另外一种方法&#xff1a;IDA的远程linux动态调试。个…

探索 Redis 数据库:一款高性能的内存键值存储系统

目录 引言 一、非关系型数据库 &#xff08;一&#xff09;什么是非关系型数据库 &#xff08;二&#xff09;非关系型数据库的主要特征 &#xff08;三&#xff09;关系数据库与非关系型数据库的区别 二、Redis 简介 &#xff08;一&#xff09;基本信息 &#xff08;…

栈————顺序栈和链式栈

目录 栈 顺序栈 1、初始化顺序栈 2、判栈空 3、进栈 4、出栈 5、读栈顶元素 6、遍历 链式栈 1、初始化链式栈 2、断链式栈是否为空判 3、入栈(插入) ​编辑​编辑 4、出栈(删除) 5、读取栈顶元素 6、输出链式栈中各个节点的值&#xff08;遍历&#xff09; 栈 …

LeetCode-240. 搜索二维矩阵 II【数组 二分查找 分治 矩阵】

LeetCode-240. 搜索二维矩阵 II【数组 二分查找 分治 矩阵】 题目描述&#xff1a;解题思路一&#xff1a;从左下角或者右上角元素出发&#xff0c;来寻找target。解题思路二&#xff1a;右上角元素&#xff0c;代码解题思路三&#xff1a;暴力也能过解题思路四&#xff1a;二分…

【小呆的力学笔记】弹塑性力学的初步认知六:后继屈服条件

文章目录 4. 后继屈服条件4.1 后继屈服条件4.2 强化模型4.2.1 等向强化模型4.2.2 随动强化模型4.2.3 两种强化模型的讨论 4. 后继屈服条件 4.1 后继屈服条件 上一章节的屈服条件是在当材料未经受任何塑性变形时且在载荷作用下材料第一次进入屈服应该满足的条件&#xff08;也…

Vscode + PlatformIO + Arduino 搭建EPS32开发环境

Vscode PlatformIO Arduino 搭建EPS32开发环境 文章目录 Vscode PlatformIO Arduino 搭建EPS32开发环境1. Vscode插件安装2. 使用PlatformIO新建工程3.工程文件的基本结构4.一个基本的测试用例Reference 1. Vscode插件安装 如何下载vscode这里不再赘述&#xff0c;完成基本…

LeetCode-热题100:160. 相交链表

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…

异常,Lambda表达式

文章目录 异常介绍存在形式程序中异常发生后的第一反应体系JVM的默认处理方案处理方式声明 throws概述格式抛出 throw格式注意意义 throws和throw的区别 捕获 try,catch介绍格式执行方式多异常捕获处理意义 如何选择用哪个 Throwable类介绍常用方法 自定义异常概述实现步骤范例…

2_3.Linux系统中的日志管理

# 1.journald # 服务名称&#xff1a;systemd-journald.service journalctl 默认日志存放路径&#xff1a; /run/log &#xff08;1&#xff09; journalctl命令的用法 journalctl -n 3 ##日志的最新3条--since "2020-05-01 11:00:00" ##显示11&#xff1a;00后的日…

Mysql的高级语句3

目录 一、子查询 注意&#xff1a;子语句可以与主语句所查询的表相同&#xff0c;但是也可以是不同表。 1、select in 1.1 相同表查询 1.2 多表查询 2、not in 取反&#xff0c;就是将子查询结果&#xff0c;进行取反处理 3、insert into in 4、update…