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

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

  • 分解:分解待排序的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++实现一下:

cpp 复制代码
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++;
		}
	}
}

这样我们可以得到自底向上的排序:

算法的伪代码是:

cpp 复制代码
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);
    }
}

测试代码:

cpp 复制代码
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;
}

第一行是未排序,第二行是排序以后的结果。

完整代码:

cpp 复制代码
#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函数

模拟的写法

cpp 复制代码
/**
 * 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;


    }
};

另一种递归的写法:

cpp 复制代码
/**
 * 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))

cpp 复制代码
/**
 * 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个,以此类推,这里每两个链表也是使用最重要的是找到两个链表的头指针:

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

cpp 复制代码
while (node->next != NULL)//node不为空
{
    node = node->next;
}

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

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

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

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

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

cpp 复制代码
/**
 * 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;

    }
};
相关推荐
莫叫石榴姐8 分钟前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
茶猫_1 小时前
力扣面试题 - 25 二进制数转字符串
c语言·算法·leetcode·职场和发展
Hera_Yc.H2 小时前
数据结构之一:复杂度
数据结构
肥猪猪爸3 小时前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
linux_carlos3 小时前
环形缓冲区
数据结构
readmancynn3 小时前
二分基本实现
数据结构·算法
萝卜兽编程3 小时前
优先级队列
c++·算法
Bucai_不才3 小时前
【数据结构】树——链式存储二叉树的基础
数据结构·二叉树
盼海4 小时前
排序算法(四)--快速排序
数据结构·算法·排序算法
一直学习永不止步4 小时前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表