数据结构和算法笔记4:排序算法-归并排序

news2024/9/24 17:41:07

归并排序算法完全遵循分治模式。直观上其操作如下:

  • 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列。
  • 解决:使用归并排序递归地排序两个子序列。
  • 合并:合并两个已排序的子序列以产生已排序的答案。

我们直接来看例子理解算法的过程,下面是要排序的数组,总共8个元素,我们划分为左右两个数组L和R(L和R都已经是有序的),L是原数组左边4个元素,R是原数组右边4个元素,为了让排序终止,两个数组的末尾加了一个无穷,用k指针指向原数组第一个元素2,i指针指向L数组第一个元素2,j指针指向R数组第一个元素1:

在这里插入图片描述

比较i指针所指2和j指针所指1:j指针所指1更小,k指针对应第一个元素赋值为1,j和k指针右移。
在这里插入图片描述

比较i指针所指2和j指针所指2:一样大,k指针对应第2个元素赋值为2,i和k指针右移。
在这里插入图片描述

比较i指针所指4和j指针所指2:j指针所指2更小,k指针对应第3个元素赋值为2,j和k指针右移。
在这里插入图片描述
如此做下去,直到i和j指针都遍历到最后:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于上面的逻辑我们可以写作一个函数叫merge,它的目的是合并两个有序数组为一个新的有序数组,它的伪代码是:

在这里插入图片描述

其中A是一个数组,p、q和r是数组下标,满足p≤q<r。该过程假设子数组A[p.q]和A[q+1,r]都已排好序。它合并这两个子数组形成单一的已排好序的子数组并代替当前的子数组A[p.r]。

用C++实现一下:

std::vector& merge(std::vector<int>& A, int p, int q, int r)
{
	int n1 = q - p + 1;
	int n2 = r - q;
	std::vector<int> L(n1, 0);//[p,q]
	std::vector<int> R(n2, 0);//[q+1,r]
	for (int i = 0; i < n1; ++i)
		L[i] = A[p + i];
	for (int i = 0; i < n2; ++i)
		R[i] = A[q + i - 1];
	int i = 0;
	int j = 0;
	for (int k = p; k <= r; ++k)
	{
		if (i == n1)
		{
			A[k] = R[j];
			j++;
		}
		else if (j == n2)
		{
			A[k] = L[i];
			i++;
		}
		else if (L[i] <= R[j])
		{
			A[k] = L[i];
			i++;
		}
		else
		{
			A[k] = R[j];
			j++;
		}
	}
}

这样我们可以得到自底向上的排序:
在这里插入图片描述

算法的伪代码是:
在这里插入图片描述

void mergeSort(std::vector<int> &A, int p, int r)
{
    if (p < r)
    {
        int q = (p + r) / 2;
        mergeSort(A, p, q);
        mergeSort(A, q + 1, r);
        merge(A, p, q, r);
    }
}

测试代码:

int main()
{
    std::vector<int> A({1, 3, 2, 4, 5, 6, 2, 3});
    for (auto &a : A)
    {
        std::cout << a << " ";
    }
    std::cout << std::endl;
    int len = A.size();
    mergeSort(A, 0, len - 1);
    for (auto &a : A)
    {
        std::cout << a << " ";
    }
    return 0;
}

第一行是未排序,第二行是排序以后的结果。
在这里插入图片描述
完整代码:

#include <iostream>
#include <string>
#include <vector>

void merge(std::vector<int> &A, int p, int q, int r)
{
    int n1 = q - p + 1;
    int n2 = r - q;
    std::vector<int> L(n1, 0); //[p,q]
    std::vector<int> R(n2, 0); //[q+1,r]
    for (int i = 0; i < n1; ++i)
        L[i] = A[p + i];
    for (int i = 0; i < n2; ++i)
        R[i] = A[q + i + 1];
    int i = 0;
    int j = 0;
    for (int k = p; k <= r; ++k)
    {
        if (i == n1)
        {
            A[k] = R[j];
            j++;
        }
        else if (j == n2)
        {
            A[k] = L[i];
            i++;
        }
        else if (L[i] <= R[j])
        {
            A[k] = L[i];
            i++;
        }
        else
        {
            A[k] = R[j];
            j++;
        }
    }
}

void mergeSort(std::vector<int> &A, int p, int r)
{
    if (p < r)
    {
        int q = (p + r) / 2;
        mergeSort(A, p, q);
        mergeSort(A, q + 1, r);
        merge(A, p, q, r);
    }
}
int main()
{
    std::vector<int> A({1, 3, 2, 4, 5, 6, 2, 3});
    for (auto &a : A)
    {
        std::cout << a << " ";
    }
    std::cout << std::endl;
    int len = A.size();
    mergeSort(A, 0, len - 1);
    for (auto &a : A)
    {
        std::cout << a << " ";
    }
    return 0;
}

力扣相关题目

21.合并两个有序链表

学完了归并排序可以顺带把力扣的21.合并两个有序链表也做一下,等同于上面的merge函数

在这里插入图片描述

模拟的写法

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        
        ListNode* dummy = new ListNode(0);
        ListNode* node = dummy;
        while (list1 != NULL && list2 != NULL)
        {
            if (list1->val < list2->val)
            {
                node->next = list1;
                list1 = list1->next;
            }
            else
            {
                node->next = list2;
                list2 = list2->next;
            }
            node = node->next;
        }
        node->next = list1 == NULL ? list2 : list1;
        return dummy->next;


    }
};

另一种递归的写法:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        
        if (list1 == NULL)
            return list2;
        else if (list2 == NULL)
            return list1;
        else if (list1->val < list2->val)
        {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }
        else
        {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

148. 排序链表

在这里插入图片描述

自顶向下的排序。使用的是左闭右开的思路,传入的两个指针一个是头指针,一个可以是空指针(开区间)。然后使用快慢指针找中点确定mid的位置。这样就可以递归地合并链表mergeTwoLists(sortList(head, mid), sortList(mid, tail))

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:

    ListNode* sortList(ListNode* head) {
        return sortList(head, NULL);

    }
    ListNode* sortList(ListNode* head, ListNode* tail)
    {
        if (head == NULL)
            return head;
        if (head->next == tail)//左闭右开
        {
            head->next = NULL;
            return head;
        }
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast != tail)
        {
            slow = slow->next;
            fast = fast->next;
            if (fast != tail)
                fast = fast->next;
        }
        ListNode* mid = slow;
        return mergeTwoLists(sortList(head, mid), sortList(mid, tail));
    }
    ListNode* mergeTwoLists(ListNode* head1, ListNode* head2)
    {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        ListNode* cur1 = head1;
        ListNode* cur2 = head2;
        while (cur1 != NULL && cur2 != NULL)
        {
            if (cur1->val < cur2->val)
            {
                cur->next = cur1;
                cur1 = cur1->next;
            }
            else
            {
                cur->next = cur2;
                cur2 = cur2->next;
            }
            cur = cur->next;
        }
        cur->next = (cur1 == NULL ? cur2 : cur1);
        return dummy->next;

    }
};

还有一种自底向上的排序,使用的就是前面讲的方法。自底向上的排序,先排序1个长度的链表,然后2个,4个,以此类推,这里每两个链表也是使用最重要的是找到两个链表的头指针:

查找最后一个不为空的链表指针:

while (node->next != NULL)//node不为空
{
    node = node->next;
}

从node开始第subLength个指针(node不为空)

for (int i = 1; node->next != NULL && i < subLength; i++)//node确定不为空
{
    node = node->next;//到subLength的尾部
}

从node开始第subLength个指针,如果node可能为空:

for (int i = 1; node != NULL && node->next != NULL && i < subLength; i++)//node确定不为空
{
    node = node->next;//到subLength的尾部
}

每次找到subLength个指针的位置,把它后面的位置记录下来,然后它后面的位置设为空指针。方便后面的链表的合并。用mergeTwoLists合并两个链表,赋值头结点给pre的next指针,然后next指针移动到这个链表的尾部,后面还要继续相同的操作。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:

    ListNode* sortList(ListNode* head) {
        if (head == NULL)
            return head;
        ListNode* dummy = new ListNode(0, head);
        ListNode* temp = dummy;
        int length = 0;
        while (temp->next != NULL)
        {
            temp = temp->next;
            length++;
        }
        for (int subLength = 1; subLength < length; subLength <<= 1)
        {
            ListNode* pre = dummy;
            ListNode* cur = dummy->next;
            while (cur != NULL)
            {
                ListNode* head1 = cur;
                for (int i = 1; cur->next != NULL && i < subLength; i++)
                {
                    cur = cur->next;//到subLength的尾部
                }
                ListNode* head2 = cur->next;
                cur->next = NULL;
                cur = head2;
                for (int i = 1; cur != NULL && cur->next != NULL && i < subLength; i++)
                {
                    cur = cur->next;
                }
                ListNode* next = NULL;
                if (cur != NULL)
                {
                    next = cur->next;
                    cur->next = NULL;
                }
                pre->next = mergeTwoLists(head1, head2);
                while (pre->next != NULL)
                {
                    pre = pre->next;
                }
                cur = next;
            }
        }
        return dummy->next;

    }

    ListNode* mergeTwoLists(ListNode* head1, ListNode* head2)
    {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        ListNode* cur1 = head1;
        ListNode* cur2 = head2;
        while (cur1 != NULL && cur2 != NULL)
        {
            if (cur1->val < cur2->val)
            {
                cur->next = cur1;
                cur1 = cur1->next;
            }
            else
            {
                cur->next = cur2;
                cur2 = cur2->next;
            }
            cur = cur->next;
        }
        cur->next = (cur1 == NULL ? cur2 : cur1);
        return dummy->next;

    }
};

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

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

相关文章

ubuntu-20.04.6-live-server-amd64安装教程-完整版

简介 Ubuntu 20.04.6 Live Server AMD64 安装教程 - 完整版" 提供了详细的指南&#xff0c;旨在帮助用户在使用 AMD64 架构的服务器上安装 Ubuntu 20.04.6 Live Server 版本。该教程包含全面的步骤和详细说明&#xff0c;使用户能够顺利完成整个安装过程&#xff0c;建立…

力扣70. 爬楼梯(动态规划 Java,C++解法)

Problem: 70. 爬楼梯 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 由于本题目中第i层台阶只能由于第i- 1层台阶和第i-2层台阶走来&#xff0c;所以可以联想到动态规划&#xff0c;具体如下&#xff1a; 1.定义多阶段决策模型&#xff1a;对于每一上台阶看作一种状…

【LGR-172-Div.4】洛谷入门赛 #19(A—H,c++详解!)

文章目录 【LGR-172-Div.4】洛谷入门赛 #19A.分饼干 I题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示样例解释 1样例解释 2数据范围与约定思路: 代码 B.分饼干 II题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样…

SpringMVC基础知识学习笔记

Universe Infinity Inc. 目录 一、学习SpringMVC主要是学什么1、SpringMVC的基本原理2、SpringMVC学习串联 二、快速体验SpringMVC的开发1、新建项目&#xff0c;转成web项目2、引入依赖3、编写Spring的配置类4、配置web启动类&#xff0c;替代web.xml5、编写Handler&#xff…

助力焊接场景下自动化缺陷检测识别,基于YOLOv3模型开发构建工业焊接场景下缺陷检测识别分析系统

焊接是一个不陌生但是对于开发来说相对小众的场景&#xff0c;在我们前面的博文开发实践中也有一些相关的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《轻量级模型YOLOv5-Lite基于自己的数据集【焊接质量检测】从零构建模型超详细教程》 《基于DeepLabV3Pl…

令牌桶算法与Guava的实现RateLimiter源码分析

令牌桶算法与Guava的实现RateLimiter源码分析 令牌桶RateLimiter简介RateLimiter使用示例导入maven依赖编写测试代码 RateLimiter的实现源码解析SmoothRateLimiterSmoothBursty恒速获取令牌acquire(int)tryAcquire(int,long,TimeUnit) 存量桶系数小结 优缺点与漏桶的区别总结 令…

01-开始Rust之旅

1. 下载Rust 官方推荐使用 rustup 下载 Rust&#xff0c;这是一个管理 Rust 版本和相关工具的命令行工具。下载时需要连接互联网。 这边提供了离线安装版本。本人学习的机器环境为&#xff1a; ubuntu x86_64&#xff0c;因此选用第②个工具链&#xff1b; 1. rust-1.75.0-x86_…

CloudPanel RCE漏洞复现(CVE-2023-35885)

0x01 产品简介 CloudPanel 是一个基于 Web 的控制面板或管理界面,旨在简化云托管环境的管理。它提供了一个集中式平台,用于管理云基础架构的各个方面,包括虚拟机 (VM)、存储、网络和应用程序。 0x02 漏洞概述 由于2.3.1 之前的 CloudPanel 具有不安全的文件管理器 cook…

【JSON2WEB】01 WEB管理信息系统架构设计

WEB管理信息系统分三层设计&#xff0c;分别为DataBase数据库、REST2SQL后端、JSON2WEB前端&#xff0c;三层都可以单独部署。 1 DataBase数据库 数据库根据需要选型即可&#xff0c;不需要自己设计开发&#xff0c;一般管理信息系统都选关系数据库&#xff0c;比如Oracle、…

beego的模块篇 - I18n国际化

1. i18n 安装导入 安装该模块&#xff1a; go get github.com/beego/i18n 导入引用包&#xff1a; import ("github.com/beego/i18n" ) conf 目录下就有 locale_en-US.ini 和 locale_zh-CN.ini 两个本地化文件。 本地化文件的文件名和后缀是随意的&#xff0c;不…

C++_Lambda表达式的完整介绍

目录 1. 什么是Lambda表达式 1.1 四种表达式的含义 1.2 lambda表达式各个成员的解释 2. 捕获列表 3. 编译器如何看待Lambda表达式 参考文章 参考: C Lambda表达式的完整介绍 - 知乎 c在c11标准中引入了lambda表达式&#xff0c;一般用于定义匿名函数&#xff0c;使得代码…

超过GPT3.5?Mixtral 8*7B 模型结构分析

Datawhale干货 作者&#xff1a;宋志学&#xff0c;Datawhale成员 前言 2023年12月11日&#xff0c;Mistral AI团队发布了一款高质量的稀疏专家混合模型Mixtral 8x7B。 Mistral AI继续致力于向开发者社区提供最优秀的开放模型。在人工智能领域向前发展&#xff0c;需要采取超越…

关于SpringBoot项目整合Log4j2实现自定义日志打印失效原因

主要的原因是因为&#xff0c;SpringBoot的logback包的存在&#xff0c;会导致Spring Boot项目优先实现logback的日志设置&#xff0c;所以导致我们用Log4j2实现自定义日志失效。 先找l哪个包引用了logback包 进入之后查询logback 然后双击包 发现是spring-boot-starter-loggin…

UVa1318/LA2797 Monster Trap

题目链接 本题是2003年ICPC亚洲区域赛会津(日本)赛区的H题 题意 给出一些线段障碍&#xff0c;你的任务是判断怪物能否逃到无穷远处。如下图所示&#xff0c;左图无法逃出&#xff0c;右图的可以逃出。 输入包含多组数据。每组数据第一行为整数n&#xff08;1≤n≤100&#xf…

C++编写、生成、调用so库详解(一)

开发中经常会用到so库,大多是调用第三方的so库,偶尔也需要自己封装一个so库给别人调用,这边就记录一下开发so库的一个过程. 首先我们这边是在Android Studio中开发的,所以仅描述在Android环境下开发过程,当然也可以用其他工具开发. 目录 1.第一步新建项目,配置需要的工具 2…

插件分享 Chrome浏览器实现外语翻译自由

【有道灵动翻译】使用有道翻译大模型&#xff0c;沉浸式网页翻译的首选工具&#xff01; 实时对照翻译:让任何网页变成对照。输入框即时翻译:输入中文轻松变英文。 &#x1f525;功能亮点&#x1f525;&#xff1a; 实时对照翻译&#xff1a;使用有道翻译大模型&#xff0c;无…

了解Vue中日历插件Fullcalendar

实现效果如下图&#xff1a; 月视图 周视图 日视图 官方文档地址&#xff1a;Vue Component - Docs | FullCalendar 1、安装与FullCalendar相关的依赖项 npm install --save fullcalendar/vue fullcalendar/core fullcalendar/daygrid fullcalendar/timegrid fullcalend…

MySQL复合查询 内外连接

目录 前言&#xff1a; 多表查询&#xff1a; 显示部门号为10的部门名&#xff0c;员工名和工资 : 显示各个员工的姓名&#xff0c;工资&#xff0c;及工资级别: 自连接 显示员工FORD的上级领导的编号和姓名(mgr是员工领导的编号&#xff09; 子查询 单行子查询&#…

IPv6自动隧道---6to4中继

6to4中继 普通IPv6网络需要与6to4网络通过IPv4网络互通,这可以通过6to4中继路由器方式实现。所谓6to4中继,就是通过6to4隧道转发的IPv6报文的目的地址不是6to4地址,但转发的下一跳是6to4地址,该下一跳为路由器我们称之为6to4中继。隧道的IPv4目的地址依然从下一跳的6to4地…

电池容量常见测试方法分享 -纳米软件

电池容量是衡量电池性能的重要指标之一&#xff0c;它是指电池在一定条件下放出的电量&#xff0c;可以用于帮助评估电池的性能和寿命。那么如何快速测试电池容量呢? 一、用万用表测试 用万用表测试电池容量&#xff0c;需要将万用表调整到电容模式&#xff0c;然后连接电池到…